Initial commit
This commit is contained in:
commit
fe3aa4a77e
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
* text=auto
|
||||||
|
*.sh text eol=lf
|
303
.gitignore
vendored
Normal file
303
.gitignore
vendored
Normal file
@ -0,0 +1,303 @@
|
|||||||
|
*.d
|
||||||
|
*.slo
|
||||||
|
*.lo
|
||||||
|
*.o
|
||||||
|
*.obj
|
||||||
|
*.gch
|
||||||
|
*.pch
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.dll
|
||||||
|
*.mod
|
||||||
|
*.smod
|
||||||
|
*.lai
|
||||||
|
*.la
|
||||||
|
*.a
|
||||||
|
*.lib
|
||||||
|
*.exe
|
||||||
|
*.out
|
||||||
|
*.app
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/**/usage.statistics.xml
|
||||||
|
.idea/**/dictionaries
|
||||||
|
.idea/**/shelf
|
||||||
|
.idea/**/aws.xml
|
||||||
|
.idea/**/contentModel.xml
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/dbnavigator.xml
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
cmake-build-*/
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
*.iws
|
||||||
|
out/
|
||||||
|
.idea_modules/
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
.idea/replstate.xml
|
||||||
|
.idea/sonarlint/
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
.idea/httpRequests
|
||||||
|
.idea/caches/build_file_checksums.ser
|
||||||
|
*~
|
||||||
|
.fuse_hidden*
|
||||||
|
.directory
|
||||||
|
.Trash-*
|
||||||
|
.nfs*
|
||||||
|
CMakeLists.txt.user
|
||||||
|
CMakeCache.txt
|
||||||
|
CMakeFiles
|
||||||
|
CMakeScripts
|
||||||
|
Testing
|
||||||
|
Makefile
|
||||||
|
cmake_install.cmake
|
||||||
|
install_manifest.txt
|
||||||
|
compile_commands.json
|
||||||
|
CTestTestfile.cmake
|
||||||
|
_deps
|
||||||
|
*.rsuser
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
*.userprefs
|
||||||
|
mono_crash.*
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
[Ww][Ii][Nn]32/
|
||||||
|
[Aa][Rr][Mm]/
|
||||||
|
[Aa][Rr][Mm]64/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Ll]og/
|
||||||
|
[Ll]ogs/
|
||||||
|
.vs/
|
||||||
|
Generated\ Files/
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
*.VisualState.xml
|
||||||
|
TestResult.xml
|
||||||
|
nunit-*.xml
|
||||||
|
[Dd]ebugPS/
|
||||||
|
[Rr]eleasePS/
|
||||||
|
dlldata.c
|
||||||
|
BenchmarkDotNet.Artifacts/
|
||||||
|
project.lock.json
|
||||||
|
project.fragment.lock.json
|
||||||
|
artifacts/
|
||||||
|
ScaffoldingReadMe.txt
|
||||||
|
StyleCopReport.xml
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_h.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.iobj
|
||||||
|
*.pdb
|
||||||
|
*.ipdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*_wpftmp.csproj
|
||||||
|
*.log
|
||||||
|
*.tlog
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
_Chutzpah*
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opendb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
*.VC.db
|
||||||
|
*.VC.VC.opendb
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
*.sap
|
||||||
|
*.e2e
|
||||||
|
$tf/
|
||||||
|
*.gpState
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.DotSettings.user
|
||||||
|
_TeamCity*
|
||||||
|
*.dotCover
|
||||||
|
.axoCover/*
|
||||||
|
!.axoCover/settings.json
|
||||||
|
coverage*.json
|
||||||
|
coverage*.xml
|
||||||
|
coverage*.info
|
||||||
|
*.coverage
|
||||||
|
*.coveragexml
|
||||||
|
_NCrunch_*
|
||||||
|
.*crunch*.local.xml
|
||||||
|
nCrunchTemp_*
|
||||||
|
*.mm.*
|
||||||
|
AutoTest.Net/
|
||||||
|
.sass-cache/
|
||||||
|
[Ee]xpress/
|
||||||
|
DocProject/buildhelp/
|
||||||
|
DocProject/Help/*.HxT
|
||||||
|
DocProject/Help/*.HxC
|
||||||
|
DocProject/Help/*.hhc
|
||||||
|
DocProject/Help/*.hhk
|
||||||
|
DocProject/Help/*.hhp
|
||||||
|
DocProject/Help/Html2
|
||||||
|
DocProject/Help/html
|
||||||
|
publish/
|
||||||
|
*.[Pp]ublish.xml
|
||||||
|
*.azurePubxml
|
||||||
|
*.pubxml
|
||||||
|
*.publishproj
|
||||||
|
PublishScripts/
|
||||||
|
*.nupkg
|
||||||
|
*.snupkg
|
||||||
|
**/[Pp]ackages/*
|
||||||
|
!**/[Pp]ackages/build/
|
||||||
|
*.nuget.props
|
||||||
|
*.nuget.targets
|
||||||
|
csx/
|
||||||
|
*.build.csdef
|
||||||
|
ecf/
|
||||||
|
rcf/
|
||||||
|
AppPackages/
|
||||||
|
BundleArtifacts/
|
||||||
|
Package.StoreAssociation.xml
|
||||||
|
_pkginfo.txt
|
||||||
|
*.appx
|
||||||
|
*.appxbundle
|
||||||
|
*.appxupload
|
||||||
|
*.[Cc]ache
|
||||||
|
!?*.[Cc]ache/
|
||||||
|
ClientBin/
|
||||||
|
~$*
|
||||||
|
*.dbmdl
|
||||||
|
*.dbproj.schemaview
|
||||||
|
*.jfm
|
||||||
|
*.pfx
|
||||||
|
*.publishsettings
|
||||||
|
orleans.codegen.cs
|
||||||
|
Generated_Code/
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
UpgradeLog*.htm
|
||||||
|
ServiceFabricBackup/
|
||||||
|
*.rptproj.bak
|
||||||
|
*.mdf
|
||||||
|
*.ldf
|
||||||
|
*.ndf
|
||||||
|
*.rdl.data
|
||||||
|
*.bim.layout
|
||||||
|
*.bim_*.settings
|
||||||
|
*.rptproj.rsuser
|
||||||
|
*- [Bb]ackup.rdl
|
||||||
|
*- [Bb]ackup ([0-9]).rdl
|
||||||
|
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||||
|
FakesAssemblies/
|
||||||
|
*.GhostDoc.xml
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
node_modules/
|
||||||
|
*.plg
|
||||||
|
*.opt
|
||||||
|
*.vbw
|
||||||
|
*.vbp
|
||||||
|
*.dsw
|
||||||
|
*.dsp
|
||||||
|
**/*.HTMLClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/ModelManifest.xml
|
||||||
|
**/*.Server/GeneratedArtifacts
|
||||||
|
**/*.Server/ModelManifest.xml
|
||||||
|
_Pvt_Extensions
|
||||||
|
.paket/paket.exe
|
||||||
|
paket-files/
|
||||||
|
.fake/
|
||||||
|
.cr/personal
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.tss
|
||||||
|
*.jmconfig
|
||||||
|
*.btp.cs
|
||||||
|
*.btm.cs
|
||||||
|
*.odx.cs
|
||||||
|
*.xsd.cs
|
||||||
|
OpenCover/
|
||||||
|
ASALocalRun/
|
||||||
|
*.binlog
|
||||||
|
*.nvuser
|
||||||
|
.mfractor/
|
||||||
|
.localhistory/
|
||||||
|
.vshistory/
|
||||||
|
healthchecksdb
|
||||||
|
MigrationBackup/
|
||||||
|
.ionide/
|
||||||
|
FodyWeavers.xsd
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
*.code-workspace
|
||||||
|
.history/
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msix
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
*.sln.iml
|
||||||
|
Thumbs.db
|
||||||
|
Thumbs.db:encryptable
|
||||||
|
ehthumbs.db
|
||||||
|
ehthumbs_vista.db
|
||||||
|
*.stackdump
|
||||||
|
[Dd]esktop.ini
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
*.lnk
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
Icon
|
||||||
|
._*
|
||||||
|
.DocumentRevisions-V100
|
||||||
|
.fseventsd
|
||||||
|
.Spotlight-V100
|
||||||
|
.TemporaryItems
|
||||||
|
.Trashes
|
||||||
|
.VolumeIcon.icns
|
||||||
|
.com.apple.timemachine.donotpresent
|
||||||
|
.AppleDB
|
||||||
|
.AppleDesktop
|
||||||
|
Network Trash Folder
|
||||||
|
Temporary Items
|
||||||
|
.apdisk
|
||||||
|
|
||||||
|
.runenv/
|
||||||
|
vcpkg_installed/
|
||||||
|
git2
|
43
CMakeLists.txt
Normal file
43
CMakeLists.txt
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.30)
|
||||||
|
project(mmo)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 26)
|
||||||
|
if (MSVC)
|
||||||
|
add_compile_options(/W4)
|
||||||
|
add_compile_options(/WX)
|
||||||
|
add_compile_options(/external:anglebrackets)
|
||||||
|
add_compile_options(/external:W0)
|
||||||
|
add_compile_options(/wd4100)
|
||||||
|
add_compile_options(/wd5050)
|
||||||
|
add_definitions(-DWIN32_LEAN_AND_MEAN -DVC_EXTRALEAN)
|
||||||
|
add_compile_definitions(WIN32_LEAN_AND_MEAN NOMINMAX)
|
||||||
|
else ()
|
||||||
|
add_compile_options(-Wall)
|
||||||
|
add_compile_options(-Wextra)
|
||||||
|
add_compile_options(-Wpedantic)
|
||||||
|
add_compile_options(-Werror)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
# function add santizers
|
||||||
|
function(add_sanitizers target)
|
||||||
|
if (MSVC)
|
||||||
|
target_compile_options(${target} PRIVATE /fsanitize=address /fsanitize=fuzzer /Zi)
|
||||||
|
target_link_options(${target} PRIVATE /fsanitize=address /fsanitize=fuzzer /Zi)
|
||||||
|
else ()
|
||||||
|
target_compile_options(${target} PRIVATE -fsanitize=address -fsanitize=fuzzer)
|
||||||
|
target_link_options(${target} PRIVATE -fsanitize=address -fsanitize=fuzzer)
|
||||||
|
endif ()
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
if (DEFINED VCPKG_INSTALLED_DIR)
|
||||||
|
elseif (DEFINED ENV{VCPKG_ROOT})
|
||||||
|
include($ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake)
|
||||||
|
else ()
|
||||||
|
message(FATAL_ERROR "VCPKG is not loaded, set VCPKG_ROOT to automatically load it or specify the cmake toolchain")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||||
|
|
||||||
|
add_subdirectory(common)
|
||||||
|
#add_subdirectory(client)
|
||||||
|
#add_subdirectory(server)
|
38
README.md
Normal file
38
README.md
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
Add `-DCMAKE_TOOLCHAIN_FILE=K:\Dev\Tools\vcpkg\scripts\buildsystems\vcpkg.cmake` to cmake command line to use vcpkg
|
||||||
|
toolchain file.
|
||||||
|
|
||||||
|
This is only necessary in debug mode:
|
||||||
|
|
||||||
|
Modify the VCPKG port of crypto++ and cli11 add the following to the
|
||||||
|
`<vcpkg install dir>\ports\<cryptopp|cli11>\portfile.cmake` file before the configure
|
||||||
|
command:
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
if (MSVC)
|
||||||
|
set(VCPKG_CXX_FLAGS_DEBUG "/fsanitize=address /fsanitize=fuzzer")
|
||||||
|
set(VCPKG_C_FLAGS_DEBUG "/fsanitize=address /fsanitize=fuzzer")
|
||||||
|
set(VCPKG_LINKER_FLAGS_DEBUG "/fsanitize=address /fsanitize=fuzzer")
|
||||||
|
else ()
|
||||||
|
set(VCPKG_CXX_FLAGS_DEBUG -fsanitize=address)
|
||||||
|
set(VCPKG_C_FLAGS_DEBUG -fsanitize=address)
|
||||||
|
set(VCPKG_LINKER_FLAGS_DEBUG -fsanitize=address)
|
||||||
|
endif ()
|
||||||
|
```
|
||||||
|
|
||||||
|
An overlay can be created but I don't want to maintain adding the overlay to the vcpkg toolchain file and keeping it up
|
||||||
|
to date with the vcpkg toolchain file.
|
||||||
|
|
||||||
|
Generate a certificate using the following command (for testing purposes only):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openssl req -new -newkey rsa:2048 -days 1 -nodes -x509 -keyout key.pem -out cert.pem -subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=www.example.com"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
Someone decided to migrate MSQuic's vcpkg to schannel instead of using OpenSSL (which is exclusive to windows).
|
||||||
|
This means that certificates are not supported, which is a problem. Which is why the 0-RTT feature is enabled by in
|
||||||
|
vcpkg.json, not because we need or want it.
|
||||||
|
|
||||||
|
Initially, Glaze was intended for parsing session info, but it caused issues with the MSVC compiler.
|
||||||
|
Instead, we're using simdjson.
|
3
common/CMakeLists.txt
Normal file
3
common/CMakeLists.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
add_subdirectory(logging)
|
||||||
|
add_subdirectory(connection)
|
||||||
|
add_subdirectory(packet)
|
62
common/connection/CMakeLists.txt
Normal file
62
common/connection/CMakeLists.txt
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
project(connection)
|
||||||
|
|
||||||
|
|
||||||
|
set(${PROJECT_NAME}_src
|
||||||
|
src/DataConnection.cppm
|
||||||
|
src/MSQuicConnection.cppm
|
||||||
|
src/MSQuicServer.cppm
|
||||||
|
src/MSQuicGlobal.cppm
|
||||||
|
src/MSQuicError.cppm)
|
||||||
|
# this is the "object library" target: compiles the sources only once
|
||||||
|
add_library(${PROJECT_NAME}_lib STATIC ${${PROJECT_NAME}_src})
|
||||||
|
target_include_directories(${PROJECT_NAME}_lib PUBLIC ./src)
|
||||||
|
|
||||||
|
find_package(msquic CONFIG REQUIRED)
|
||||||
|
target_link_libraries(${PROJECT_NAME}_lib PRIVATE msquic)
|
||||||
|
target_link_libraries(${PROJECT_NAME}_lib PUBLIC logging_lib)
|
||||||
|
if(WIN32)
|
||||||
|
target_link_libraries(${PROJECT_NAME}_lib PUBLIC ntdll)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Setup tests
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
|
find_package(GTest CONFIG REQUIRED)
|
||||||
|
include(GoogleTest)
|
||||||
|
|
||||||
|
file(GLOB_RECURSE tests_${PROJECT_NAME}_src CONFIGURE_DEPENDS tests/*.cppm tests/*/*.cppm)
|
||||||
|
|
||||||
|
add_executable(tests_${PROJECT_NAME} ${tests_${PROJECT_NAME}_src}
|
||||||
|
tests/sanitizers.cpp)
|
||||||
|
target_link_libraries(tests_${PROJECT_NAME} PRIVATE GTest::gtest GTest::gtest_main GTest::gmock GTest::gmock_main ${PROJECT_NAME}_lib)
|
||||||
|
|
||||||
|
add_test(AllTestsIn${PROJECT_NAME} tests_${PROJECT_NAME})
|
||||||
|
|
||||||
|
gtest_discover_tests(tests_${PROJECT_NAME})
|
||||||
|
|
||||||
|
find_program(OPENSSL_EXECUTABLE openssl)
|
||||||
|
|
||||||
|
set(OPENSSL_EXECUTABLE_CFG "" CACHE FILEPATH "Path to openssl.cnf")
|
||||||
|
if (NOT OPENSSL_EXECUTABLE)
|
||||||
|
find_package(OpenSSL REQUIRED)
|
||||||
|
|
||||||
|
message(STATUS "OpenSSL found: ${OPENSSL_FOUND} ${OPENSSL_VERSION} ${OPENSSL_INCLUDE_DIR} ${OPENSSL_LIBRARIES}")
|
||||||
|
|
||||||
|
set(OPENSSL_EXECUTABLE ${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/tools/openssl/openssl)
|
||||||
|
endif ()
|
||||||
|
if (NOT OPENSSL_EXECUTABLE)
|
||||||
|
message(FATAL_ERROR "OpenSSL not found")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (OPENSSL_EXECUTABLE MATCHES ^${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/tools/openssl/openssl)
|
||||||
|
set(OPENSSL_EXECUTABLE_CFG ${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/tools/openssl/openssl.cnf)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
add_custom_command(TARGET tests_${PROJECT_NAME} POST_BUILD
|
||||||
|
COMMAND "${OPENSSL_EXECUTABLE}" req -new -newkey rsa:2048 -days 1 -nodes -x509 -keyout key.pem -out cert.pem -subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=www.example.com" $<$<BOOL:${OPENSSL_EXECUTABLE_CFG}>:-config> $<$<BOOL:${OPENSSL_EXECUTABLE_CFG}>:${OPENSSL_EXECUTABLE_CFG}>
|
||||||
|
WORKING_DIRECTORY ${WORKING_DIRECTORY}
|
||||||
|
BYPRODUCTS ${WORKING_DIRECTORY}/key.pem ${WORKING_DIRECTORY}/cert.pem
|
||||||
|
COMMENT "Generating self-signed certificate using ${OPENSSL_EXECUTABLE} ${OPENSSL_EXECUTABLE_CFG}"
|
||||||
|
VERBATIM
|
||||||
|
COMMAND_EXPAND_LISTS
|
||||||
|
)
|
26
common/connection/src/DataConnection.cppm
Normal file
26
common/connection/src/DataConnection.cppm
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
export module MMO.DataConnection;
|
||||||
|
import std;
|
||||||
|
import :MSQuicConnection;
|
||||||
|
import :MSQuicServer;
|
||||||
|
|
||||||
|
namespace MMO::Networking {
|
||||||
|
export using NetworkingDataLane = MSQUIC::MSQuicStream;
|
||||||
|
export using NetworkingConnection = MSQUIC::MSQuicConnection;
|
||||||
|
export using NetworkingServer = MSQUIC::MSQuicServer;
|
||||||
|
|
||||||
|
export using NetworkingError = MSQUIC::MSQuicError;
|
||||||
|
|
||||||
|
export using NetworkSettings = QUIC_SETTINGS;
|
||||||
|
export using NetworkCertificate = QUIC_CERTIFICATE_FILE;
|
||||||
|
export using NetworkCertificateProtected = QUIC_CERTIFICATE_FILE_PROTECTED;
|
||||||
|
export using NetworkCredentials = QUIC_CREDENTIAL_CONFIG;
|
||||||
|
|
||||||
|
export namespace Additional {
|
||||||
|
using CertificateHash = QUIC_CERTIFICATE_HASH;
|
||||||
|
using CertificateHashStore = QUIC_CERTIFICATE_HASH_STORE;
|
||||||
|
using CertificateHashStoreFlags = QUIC_CERTIFICATE_HASH_STORE_FLAGS;
|
||||||
|
|
||||||
|
using CredentialFlags = QUIC_CREDENTIAL_FLAGS;
|
||||||
|
using CredentialType = QUIC_CREDENTIAL_TYPE;
|
||||||
|
}
|
||||||
|
}
|
850
common/connection/src/MSQuicConnection.cppm
Normal file
850
common/connection/src/MSQuicConnection.cppm
Normal file
@ -0,0 +1,850 @@
|
|||||||
|
export module MMO.DataConnection:MSQuicConnection;
|
||||||
|
|
||||||
|
import <msquic.h>;
|
||||||
|
import std;
|
||||||
|
import :MSQuicGlobal;
|
||||||
|
import :MSQuicError;
|
||||||
|
import MMO.Logging;
|
||||||
|
|
||||||
|
namespace MMO::Networking::MSQUIC {
|
||||||
|
export template<class T>
|
||||||
|
concept DataStorage = requires(T a) {
|
||||||
|
sizeof(typename std::remove_reference_t<decltype(a)>::value_type) == 1;
|
||||||
|
{ reinterpret_cast<uint8_t*>(std::data(a)) } -> std::same_as<uint8_t*>;
|
||||||
|
{ static_cast<uint32_t>(std::size(a)) } -> std::same_as<uint32_t>;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Forward declaration because we need to be able to ref MSQuicServer
|
||||||
|
QUIC_STATUS msquicClientConnectionCallbackProxy(HQUIC connection, void* context, QUIC_CONNECTION_EVENT* event);
|
||||||
|
QUIC_STATUS msquicClientStreamCallbackProxy(HQUIC connection, void* context, QUIC_STREAM_EVENT* event);
|
||||||
|
|
||||||
|
// Forward declaration because we need to be able to ref MSQuicConnection
|
||||||
|
QUIC_STATUS msquicServerListeningCallbackProxy(HQUIC connection, void* context, QUIC_LISTENER_EVENT* event);
|
||||||
|
QUIC_STATUS msquicServerConnectionCallbackProxy(HQUIC connection, void* context, QUIC_CONNECTION_EVENT* event);
|
||||||
|
QUIC_STATUS msquicServerStreamCallbackProxy(HQUIC connection, void* context, QUIC_STREAM_EVENT* event);
|
||||||
|
|
||||||
|
export class MSQuicServer;
|
||||||
|
export class MSQuicConnection;
|
||||||
|
|
||||||
|
export class MSQuicStream : public std::enable_shared_from_this<MSQuicStream> {
|
||||||
|
friend MSQuicConnection;
|
||||||
|
std::shared_ptr<MSQuicStream> backReference = nullptr;
|
||||||
|
std::shared_ptr<HQUIC> connectionStream = nullptr;
|
||||||
|
std::shared_ptr<void> streamAutoCloser = nullptr;
|
||||||
|
QUIC_STATUS status = QUIC_STATUS_NOT_FOUND;
|
||||||
|
|
||||||
|
std::vector<uint8_t> incomingData = { };
|
||||||
|
|
||||||
|
std::weak_ptr<MSQuicConnection> origin;
|
||||||
|
|
||||||
|
MSQuicStream(
|
||||||
|
std::shared_ptr<HQUIC> connectionStream,
|
||||||
|
std::shared_ptr<void> streamAutoCloser,
|
||||||
|
std::weak_ptr<MSQuicConnection> origin,
|
||||||
|
const QUIC_STATUS status
|
||||||
|
) :
|
||||||
|
connectionStream(std::move(connectionStream)), streamAutoCloser(std::move(streamAutoCloser)),
|
||||||
|
status(status),
|
||||||
|
origin(std::move(origin)) { }
|
||||||
|
|
||||||
|
public:
|
||||||
|
[[nodiscard]]
|
||||||
|
std::vector<uint8_t> retrieveAndResetData() {
|
||||||
|
std::vector<uint8_t> outgoingData;
|
||||||
|
outgoingData.swap(incomingData);
|
||||||
|
return outgoingData;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
bool hasData() const {
|
||||||
|
return !incomingData.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::weak_ptr<MSQuicConnection> getOrigin() {
|
||||||
|
return origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
bool isConnected() const {
|
||||||
|
return status == QUIC_STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Payload : public QUIC_BUFFER, std::enable_shared_from_this<Payload> {
|
||||||
|
// We use this persistent buffer to ensure that the data is not deallocated
|
||||||
|
std::any data;
|
||||||
|
|
||||||
|
template<DataStorage T>
|
||||||
|
explicit Payload(T& data) : data(data) {
|
||||||
|
this->Length = static_cast<uint32_t>(std::size(std::any_cast<T&>(this->data)));
|
||||||
|
this->Buffer = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(
|
||||||
|
std::data(std::any_cast<T&>(this->data))));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export class MSQuicConnection : public std::enable_shared_from_this<MSQuicConnection> {
|
||||||
|
public:
|
||||||
|
std::shared_ptr<Logging::Logger> logger = MMO::Logging::DEFAULT_LOGGER
|
||||||
|
? MMO::Logging::DEFAULT_LOGGER
|
||||||
|
: std::make_shared<Logging::NullLogger>();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::shared_ptr<HQUIC> configuration = nullptr;
|
||||||
|
std::shared_ptr<HQUIC> connectionHandle = nullptr;
|
||||||
|
std::vector<std::shared_ptr<HQUIC>> connectionStreams = { };
|
||||||
|
std::unordered_map<HQUIC, std::weak_ptr<MSQuicStream>> connectionStreamMap = { };
|
||||||
|
|
||||||
|
std::unordered_map<void*, std::shared_ptr<Payload>> queuedData = { };
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<std::weak_ptr<std::function<void(
|
||||||
|
std::shared_ptr<MSQuicStream>,
|
||||||
|
std::shared_ptr<MSQuicConnection>
|
||||||
|
)>>> onRemoteStreamCallbacks;
|
||||||
|
|
||||||
|
QUIC_STATUS connectionStatus = QUIC_STATUS_NOT_FOUND;
|
||||||
|
|
||||||
|
public:
|
||||||
|
const std::string remoteAddress;
|
||||||
|
const uint16_t remotePort;
|
||||||
|
|
||||||
|
|
||||||
|
void addRemoteStreamCallbacks(
|
||||||
|
const std::weak_ptr<std::function<void(
|
||||||
|
std::shared_ptr<MSQuicStream>,
|
||||||
|
std::shared_ptr<MSQuicConnection>
|
||||||
|
)>>& callback
|
||||||
|
) {
|
||||||
|
onRemoteStreamCallbacks.emplace_back(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
bool isConnected() const {
|
||||||
|
return connectionStatus == QUIC_STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
bool isTerminated() const {
|
||||||
|
return connectionStatus != QUIC_STATUS_PENDING && connectionStatus != QUIC_STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
bool isConnecting() const {
|
||||||
|
return connectionStatus == QUIC_STATUS_PENDING;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
[[nodiscard]] explicit
|
||||||
|
MSQuicConnection(
|
||||||
|
std::shared_ptr<HQUIC> configuration,
|
||||||
|
std::string remoteAddress,
|
||||||
|
const uint16_t remotePort
|
||||||
|
) :
|
||||||
|
configuration(std::move(configuration)), remoteAddress(std::move(remoteAddress)), remotePort(remotePort) { }
|
||||||
|
|
||||||
|
friend MSQuicServer;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static void defaultSettings(QUIC_SETTINGS& settings, QUIC_CREDENTIAL_CONFIG& credConfig) {
|
||||||
|
settings.IdleTimeoutMs = 5000;
|
||||||
|
settings.IsSet.IdleTimeoutMs = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
static std::expected<std::shared_ptr<MSQuicConnection>, MSQuicError> connectTo(
|
||||||
|
const QUIC_SETTINGS& settings,
|
||||||
|
const QUIC_CREDENTIAL_CONFIG& credConfig,
|
||||||
|
const std::string& remoteAddress,
|
||||||
|
const uint16_t remotePort
|
||||||
|
) {
|
||||||
|
auto MSQGR = MSQuicGlobal::get();
|
||||||
|
|
||||||
|
if (!MSQGR.has_value()) {
|
||||||
|
return std::unexpected(MSQGR.error());
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& MSQG = MSQGR.value().get();
|
||||||
|
const QUIC_API_TABLE* MsQuic = *MSQG.msQuicApiTable;
|
||||||
|
|
||||||
|
std::shared_ptr<HQUIC> configuration(
|
||||||
|
new HQUIC(nullptr), [](const HQUIC* configuration) {
|
||||||
|
auto msquic = MSQuicGlobal::get();
|
||||||
|
if (msquic.has_value() && *configuration != nullptr) {
|
||||||
|
(*msquic.value().get().msQuicApiTable)->ConfigurationClose(*configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete configuration;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (QUIC_STATUS status = MsQuic->ConfigurationOpen(
|
||||||
|
*MSQG.registration,
|
||||||
|
&MSQG.applicationLayerProtocolNegotiationName, 1,
|
||||||
|
&settings, sizeof(settings), nullptr,
|
||||||
|
configuration.get()
|
||||||
|
);
|
||||||
|
QUIC_FAILED(status)) {
|
||||||
|
return std::unexpected(MSQuicError(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (QUIC_STATUS status = MsQuic->ConfigurationLoadCredential(*configuration.get(), &credConfig);
|
||||||
|
QUIC_FAILED(status)) {
|
||||||
|
return std::unexpected(MSQuicError(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::shared_ptr<MSQuicConnection>(
|
||||||
|
new MSQuicConnection(configuration, remoteAddress, remotePort),
|
||||||
|
[](const MSQuicConnection* client) {
|
||||||
|
auto msquic = MSQuicGlobal::get();
|
||||||
|
if (msquic.has_value()) {
|
||||||
|
auto MSQG2 = msquic.value().get();
|
||||||
|
for (auto& stream : client->connectionStreams) {
|
||||||
|
(*MSQG2.msQuicApiTable)->StreamShutdown(
|
||||||
|
*stream,
|
||||||
|
QUIC_STREAM_SHUTDOWN_FLAG_ABORT | QUIC_STREAM_SHUTDOWN_FLAG_IMMEDIATE,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete client;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
static std::expected<std::shared_ptr<MSQuicConnection>, MSQuicError>
|
||||||
|
connectTo(const std::string& remoteAddress, const uint16_t remotePort) {
|
||||||
|
QUIC_SETTINGS settings{ 0 };
|
||||||
|
QUIC_CREDENTIAL_CONFIG credentialConfig{
|
||||||
|
.Type = QUIC_CREDENTIAL_TYPE_NONE,
|
||||||
|
.Flags = QUIC_CREDENTIAL_FLAG_CLIENT | QUIC_CREDENTIAL_FLAG_NO_CERTIFICATE_VALIDATION
|
||||||
|
};
|
||||||
|
defaultSettings(settings, credentialConfig);
|
||||||
|
return connectTo(settings, credentialConfig, remoteAddress, remotePort);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
std::optional<MSQuicError> establishConnection(const std::span<std::uint8_t>& resumptionTicket = { }) {
|
||||||
|
if (!isConnecting() && !isConnected()) {
|
||||||
|
connectionStatus = QUIC_STATUS_PENDING;
|
||||||
|
auto MSQGR = MSQuicGlobal::get();
|
||||||
|
|
||||||
|
if (!MSQGR.has_value()) {
|
||||||
|
connectionStatus = MSQGR.error().status;
|
||||||
|
return MSQGR.error();
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& MSQG = MSQGR.value().get();
|
||||||
|
|
||||||
|
connectionHandle.reset(
|
||||||
|
new HQUIC(nullptr), [](const HQUIC* connectionHandle) {
|
||||||
|
auto msquic = MSQuicGlobal::get();
|
||||||
|
if (msquic.has_value() && *connectionHandle != nullptr) {
|
||||||
|
(*msquic.value().get().msQuicApiTable)->ConnectionClose(*connectionHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete connectionHandle;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (QUIC_STATUS status = (*MSQG.msQuicApiTable)->ConnectionOpen(
|
||||||
|
*MSQG.registration, msquicClientConnectionCallbackProxy, this, connectionHandle.get()
|
||||||
|
);
|
||||||
|
QUIC_FAILED(status)) {
|
||||||
|
connectionStatus = status;
|
||||||
|
connectionHandle.reset();
|
||||||
|
return MSQuicError(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resumptionTicket.empty()) {
|
||||||
|
if (QUIC_STATUS status = (*MSQG.msQuicApiTable)->SetParam(
|
||||||
|
*connectionHandle, QUIC_PARAM_CONN_RESUMPTION_TICKET,
|
||||||
|
static_cast<uint32_t>(resumptionTicket.size()),
|
||||||
|
resumptionTicket.data()
|
||||||
|
);
|
||||||
|
QUIC_FAILED(status)) {
|
||||||
|
connectionStatus = status;
|
||||||
|
connectionHandle.reset();
|
||||||
|
return MSQuicError(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (QUIC_STATUS status = (*MSQG.msQuicApiTable)->ConnectionStart(
|
||||||
|
*connectionHandle, *configuration, QUIC_ADDRESS_FAMILY_UNSPEC,
|
||||||
|
remoteAddress.c_str(), remotePort
|
||||||
|
);
|
||||||
|
QUIC_FAILED(status)) {
|
||||||
|
connectionStatus = status;
|
||||||
|
connectionHandle.reset();
|
||||||
|
return MSQuicError(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
std::expected<std::shared_ptr<MSQuicStream>, MSQuicError> createStream(
|
||||||
|
QUIC_STREAM_OPEN_FLAGS openFlags = QUIC_STREAM_OPEN_FLAG_NONE,
|
||||||
|
QUIC_STREAM_START_FLAGS startFlags = QUIC_STREAM_START_FLAG_NONE
|
||||||
|
) {
|
||||||
|
auto MSQGR = MSQuicGlobal::get();
|
||||||
|
|
||||||
|
if (!MSQGR.has_value()) {
|
||||||
|
return std::unexpected(MSQGR.error());
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& MSQG = MSQGR.value().get();
|
||||||
|
|
||||||
|
std::shared_ptr<HQUIC> stream;
|
||||||
|
stream.reset(
|
||||||
|
new HQUIC(nullptr), [&](const HQUIC* connectionStream) {
|
||||||
|
auto msquic = MSQuicGlobal::get();
|
||||||
|
if (msquic.has_value() && *connectionStream != nullptr) {
|
||||||
|
(*msquic.value().get().msQuicApiTable)->StreamClose(*connectionStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete connectionStream;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (QUIC_STATUS status = (*MSQG.msQuicApiTable)->StreamOpen(
|
||||||
|
*connectionHandle, openFlags,
|
||||||
|
msquicClientStreamCallbackProxy, this,
|
||||||
|
stream.get()
|
||||||
|
);
|
||||||
|
QUIC_FAILED(status)) {
|
||||||
|
return std::unexpected(MSQuicError(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (QUIC_STATUS status = (*MSQG.msQuicApiTable)->StreamStart(*stream, startFlags);
|
||||||
|
QUIC_FAILED(status)) {
|
||||||
|
return std::unexpected(MSQuicError(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
connectionStreams.emplace_back(stream);
|
||||||
|
|
||||||
|
// We store these separately, since connectionStreams should be closed based on the relevant events but
|
||||||
|
// a close can also be requested by losing the shared_ptr preventing a memory/stream leak
|
||||||
|
std::shared_ptr<MSQuicStream> streamPtr(
|
||||||
|
new MSQuicStream(
|
||||||
|
stream,
|
||||||
|
std::shared_ptr<void>(
|
||||||
|
nullptr, [&, stream](void*) {
|
||||||
|
auto msquic = MSQuicGlobal::get();
|
||||||
|
if (msquic.has_value() && *stream != nullptr) {
|
||||||
|
(*msquic.value().get().msQuicApiTable)->StreamShutdown(
|
||||||
|
*stream, QUIC_STREAM_SHUTDOWN_FLAG_GRACEFUL, 0
|
||||||
|
);
|
||||||
|
} else { std::erase(connectionStreams, stream); }
|
||||||
|
}
|
||||||
|
),
|
||||||
|
shared_from_this(),
|
||||||
|
QUIC_STATUS_SUCCESS
|
||||||
|
), [](const MSQuicStream* ptr) {
|
||||||
|
delete ptr;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
connectionStreamMap.emplace(*stream, streamPtr);
|
||||||
|
|
||||||
|
return streamPtr;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<DataStorage T>
|
||||||
|
[[nodiscard]]
|
||||||
|
std::optional<MSQuicError> send(
|
||||||
|
T& data,
|
||||||
|
std::shared_ptr<MSQuicStream>& stream,
|
||||||
|
QUIC_SEND_FLAGS flags = QUIC_SEND_FLAG_NONE
|
||||||
|
) {
|
||||||
|
if (!isConnected() || (stream && !stream->isConnected())) {
|
||||||
|
return MSQuicError(QUIC_STATUS_INVALID_STATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto MSQGR = MSQuicGlobal::get();
|
||||||
|
|
||||||
|
if (!MSQGR.has_value()) {
|
||||||
|
return MSQGR.error();
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& MSQG = MSQGR.value().get();
|
||||||
|
|
||||||
|
if ((!stream || !stream->connectionStream) && !(flags & QUIC_SEND_FLAG_START)) {
|
||||||
|
return MSQuicError(QUIC_STATUS_INVALID_STATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<HQUIC> dataStream = nullptr;
|
||||||
|
if (stream && stream->connectionStream) {
|
||||||
|
dataStream = stream->connectionStream;
|
||||||
|
} else if (flags & QUIC_SEND_FLAG_FIN) {
|
||||||
|
dataStream.reset(
|
||||||
|
new HQUIC(nullptr), [](const HQUIC* connectionStream) {
|
||||||
|
auto msquic = MSQuicGlobal::get();
|
||||||
|
if (msquic.has_value() && *connectionStream != nullptr) {
|
||||||
|
(*msquic.value().get().msQuicApiTable)->StreamClose(*connectionStream);
|
||||||
|
}
|
||||||
|
delete connectionStream;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (QUIC_STATUS status = (*MSQG.msQuicApiTable)->StreamOpen(
|
||||||
|
*connectionHandle, QUIC_STREAM_OPEN_FLAG_NONE, //QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL,
|
||||||
|
msquicClientStreamCallbackProxy, this,
|
||||||
|
dataStream.get()
|
||||||
|
);
|
||||||
|
QUIC_FAILED(status)) {
|
||||||
|
return MSQuicError(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
connectionStreams.emplace_back(dataStream);
|
||||||
|
} else if (auto strim = createStream(); strim.has_value()) {
|
||||||
|
stream = strim.value();
|
||||||
|
dataStream = stream->connectionStream;
|
||||||
|
} else {
|
||||||
|
return strim.error();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dataStream) {
|
||||||
|
return MSQuicError(QUIC_STATUS_INVALID_STATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::shared_ptr<Payload> sendData = std::make_shared<Payload>(data);
|
||||||
|
|
||||||
|
queuedData.emplace(sendData.get(), sendData);
|
||||||
|
|
||||||
|
// QUIC_BUFFER* buffer = new QUIC_BUFFER{
|
||||||
|
// static_cast<uint32_t>(std::size(data)), reinterpret_cast<uint8_t*>(std::data(data))
|
||||||
|
// };
|
||||||
|
if (QUIC_STATUS status = (*MSQG.msQuicApiTable)->StreamSend(
|
||||||
|
*dataStream.get(), sendData.get(), 1, flags, sendData.get()
|
||||||
|
);
|
||||||
|
QUIC_FAILED(status)) {
|
||||||
|
(*MSQG.msQuicApiTable)->StreamShutdown(*dataStream.get(), QUIC_STREAM_SHUTDOWN_FLAG_ABORT, 1);
|
||||||
|
return MSQuicError(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<DataStorage T>
|
||||||
|
[[nodiscard]]
|
||||||
|
constexpr std::optional<MSQuicError> send(
|
||||||
|
T& data,
|
||||||
|
QUIC_SEND_FLAGS flags = QUIC_SEND_FLAG_START | QUIC_SEND_FLAG_FIN
|
||||||
|
) {
|
||||||
|
std::shared_ptr<MSQuicStream> stream;
|
||||||
|
return send(data, stream, flags | QUIC_SEND_FLAG_START | QUIC_SEND_FLAG_FIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
std::optional<MSQuicError> shutdown() const {
|
||||||
|
if (!isConnected()) {
|
||||||
|
return MSQuicError(QUIC_STATUS_INVALID_STATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto MSQGR = MSQuicGlobal::get();
|
||||||
|
|
||||||
|
if (!MSQGR.has_value()) {
|
||||||
|
return MSQGR.error();
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& MSQG = MSQGR.value().get();
|
||||||
|
|
||||||
|
(*MSQG.msQuicApiTable)->ConnectionShutdown(
|
||||||
|
*connectionHandle, QUIC_CONNECTION_SHUTDOWN_FLAG_NONE, 0
|
||||||
|
);
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend QUIC_STATUS msquicClientConnectionCallbackProxy(
|
||||||
|
HQUIC connection,
|
||||||
|
void* context,
|
||||||
|
QUIC_CONNECTION_EVENT* event
|
||||||
|
);
|
||||||
|
friend QUIC_STATUS msquicClientStreamCallbackProxy(
|
||||||
|
HQUIC stream,
|
||||||
|
void* context,
|
||||||
|
QUIC_STREAM_EVENT* event
|
||||||
|
);
|
||||||
|
friend QUIC_STATUS msquicServerConnectionCallbackProxy(
|
||||||
|
HQUIC connection,
|
||||||
|
void* context,
|
||||||
|
QUIC_CONNECTION_EVENT* event
|
||||||
|
);
|
||||||
|
friend QUIC_STATUS msquicServerStreamCallbackProxy(HQUIC connection, void* context, QUIC_STREAM_EVENT* event);
|
||||||
|
|
||||||
|
QUIC_STATUS connectionCallback(HQUIC connection, const QUIC_CONNECTION_EVENT* event) {
|
||||||
|
auto clientId = reinterpret_cast<std::uintptr_t>(connection);
|
||||||
|
switch (event->Type) {
|
||||||
|
case QUIC_CONNECTION_EVENT_CONNECTED:
|
||||||
|
logger->log(
|
||||||
|
Logging::LEVEL_DEBUG,
|
||||||
|
"[client][{:X}] Connected", clientId
|
||||||
|
);
|
||||||
|
|
||||||
|
connectionStatus = QUIC_STATUS_SUCCESS;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case QUIC_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_TRANSPORT:
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The transport has shut down the connection.
|
||||||
|
* Generally, this is the expected way for the connection to shut down with this
|
||||||
|
* protocol, since we let idle timeout kill the connection.
|
||||||
|
*/
|
||||||
|
if (event->SHUTDOWN_INITIATED_BY_TRANSPORT.Status == QUIC_STATUS_CONNECTION_IDLE) {
|
||||||
|
logger->log(
|
||||||
|
Logging::LEVEL_DEBUG, "[client][{:X}] Successfully shutdown on idle", clientId
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
logger->log(
|
||||||
|
Logging::LEVEL_DEBUG,
|
||||||
|
"[client][{:X}] Shut down by transport, 0x{:X}", clientId,
|
||||||
|
static_cast<std::uint64_t>(event->SHUTDOWN_INITIATED_BY_TRANSPORT.Status)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case QUIC_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_PEER:
|
||||||
|
logger->log(
|
||||||
|
Logging::LEVEL_DEBUG,
|
||||||
|
"[client][{:X}] Shut down by peer 0x{:X}", clientId,
|
||||||
|
event->SHUTDOWN_INITIATED_BY_PEER.ErrorCode
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE:
|
||||||
|
logger->log(
|
||||||
|
Logging::LEVEL_DEBUG, "[client][{:X}] Connection shutdown complete", clientId
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
connectionStatus = QUIC_STATUS_NOT_FOUND;
|
||||||
|
if (!event->SHUTDOWN_COMPLETE.AppCloseInProgress) {
|
||||||
|
logger->log(
|
||||||
|
Logging::LEVEL_DEBUG, "[client][{:X}] Connection closed", clientId
|
||||||
|
);
|
||||||
|
connectionHandle.reset();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case QUIC_CONNECTION_EVENT_RESUMPTION_TICKET_RECEIVED:
|
||||||
|
logger->log(
|
||||||
|
Logging::LEVEL_DEBUG,
|
||||||
|
"[client][{:X}] Resumption ticket received ({} bytes):\n{}", clientId,
|
||||||
|
event->RESUMPTION_TICKET_RECEIVED.ResumptionTicketLength, std::ranges::to<std::string>(
|
||||||
|
std::views::join_with(
|
||||||
|
std::ranges::transform_view(
|
||||||
|
std::ranges::subrange(
|
||||||
|
event->RESUMPTION_TICKET_RECEIVED.ResumptionTicket,
|
||||||
|
event->RESUMPTION_TICKET_RECEIVED.ResumptionTicket + event->
|
||||||
|
RESUMPTION_TICKET_RECEIVED.ResumptionTicketLength
|
||||||
|
),
|
||||||
|
[](std::uint8_t byte) {
|
||||||
|
return std::format("{:0>2X}", byte);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
std::string("")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
logger->log(
|
||||||
|
Logging::LEVEL_DEBUG, "[client][{:X}] Connection event [{}]", clientId, std::to_underlying(event->Type));
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return QUIC_STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cleanupStreams() const {
|
||||||
|
auto MSQGR = MSQuicGlobal::get();
|
||||||
|
if (MSQGR.has_value()) {
|
||||||
|
auto MSQG = MSQGR.value().get();
|
||||||
|
for (auto& stream : connectionStreams) {
|
||||||
|
if (*stream != nullptr) {
|
||||||
|
(*MSQG.msQuicApiTable)->StreamShutdown(*stream, QUIC_STREAM_SHUTDOWN_FLAG_GRACEFUL, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QUIC_STATUS commonStreamCallback(HQUIC stream, const QUIC_STREAM_EVENT* event, const bool isClient) {
|
||||||
|
auto streamId = reinterpret_cast<std::uintptr_t>(stream);
|
||||||
|
auto side = isClient ? "client" : "server";
|
||||||
|
switch (event->Type) {
|
||||||
|
case QUIC_STREAM_EVENT_SEND_COMPLETE:
|
||||||
|
{
|
||||||
|
//
|
||||||
|
// A previous StreamSend call has completed, and the context is being
|
||||||
|
// returned to the app.
|
||||||
|
//
|
||||||
|
auto dataId = reinterpret_cast<std::uintptr_t>(event->SEND_COMPLETE.ClientContext);
|
||||||
|
logger->log(
|
||||||
|
Logging::LEVEL_DEBUG,
|
||||||
|
"[{}][stream][{:X}] Data sent {} bytes [{:X} {}]", side, streamId,
|
||||||
|
static_cast<Payload*>(event->SEND_COMPLETE.ClientContext)->Length,
|
||||||
|
dataId,
|
||||||
|
queuedData.contains(event->SEND_COMPLETE.ClientContext)
|
||||||
|
? "CACHE HIT"
|
||||||
|
: "CACHE MISS"
|
||||||
|
);
|
||||||
|
std::flush(std::cout);
|
||||||
|
|
||||||
|
queuedData.erase(event->SEND_COMPLETE.ClientContext);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case QUIC_STREAM_EVENT_RECEIVE:
|
||||||
|
//
|
||||||
|
// Data was received from the peer on the stream.
|
||||||
|
//
|
||||||
|
logger->log(
|
||||||
|
Logging::LEVEL_DEBUG,
|
||||||
|
"[{}][stream][{:X}] Data received ({} bytes)", side, streamId,
|
||||||
|
event->RECEIVE.TotalBufferLength
|
||||||
|
);
|
||||||
|
std::flush(std::cout);
|
||||||
|
|
||||||
|
if (connectionStreamMap.contains(stream)) {
|
||||||
|
if (const auto ds = connectionStreamMap.at(stream).lock(); ds) {
|
||||||
|
for (uint32_t i = 0; i < event->RECEIVE.BufferCount; i++) {
|
||||||
|
const auto& [Length, Buffer] = event->RECEIVE.Buffers[i];
|
||||||
|
ds->incomingData.insert(
|
||||||
|
std::end(ds->incomingData),
|
||||||
|
Buffer, Buffer + Length
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
connectionStreamMap.erase(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case QUIC_STREAM_EVENT_PEER_SEND_ABORTED:
|
||||||
|
{
|
||||||
|
logger->log(
|
||||||
|
Logging::LEVEL_DEBUG,
|
||||||
|
"[{}][stream][{:X}] Peer aborted reason: {}", side, streamId,
|
||||||
|
event->PEER_SEND_ABORTED.ErrorCode
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
auto MSQGR = MSQuicGlobal::get();
|
||||||
|
|
||||||
|
if (!MSQGR.has_value()) {
|
||||||
|
connectionStatus = MSQGR.error().status;
|
||||||
|
return MSQGR.error().status;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& MSQG = MSQGR.value().get();
|
||||||
|
(*MSQG.msQuicApiTable)->StreamShutdown(stream, QUIC_STREAM_SHUTDOWN_FLAG_ABORT, 0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case QUIC_STREAM_EVENT_PEER_SEND_SHUTDOWN:
|
||||||
|
{
|
||||||
|
logger->log(
|
||||||
|
Logging::LEVEL_DEBUG, "[{}][stream][{:X}] Peer shut down", side, streamId);
|
||||||
|
|
||||||
|
auto MSQGR = MSQuicGlobal::get();
|
||||||
|
|
||||||
|
if (!MSQGR.has_value()) {
|
||||||
|
connectionStatus = MSQGR.error().status;
|
||||||
|
return MSQGR.error().status;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& MSQG = MSQGR.value().get();
|
||||||
|
(*MSQG.msQuicApiTable)->StreamShutdown(stream, QUIC_STREAM_SHUTDOWN_FLAG_GRACEFUL, 0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case QUIC_STREAM_EVENT_SHUTDOWN_COMPLETE:
|
||||||
|
logger->log(
|
||||||
|
Logging::LEVEL_DEBUG, "[{}][stream][{:X}] Shutdown done", side, streamId);
|
||||||
|
|
||||||
|
if (connectionStreamMap.contains(stream)) {
|
||||||
|
if (auto ds = connectionStreamMap.at(stream).lock(); ds) {
|
||||||
|
ds->status = QUIC_STATUS_ABORTED;
|
||||||
|
}
|
||||||
|
connectionStreamMap.erase(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::erase_if(
|
||||||
|
connectionStreams,
|
||||||
|
[&](const std::shared_ptr<HQUIC>& strm) {
|
||||||
|
return *strm.get() == stream;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return QUIC_STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
QUIC_STATUS streamCallback(HQUIC stream, const QUIC_STREAM_EVENT* event) {
|
||||||
|
return commonStreamCallback(stream, event, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
QUIC_STATUS serverConnectionCallback(HQUIC connection, const QUIC_CONNECTION_EVENT* event) {
|
||||||
|
auto connectionId = reinterpret_cast<std::uintptr_t>(connection);
|
||||||
|
switch (event->Type) {
|
||||||
|
case QUIC_CONNECTION_EVENT_CONNECTED:
|
||||||
|
{
|
||||||
|
logger->log(
|
||||||
|
Logging::LEVEL_DEBUG, "[server][{:X}] Connected", connectionId);
|
||||||
|
|
||||||
|
auto MSQGR = MSQuicGlobal::get();
|
||||||
|
|
||||||
|
if (!MSQGR.has_value()) {
|
||||||
|
connectionStatus = MSQGR.error().status;
|
||||||
|
return MSQGR.error().status;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& MSQG = MSQGR.value().get();
|
||||||
|
|
||||||
|
(*MSQG.msQuicApiTable)->ConnectionSendResumptionTicket(
|
||||||
|
connection, QUIC_SEND_RESUMPTION_FLAG_NONE, 0,
|
||||||
|
nullptr
|
||||||
|
);
|
||||||
|
|
||||||
|
connectionStatus = QUIC_STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case QUIC_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_TRANSPORT:
|
||||||
|
if (event->SHUTDOWN_INITIATED_BY_TRANSPORT.Status == QUIC_STATUS_CONNECTION_IDLE) {
|
||||||
|
logger->log(
|
||||||
|
Logging::LEVEL_DEBUG, "[server][{:X}] Successfully shutdown on idle", connectionId
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
logger->log(
|
||||||
|
Logging::LEVEL_DEBUG,
|
||||||
|
"[server][{:X}] Shut down by transport, 0x{:X}", connectionId,
|
||||||
|
static_cast<uint64_t>(event->SHUTDOWN_INITIATED_BY_TRANSPORT.Status)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case QUIC_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_PEER:
|
||||||
|
logger->log(
|
||||||
|
Logging::LEVEL_DEBUG,
|
||||||
|
"[server][{:X}] Shut down by peer 0x{:X}", connectionId,
|
||||||
|
event->SHUTDOWN_INITIATED_BY_PEER.ErrorCode
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE:
|
||||||
|
logger->log(
|
||||||
|
Logging::LEVEL_DEBUG,"[server][{:X}] Shutdown complete", connectionId);
|
||||||
|
|
||||||
|
|
||||||
|
connectionStatus = QUIC_STATUS_NOT_FOUND;
|
||||||
|
connectionHandle.reset();
|
||||||
|
break;
|
||||||
|
case QUIC_CONNECTION_EVENT_PEER_STREAM_STARTED:
|
||||||
|
{
|
||||||
|
logger->log(
|
||||||
|
Logging::LEVEL_DEBUG,
|
||||||
|
"[server][{:X}] Peer stream started ({:X})", connectionId,
|
||||||
|
reinterpret_cast<std::uintptr_t>(event->PEER_STREAM_STARTED.Stream)
|
||||||
|
);
|
||||||
|
|
||||||
|
auto MSQGR = MSQuicGlobal::get();
|
||||||
|
|
||||||
|
if (!MSQGR.has_value()) {
|
||||||
|
connectionStatus = MSQGR.error().status;
|
||||||
|
return MSQGR.error().status;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& MSQG = MSQGR.value().get();
|
||||||
|
|
||||||
|
std::shared_ptr<HQUIC> newStream(
|
||||||
|
new HQUIC(event->PEER_STREAM_STARTED.Stream),
|
||||||
|
[&](const HQUIC* connectionStream) {
|
||||||
|
auto msquic = MSQuicGlobal::get();
|
||||||
|
if (msquic.has_value() && *connectionStream != nullptr) {
|
||||||
|
auto table = *MSQG.msQuicApiTable;
|
||||||
|
table->SetCallbackHandler(
|
||||||
|
*connectionStream, nullptr, this
|
||||||
|
);
|
||||||
|
table->StreamClose(*connectionStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete connectionStream;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
connectionStreams.emplace_back(newStream);
|
||||||
|
|
||||||
|
if (!onRemoteStreamCallbacks.empty()) {
|
||||||
|
std::shared_ptr<MSQuicStream> client(
|
||||||
|
new MSQuicStream(
|
||||||
|
newStream, std::shared_ptr<void>(
|
||||||
|
nullptr, [&, newStream](void*) {
|
||||||
|
auto msquic = MSQuicGlobal::get();
|
||||||
|
if (msquic.has_value() && *newStream != nullptr) {
|
||||||
|
(*msquic.value().get().msQuicApiTable)->StreamShutdown(
|
||||||
|
*newStream, QUIC_STREAM_SHUTDOWN_FLAG_GRACEFUL, 0
|
||||||
|
);
|
||||||
|
} else { std::erase(connectionStreams, newStream); }
|
||||||
|
}
|
||||||
|
), shared_from_this(), QUIC_STATUS_SUCCESS
|
||||||
|
), [](const MSQuicStream* ptr) {
|
||||||
|
delete ptr;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
connectionStreamMap.emplace(*newStream, client);
|
||||||
|
for (auto& callback : onRemoteStreamCallbacks) {
|
||||||
|
if (const auto cb = callback.lock(); cb) {
|
||||||
|
cb->operator()(client, shared_from_this());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(*MSQG.msQuicApiTable)->SetCallbackHandler(
|
||||||
|
event->PEER_STREAM_STARTED.Stream,
|
||||||
|
msquicServerStreamCallbackProxy, this
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case QUIC_CONNECTION_EVENT_RESUMED:
|
||||||
|
logger->log(
|
||||||
|
Logging::LEVEL_DEBUG, "[server][{:X}] Connection resumed!", connectionId
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
logger->log(
|
||||||
|
Logging::LEVEL_DEBUG, "[server][{:X}] Connection event [{}]", connectionId, std::to_underlying(event->Type));
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return QUIC_STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
QUIC_STATUS serverStreamCallback(HQUIC stream, const QUIC_STREAM_EVENT* event) {
|
||||||
|
return commonStreamCallback(stream, event, false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
QUIC_STATUS msquicClientConnectionCallbackProxy(HQUIC connection, void* context, QUIC_CONNECTION_EVENT* event) {
|
||||||
|
return static_cast<MSQuicConnection*>(context)->connectionCallback(connection, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
QUIC_STATUS msquicClientStreamCallbackProxy(HQUIC stream, void* context, QUIC_STREAM_EVENT* event) {
|
||||||
|
return static_cast<MSQuicConnection*>(context)->streamCallback(stream, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
QUIC_STATUS msquicServerConnectionCallbackProxy(HQUIC connection, void* context, QUIC_CONNECTION_EVENT* event) {
|
||||||
|
return static_cast<MSQuicConnection*>(context)->serverConnectionCallback(connection, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
QUIC_STATUS msquicServerStreamCallbackProxy(HQUIC connection, void* context, QUIC_STREAM_EVENT* event) {
|
||||||
|
return static_cast<MSQuicConnection*>(context)->serverStreamCallback(connection, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
17
common/connection/src/MSQuicError.cppm
Normal file
17
common/connection/src/MSQuicError.cppm
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
export module MMO.DataConnection:MSQuicError;
|
||||||
|
|
||||||
|
import <msquic.h>;
|
||||||
|
import std;
|
||||||
|
namespace MMO::Networking::MSQUIC {
|
||||||
|
|
||||||
|
export class MSQuicError : public std::runtime_error {
|
||||||
|
public:
|
||||||
|
QUIC_STATUS status = QUIC_STATUS_SUCCESS;
|
||||||
|
|
||||||
|
[[nodiscard]] explicit
|
||||||
|
MSQuicError(QUIC_STATUS status) : status(status),
|
||||||
|
std::runtime_error(
|
||||||
|
std::format("MSQuic error: 0x{:X}", static_cast<uint64_t>(status))
|
||||||
|
) { }
|
||||||
|
};
|
||||||
|
}
|
62
common/connection/src/MSQuicGlobal.cppm
Normal file
62
common/connection/src/MSQuicGlobal.cppm
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
export module MMO.DataConnection:MSQuicGlobal;
|
||||||
|
|
||||||
|
import <msquic.h>;
|
||||||
|
import std;
|
||||||
|
import :MSQuicError;
|
||||||
|
|
||||||
|
namespace MMO::Networking::MSQUIC {
|
||||||
|
export class MSQuicGlobal {
|
||||||
|
public:
|
||||||
|
std::shared_ptr<const QUIC_API_TABLE*> msQuicApiTable = nullptr;
|
||||||
|
std::shared_ptr<HQUIC> registration;
|
||||||
|
|
||||||
|
const QUIC_BUFFER applicationLayerProtocolNegotiationName = { sizeof("sample") - 1, (uint8_t*) "sample" };
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
std::println("Cleaning up MSQuic...");
|
||||||
|
|
||||||
|
|
||||||
|
registration.reset();
|
||||||
|
msQuicApiTable.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
static std::expected<std::reference_wrapper<MSQuicGlobal>, MSQuicError> get() {
|
||||||
|
static MSQuicGlobal instance;
|
||||||
|
if (!instance.msQuicApiTable) {
|
||||||
|
instance.msQuicApiTable = std::shared_ptr<const QUIC_API_TABLE*>(
|
||||||
|
new const QUIC_API_TABLE*(nullptr), [](const QUIC_API_TABLE** table) {
|
||||||
|
std::println("Cleaning up MSQuic...");
|
||||||
|
if (*table != nullptr) {
|
||||||
|
MsQuicClose(*table);
|
||||||
|
}
|
||||||
|
delete table;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (QUIC_STATUS status = MsQuicOpen2(instance.msQuicApiTable.get()); QUIC_FAILED(status)) {
|
||||||
|
return std::unexpected(MSQuicError(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
instance.registration.reset(
|
||||||
|
new HQUIC(nullptr),
|
||||||
|
[apiTable = instance.msQuicApiTable](const HQUIC* registration) {
|
||||||
|
if (*registration != nullptr) {
|
||||||
|
(*apiTable.get())->RegistrationClose(*registration);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete registration;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (QUIC_STATUS status = (*instance.msQuicApiTable.get())->RegistrationOpen(
|
||||||
|
nullptr, instance.registration.get()
|
||||||
|
);
|
||||||
|
QUIC_FAILED(status)) {
|
||||||
|
return std::unexpected(MSQuicError(status));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
316
common/connection/src/MSQuicServer.cppm
Normal file
316
common/connection/src/MSQuicServer.cppm
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
export module MMO.DataConnection:MSQuicServer;
|
||||||
|
|
||||||
|
import <msquic.h>;
|
||||||
|
import std;
|
||||||
|
import :MSQuicConnection;
|
||||||
|
import :MSQuicGlobal;
|
||||||
|
import :MSQuicError;
|
||||||
|
import MMO.Logging;
|
||||||
|
|
||||||
|
namespace MMO::Networking::MSQUIC {
|
||||||
|
export class MSQuicServer : public std::enable_shared_from_this<MSQuicServer> {
|
||||||
|
public:
|
||||||
|
std::shared_ptr<Logging::Logger> logger = MMO::Logging::DEFAULT_LOGGER
|
||||||
|
? MMO::Logging::DEFAULT_LOGGER
|
||||||
|
: std::make_shared<Logging::NullLogger>();
|
||||||
|
std::shared_ptr<HQUIC> configuration = nullptr;
|
||||||
|
std::shared_ptr<HQUIC> listenerHandle = nullptr;
|
||||||
|
const std::string hostAddress;
|
||||||
|
const uint16_t hostPort;
|
||||||
|
|
||||||
|
std::vector<std::weak_ptr<std::function<void(std::shared_ptr<MSQuicConnection>)>>> onConnectionCallbacks;
|
||||||
|
|
||||||
|
bool starting = false;
|
||||||
|
bool listening = false;
|
||||||
|
bool stopping = false;
|
||||||
|
|
||||||
|
public:
|
||||||
|
[[nodiscard]]
|
||||||
|
bool isStarting() const {
|
||||||
|
return starting;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
bool isListening() const {
|
||||||
|
return listening;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
bool isStopping() const {
|
||||||
|
return stopping;
|
||||||
|
}
|
||||||
|
|
||||||
|
private :
|
||||||
|
MSQuicServer(std::shared_ptr<HQUIC> configuration, std::string hostAddress, const uint16_t hostPort) :
|
||||||
|
configuration(std::move(configuration)), hostAddress(std::move(hostAddress)), hostPort(hostPort) { }
|
||||||
|
|
||||||
|
public:
|
||||||
|
static void defaultSettings(QUIC_SETTINGS& settings, QUIC_CREDENTIAL_CONFIG& credConfig) {
|
||||||
|
settings.IdleTimeoutMs = 5000;
|
||||||
|
settings.IsSet.IdleTimeoutMs = TRUE;
|
||||||
|
|
||||||
|
settings.ServerResumptionLevel = QUIC_SERVER_RESUME_AND_ZERORTT;
|
||||||
|
settings.IsSet.ServerResumptionLevel = TRUE;
|
||||||
|
|
||||||
|
settings.PeerBidiStreamCount = 1;
|
||||||
|
settings.IsSet.PeerBidiStreamCount = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
static std::expected<std::shared_ptr<MSQuicServer>, MSQuicError> hostFrom(
|
||||||
|
QUIC_SETTINGS& settings,
|
||||||
|
QUIC_CREDENTIAL_CONFIG& credConfig,
|
||||||
|
const uint16_t hostPort,
|
||||||
|
const std::string& hostAddress = "0.0.0.0"
|
||||||
|
) {
|
||||||
|
auto MSQGR = MSQuicGlobal::get();
|
||||||
|
|
||||||
|
if (!MSQGR.has_value()) {
|
||||||
|
return std::unexpected(MSQGR.error());
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& MSQG = MSQGR.value().get();
|
||||||
|
const QUIC_API_TABLE* MsQuic = *MSQG.msQuicApiTable;
|
||||||
|
|
||||||
|
std::shared_ptr<HQUIC> configuration(
|
||||||
|
new HQUIC(nullptr), [](const HQUIC* configuration) {
|
||||||
|
auto msquic = MSQuicGlobal::get();
|
||||||
|
if (msquic.has_value() && *configuration != nullptr) {
|
||||||
|
(*msquic.value().get().msQuicApiTable)->ConfigurationClose(*configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete configuration;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (QUIC_STATUS status = MsQuic->ConfigurationOpen(
|
||||||
|
*MSQG.registration,
|
||||||
|
&MSQG.applicationLayerProtocolNegotiationName, 1,
|
||||||
|
&settings, sizeof(settings), nullptr,
|
||||||
|
configuration.get()
|
||||||
|
);
|
||||||
|
QUIC_FAILED(status)) {
|
||||||
|
return std::unexpected(MSQuicError(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (QUIC_STATUS status = MsQuic->ConfigurationLoadCredential(*configuration.get(), &credConfig);
|
||||||
|
QUIC_FAILED(status)) {
|
||||||
|
return std::unexpected(MSQuicError(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::shared_ptr<MSQuicServer>(
|
||||||
|
new MSQuicServer(configuration, hostAddress, hostPort), [](const auto* v) {
|
||||||
|
delete v;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
static std::expected<std::shared_ptr<MSQuicServer>, MSQuicError> hostFrom(
|
||||||
|
QUIC_CREDENTIAL_CONFIG& credentialConfig,
|
||||||
|
const uint16_t hostPort,
|
||||||
|
const std::string& hostAddress = "0.0.0.0"
|
||||||
|
) {
|
||||||
|
QUIC_SETTINGS settings{ 0 };
|
||||||
|
defaultSettings(settings, credentialConfig);
|
||||||
|
return hostFrom(settings, credentialConfig, hostPort, hostAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
std::optional<MSQuicError> startListening() {
|
||||||
|
if (!listening && !starting) {
|
||||||
|
starting = true;
|
||||||
|
|
||||||
|
auto MSQGR = MSQuicGlobal::get();
|
||||||
|
|
||||||
|
if (!MSQGR.has_value()) {
|
||||||
|
starting = false;
|
||||||
|
return MSQGR.error();
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& MSQG = MSQGR.value().get();
|
||||||
|
|
||||||
|
|
||||||
|
listenerHandle.reset(
|
||||||
|
new HQUIC(nullptr), [](const HQUIC* listenerHandle) {
|
||||||
|
auto msquic = MSQuicGlobal::get();
|
||||||
|
if (msquic.has_value() && *listenerHandle != nullptr) {
|
||||||
|
(*msquic.value().get().msQuicApiTable)->ListenerClose(*listenerHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete listenerHandle;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (const QUIC_STATUS status = (*MSQG.msQuicApiTable)->ListenerOpen(
|
||||||
|
*MSQG.registration,
|
||||||
|
msquicServerListeningCallbackProxy, this,
|
||||||
|
listenerHandle.get()
|
||||||
|
);
|
||||||
|
QUIC_FAILED(status)) {
|
||||||
|
starting = false;
|
||||||
|
return MSQuicError(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
QUIC_ADDR address = { 0 };
|
||||||
|
QuicAddrSetFamily(&address, QUIC_ADDRESS_FAMILY_UNSPEC);
|
||||||
|
QuicAddrFromString(hostAddress.c_str(), hostPort, &address);
|
||||||
|
|
||||||
|
if (const QUIC_STATUS status = (*MSQG.msQuicApiTable)->ListenerStart(
|
||||||
|
*listenerHandle.get(), &MSQG.applicationLayerProtocolNegotiationName, 1, &address
|
||||||
|
);
|
||||||
|
QUIC_FAILED(status)) {
|
||||||
|
starting = false;
|
||||||
|
return MSQuicError(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
listening = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
std::optional<MSQuicError> stopListening() {
|
||||||
|
if (listening && !stopping) {
|
||||||
|
stopping = true;
|
||||||
|
|
||||||
|
auto MSQGR = MSQuicGlobal::get();
|
||||||
|
|
||||||
|
if (!MSQGR.has_value()) {
|
||||||
|
return MSQGR.error();
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& MSQG = MSQGR.value().get();
|
||||||
|
|
||||||
|
(*MSQG.msQuicApiTable)->ListenerStop(*listenerHandle.get());
|
||||||
|
|
||||||
|
listenerHandle.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void addReceiveCallback(const std::weak_ptr<std::function<void(std::shared_ptr<MSQuicConnection>)>>& callback) {
|
||||||
|
onConnectionCallbacks.emplace_back(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend QUIC_STATUS msquicServerListeningCallbackProxy(
|
||||||
|
HQUIC connection,
|
||||||
|
void* context,
|
||||||
|
QUIC_LISTENER_EVENT* event
|
||||||
|
);
|
||||||
|
friend QUIC_STATUS msquicServerConnectionCallbackProxy(
|
||||||
|
HQUIC connection,
|
||||||
|
void* context,
|
||||||
|
QUIC_CONNECTION_EVENT* event
|
||||||
|
);
|
||||||
|
friend QUIC_STATUS msquicServerStreamCallbackProxy(HQUIC connection, void* context, QUIC_STREAM_EVENT* event);
|
||||||
|
|
||||||
|
QUIC_STATUS serverListenerCallback(HQUIC connection, QUIC_LISTENER_EVENT* event) {
|
||||||
|
auto connectionId = reinterpret_cast<std::uintptr_t>(connection);
|
||||||
|
|
||||||
|
QUIC_STATUS Status = QUIC_STATUS_NOT_SUPPORTED;
|
||||||
|
switch (event->Type) {
|
||||||
|
case QUIC_LISTENER_EVENT_NEW_CONNECTION:
|
||||||
|
{
|
||||||
|
QUIC_ADDR_STR address = { 0 };
|
||||||
|
QuicAddrToString(event->NEW_CONNECTION.Info->RemoteAddress, &address);
|
||||||
|
|
||||||
|
const std::string_view remoteAddress = address.Address;
|
||||||
|
const auto indexSplit = remoteAddress.find_last_of(':');
|
||||||
|
auto remoteAddressPort = static_cast<uint16_t>(std::strtol(
|
||||||
|
remoteAddress.substr(indexSplit + 1).data(), nullptr, 10
|
||||||
|
));
|
||||||
|
std::string remoteAddressIp(remoteAddress.substr(0, indexSplit));
|
||||||
|
|
||||||
|
logger->log(
|
||||||
|
Logging::LEVEL_DEBUG,
|
||||||
|
"[server][{:X}][{}] New connection from {}:{}", connectionId,
|
||||||
|
reinterpret_cast<std::uintptr_t>(event->NEW_CONNECTION.Connection),
|
||||||
|
remoteAddressIp, remoteAddressPort
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
auto MSQGR = MSQuicGlobal::get();
|
||||||
|
|
||||||
|
if (!MSQGR.has_value()) {
|
||||||
|
listening = false;
|
||||||
|
return MSQGR.error().status;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& MSQG = MSQGR.value().get();
|
||||||
|
|
||||||
|
const std::shared_ptr<MSQuicConnection> client(
|
||||||
|
new MSQuicConnection{
|
||||||
|
configuration,
|
||||||
|
remoteAddressIp,
|
||||||
|
remoteAddressPort
|
||||||
|
}, [](MSQuicConnection* c) {
|
||||||
|
auto msquic = MSQuicGlobal::get();
|
||||||
|
if (msquic.has_value()) {
|
||||||
|
auto MSQG2 = msquic->get();
|
||||||
|
for (auto& stream : c->connectionStreams) {
|
||||||
|
(*MSQG2.msQuicApiTable)->StreamShutdown(
|
||||||
|
*stream,
|
||||||
|
QUIC_STREAM_SHUTDOWN_FLAG_ABORT | QUIC_STREAM_SHUTDOWN_FLAG_IMMEDIATE, 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (c->connectionHandle) {
|
||||||
|
const auto handle = *c->connectionHandle.get();
|
||||||
|
c->connectionHandle.reset();
|
||||||
|
(*MSQG2.msQuicApiTable)->SetCallbackHandler(handle, nullptr, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete c;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
client->connectionHandle.reset(
|
||||||
|
new HQUIC(event->NEW_CONNECTION.Connection),
|
||||||
|
[](const HQUIC* connectionHandle) {
|
||||||
|
auto msquic = MSQuicGlobal::get();
|
||||||
|
if (msquic.has_value() && *connectionHandle != nullptr) {
|
||||||
|
(*msquic.value().get().msQuicApiTable)->ConnectionClose(
|
||||||
|
*connectionHandle
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete connectionHandle;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
(*MSQG.msQuicApiTable)->SetCallbackHandler(
|
||||||
|
event->NEW_CONNECTION.Connection,
|
||||||
|
msquicServerConnectionCallbackProxy, client.get()
|
||||||
|
);
|
||||||
|
Status = (*MSQG.msQuicApiTable)->ConnectionSetConfiguration(
|
||||||
|
event->NEW_CONNECTION.Connection, *configuration.get()
|
||||||
|
);
|
||||||
|
|
||||||
|
for (auto& callback : onConnectionCallbacks) {
|
||||||
|
if (const auto cb = callback.lock(); cb) {
|
||||||
|
cb->operator()(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case QUIC_LISTENER_EVENT_STOP_COMPLETE:
|
||||||
|
{
|
||||||
|
listenerHandle.reset();
|
||||||
|
listening = false;
|
||||||
|
stopping = false;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return Status;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
QUIC_STATUS msquicServerListeningCallbackProxy(HQUIC connection, void* context, QUIC_LISTENER_EVENT* event) {
|
||||||
|
return static_cast<MSQuicServer*>(context)->serverListenerCallback(connection, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
266
common/connection/tests/DataConnection_test.cppm
Normal file
266
common/connection/tests/DataConnection_test.cppm
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
module;
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
export module DataConnection_test;
|
||||||
|
import MMO.DataConnection;
|
||||||
|
import std;
|
||||||
|
|
||||||
|
// class DataConnection : public ::testing::Test {
|
||||||
|
// void TearDown() override {
|
||||||
|
// MMO::Networking::MSQUIC::MSQuicGlobal::get()->get().reset();
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
TEST(DataConnection, ClientSettings) {
|
||||||
|
MMO::Networking::NetworkSettings settings{0};
|
||||||
|
MMO::Networking::NetworkCredentials credConfig{
|
||||||
|
.Type = MMO::Networking::Additional::CredentialType::QUIC_CREDENTIAL_TYPE_NONE,
|
||||||
|
.Flags = MMO::Networking::Additional::CredentialFlags::QUIC_CREDENTIAL_FLAG_CLIENT | MMO::Networking::Additional::CredentialFlags::QUIC_CREDENTIAL_FLAG_NO_CERTIFICATE_VALIDATION
|
||||||
|
};
|
||||||
|
|
||||||
|
MMO::Networking::NetworkingConnection::defaultSettings(settings, credConfig);
|
||||||
|
auto res = MMO::Networking::NetworkingConnection::connectTo(settings, credConfig, "127.0.0.1", 2524);
|
||||||
|
EXPECT_TRUE(res.has_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataConnection, ServerSettings) {
|
||||||
|
MMO::Networking::NetworkSettings settings{0};
|
||||||
|
MMO::Networking::NetworkCertificate certFile{
|
||||||
|
.PrivateKeyFile = "../key.pem",
|
||||||
|
.CertificateFile = "../cert.pem",
|
||||||
|
};
|
||||||
|
MMO::Networking::NetworkCredentials credConfig{
|
||||||
|
.Type = MMO::Networking::Additional::CredentialType::QUIC_CREDENTIAL_TYPE_CERTIFICATE_FILE,
|
||||||
|
.CertificateFile = &certFile
|
||||||
|
};
|
||||||
|
|
||||||
|
MMO::Networking::NetworkingServer::defaultSettings(settings, credConfig);
|
||||||
|
auto res = MMO::Networking::NetworkingServer::hostFrom(settings, credConfig, 2524, "127.0.0.1");
|
||||||
|
EXPECT_TRUE(res.has_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataConnection, ConnectToRemote) {
|
||||||
|
MMO::Networking::NetworkSettings settings{0};
|
||||||
|
MMO::Networking::NetworkCredentials credConfig{
|
||||||
|
.Type = MMO::Networking::Additional::CredentialType::QUIC_CREDENTIAL_TYPE_NONE,
|
||||||
|
.Flags = MMO::Networking::Additional::CredentialFlags::QUIC_CREDENTIAL_FLAG_CLIENT | MMO::Networking::Additional::CredentialFlags::QUIC_CREDENTIAL_FLAG_NO_CERTIFICATE_VALIDATION
|
||||||
|
};
|
||||||
|
MMO::Networking::NetworkingConnection::defaultSettings(settings, credConfig);
|
||||||
|
auto res = MMO::Networking::NetworkingConnection::connectTo(settings, credConfig, "127.0.0.1", 2524);
|
||||||
|
EXPECT_TRUE(res.has_value());
|
||||||
|
|
||||||
|
auto connection = res.value();
|
||||||
|
|
||||||
|
auto result = connection->establishConnection();
|
||||||
|
|
||||||
|
EXPECT_FALSE(result.has_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataConnection, ListenForConnections) {
|
||||||
|
MMO::Networking::NetworkSettings settings{0};
|
||||||
|
MMO::Networking::NetworkCertificate certFile{
|
||||||
|
.PrivateKeyFile = "../key.pem",
|
||||||
|
.CertificateFile = "../cert.pem",
|
||||||
|
};
|
||||||
|
MMO::Networking::NetworkCredentials credConfig{
|
||||||
|
.Type = MMO::Networking::Additional::CredentialType::QUIC_CREDENTIAL_TYPE_CERTIFICATE_FILE,
|
||||||
|
.CertificateFile = &certFile
|
||||||
|
};
|
||||||
|
MMO::Networking::NetworkingServer::defaultSettings(settings, credConfig);
|
||||||
|
auto res = MMO::Networking::NetworkingServer::hostFrom(settings, credConfig, 2524, "127.0.0.1");
|
||||||
|
EXPECT_TRUE(res.has_value());
|
||||||
|
|
||||||
|
auto server = res.value();
|
||||||
|
|
||||||
|
EXPECT_THAT(server->startListening(), testing::Eq(std::nullopt));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(DataConnection, EstablishConnection) {
|
||||||
|
auto timeout = std::chrono::seconds(5);
|
||||||
|
auto start = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
|
||||||
|
MMO::Networking::NetworkSettings serverSettings{0};
|
||||||
|
MMO::Networking::NetworkCertificate serverCertFile{
|
||||||
|
.PrivateKeyFile = "../key.pem",
|
||||||
|
.CertificateFile = "../cert.pem",
|
||||||
|
};
|
||||||
|
MMO::Networking::NetworkCredentials serverCredConfig{
|
||||||
|
.Type = MMO::Networking::Additional::CredentialType::QUIC_CREDENTIAL_TYPE_CERTIFICATE_FILE,
|
||||||
|
.CertificateFile = &serverCertFile
|
||||||
|
};
|
||||||
|
MMO::Networking::NetworkingServer::defaultSettings(serverSettings, serverCredConfig);
|
||||||
|
auto serverRes = MMO::Networking::NetworkingServer::hostFrom(serverSettings, serverCredConfig, 2524,
|
||||||
|
"0.0.0.0");
|
||||||
|
EXPECT_TRUE(serverRes.has_value());
|
||||||
|
|
||||||
|
auto server = serverRes.value();
|
||||||
|
|
||||||
|
EXPECT_THAT(server->startListening(), testing::Eq(std::nullopt));
|
||||||
|
|
||||||
|
std::println("Binding listener...");
|
||||||
|
std::fflush(stdout);
|
||||||
|
|
||||||
|
std::shared_ptr<MMO::Networking::NetworkingConnection> serverConnection;
|
||||||
|
|
||||||
|
auto serverCB = std::make_shared<std::function<void(std::shared_ptr<MMO::Networking::NetworkingConnection>)>>(
|
||||||
|
[&serverConnection](auto data) {
|
||||||
|
serverConnection = std::move(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
server->addReceiveCallback(serverCB);
|
||||||
|
|
||||||
|
std::println("Creating client->..");
|
||||||
|
std::fflush(stdout);
|
||||||
|
|
||||||
|
MMO::Networking::NetworkSettings clientSettings{0};
|
||||||
|
MMO::Networking::NetworkCredentials clientCredConfig{
|
||||||
|
.Type = MMO::Networking::Additional::CredentialType::QUIC_CREDENTIAL_TYPE_NONE,
|
||||||
|
.Flags = MMO::Networking::Additional::CredentialFlags::QUIC_CREDENTIAL_FLAG_CLIENT | MMO::Networking::Additional::CredentialFlags::QUIC_CREDENTIAL_FLAG_NO_CERTIFICATE_VALIDATION
|
||||||
|
};
|
||||||
|
MMO::Networking::NetworkingConnection::defaultSettings(clientSettings, clientCredConfig);
|
||||||
|
auto clientRes = MMO::Networking::NetworkingConnection::connectTo(clientSettings, clientCredConfig, "127.0.0.1",
|
||||||
|
2524);
|
||||||
|
EXPECT_TRUE(clientRes.has_value());
|
||||||
|
|
||||||
|
auto client = clientRes.value();
|
||||||
|
|
||||||
|
std::println("Connecting to server...");
|
||||||
|
std::fflush(stdout);
|
||||||
|
EXPECT_FALSE(client->establishConnection().has_value());
|
||||||
|
|
||||||
|
while (!client->isConnected()) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
|
if (std::chrono::high_resolution_clock::now() - start > timeout) {
|
||||||
|
FAIL() << "Connection timed out";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_TRUE(client->isConnected());
|
||||||
|
EXPECT_TRUE(serverConnection != nullptr);
|
||||||
|
|
||||||
|
|
||||||
|
while (!serverConnection->isConnected()) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
|
if (std::chrono::high_resolution_clock::now() - start > timeout) {
|
||||||
|
FAIL() << "Connection timed out";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EXPECT_TRUE(serverConnection->isConnected());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(DataConnection, SendData) {
|
||||||
|
auto timeout = std::chrono::seconds(5);
|
||||||
|
auto start = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
MMO::Networking::NetworkSettings serverSettings{0};
|
||||||
|
MMO::Networking::NetworkCertificate serverCertFile{
|
||||||
|
.PrivateKeyFile = "../key.pem",
|
||||||
|
.CertificateFile = "../cert.pem",
|
||||||
|
};
|
||||||
|
MMO::Networking::NetworkCredentials serverCredConfig{
|
||||||
|
.Type = MMO::Networking::Additional::CredentialType::QUIC_CREDENTIAL_TYPE_CERTIFICATE_FILE,
|
||||||
|
.CertificateFile = &serverCertFile
|
||||||
|
};
|
||||||
|
MMO::Networking::NetworkingServer::defaultSettings(serverSettings, serverCredConfig);
|
||||||
|
auto serverRes = MMO::Networking::NetworkingServer::hostFrom(serverSettings, serverCredConfig, 2524,
|
||||||
|
"0.0.0.0");
|
||||||
|
EXPECT_TRUE(serverRes.has_value());
|
||||||
|
|
||||||
|
auto server = serverRes.value();
|
||||||
|
|
||||||
|
EXPECT_THAT(server->startListening(), testing::Eq(std::nullopt));
|
||||||
|
|
||||||
|
std::println("Binding listener...");
|
||||||
|
std::fflush(stdout);
|
||||||
|
|
||||||
|
std::shared_ptr<MMO::Networking::NetworkingConnection> serverConnection;
|
||||||
|
|
||||||
|
auto serverCB = std::make_shared<std::function<void(std::shared_ptr<MMO::Networking::NetworkingConnection>)>>(
|
||||||
|
[&serverConnection](auto data) {
|
||||||
|
serverConnection = std::move(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
server->addReceiveCallback(serverCB);
|
||||||
|
|
||||||
|
|
||||||
|
std::println("Creating client->..");
|
||||||
|
std::fflush(stdout);
|
||||||
|
|
||||||
|
MMO::Networking::NetworkSettings clientSettings{0};
|
||||||
|
MMO::Networking::NetworkCredentials clientCredConfig{
|
||||||
|
.Type = MMO::Networking::Additional::CredentialType::QUIC_CREDENTIAL_TYPE_NONE,
|
||||||
|
.Flags = MMO::Networking::Additional::CredentialFlags::QUIC_CREDENTIAL_FLAG_CLIENT | MMO::Networking::Additional::CredentialFlags::QUIC_CREDENTIAL_FLAG_NO_CERTIFICATE_VALIDATION
|
||||||
|
};
|
||||||
|
MMO::Networking::NetworkingConnection::defaultSettings(clientSettings, clientCredConfig);
|
||||||
|
auto clientRes = MMO::Networking::NetworkingConnection::connectTo(clientSettings, clientCredConfig, "127.0.0.1",
|
||||||
|
2524);
|
||||||
|
EXPECT_TRUE(clientRes.has_value());
|
||||||
|
|
||||||
|
auto client = clientRes.value();
|
||||||
|
|
||||||
|
std::println("Connecting to server...");
|
||||||
|
std::fflush(stdout);
|
||||||
|
EXPECT_FALSE(client->establishConnection().has_value());
|
||||||
|
|
||||||
|
while (!client->isConnected()) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
|
if (std::chrono::high_resolution_clock::now() - start > timeout) {
|
||||||
|
FAIL() << "Connection timed out";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_TRUE(client->isConnected());
|
||||||
|
EXPECT_TRUE(serverConnection != nullptr);
|
||||||
|
|
||||||
|
|
||||||
|
while (!serverConnection->isConnected()) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
|
if (std::chrono::high_resolution_clock::now() - start > timeout) {
|
||||||
|
FAIL() << "Connection timed out";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EXPECT_TRUE(serverConnection->isConnected());
|
||||||
|
|
||||||
|
std::println("Sending data...");
|
||||||
|
std::fflush(stdout);
|
||||||
|
|
||||||
|
std::shared_ptr<MMO::Networking::NetworkingDataLane> serverStream;
|
||||||
|
|
||||||
|
auto serverStreamCB = std::make_shared<std::function<void(std::shared_ptr<MMO::Networking::NetworkingDataLane>, std::shared_ptr<MMO::Networking::NetworkingConnection>)>>(
|
||||||
|
[&serverStream](const auto& dataLane, const auto&) {
|
||||||
|
serverStream = dataLane;
|
||||||
|
});
|
||||||
|
|
||||||
|
serverConnection->addRemoteStreamCallbacks(serverStreamCB);
|
||||||
|
|
||||||
|
// auto streamRes = client->createStream();
|
||||||
|
// EXPECT_TRUE(streamRes.has_value());
|
||||||
|
// auto stream = streamRes.value();
|
||||||
|
std::vector<uint8_t> testData = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
|
||||||
|
auto sendResult = client->send(testData);
|
||||||
|
EXPECT_FALSE(sendResult.has_value());
|
||||||
|
|
||||||
|
std::println("Waiting for data to be received...");
|
||||||
|
std::fflush(stdout);
|
||||||
|
while (!serverStream || !serverStream->hasData()) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
|
if (std::chrono::high_resolution_clock::now() - start > timeout) {
|
||||||
|
FAIL() << "No data received";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_TRUE(serverStream->hasData());
|
||||||
|
|
||||||
|
auto receivedData = serverStream->retrieveAndResetData();
|
||||||
|
|
||||||
|
EXPECT_THAT(receivedData, testing::ContainerEq(testData));
|
||||||
|
|
||||||
|
}
|
14
common/connection/tests/sanitizers.cpp
Normal file
14
common/connection/tests/sanitizers.cpp
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
void __ubsan_on_report() {
|
||||||
|
FAIL() << "Encountered an undefined behavior sanitizer error";
|
||||||
|
}
|
||||||
|
void __asan_on_error() {
|
||||||
|
FAIL() << "Encountered an address sanitizer error";
|
||||||
|
}
|
||||||
|
void __tsan_on_report() {
|
||||||
|
FAIL() << "Encountered a thread sanitizer error";
|
||||||
|
}
|
||||||
|
}
|
26
common/logging/CMakeLists.txt
Normal file
26
common/logging/CMakeLists.txt
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
project(logging)
|
||||||
|
|
||||||
|
|
||||||
|
set(${PROJECT_NAME}_src
|
||||||
|
src/logging.cppm)
|
||||||
|
# this is the "object library" target: compiles the sources only once
|
||||||
|
add_library(${PROJECT_NAME}_lib STATIC ${${PROJECT_NAME}_src})
|
||||||
|
target_include_directories(${PROJECT_NAME}_lib PUBLIC ./src)
|
||||||
|
|
||||||
|
find_package(spdlog CONFIG REQUIRED)
|
||||||
|
target_link_libraries(${PROJECT_NAME}_lib PUBLIC spdlog::spdlog)
|
||||||
|
# Setup tests
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
|
find_package(GTest CONFIG REQUIRED)
|
||||||
|
include(GoogleTest)
|
||||||
|
|
||||||
|
file(GLOB_RECURSE tests_${PROJECT_NAME}_src CONFIGURE_DEPENDS tests/*.cppm tests/*/*.cppm)
|
||||||
|
|
||||||
|
add_executable(tests_${PROJECT_NAME} ${tests_${PROJECT_NAME}_src}
|
||||||
|
tests/sanitizers.cpp)
|
||||||
|
target_link_libraries(tests_${PROJECT_NAME} PRIVATE GTest::gtest GTest::gtest_main GTest::gmock GTest::gmock_main ${PROJECT_NAME}_lib)
|
||||||
|
add_test(AllTestsIn${PROJECT_NAME} tests_${PROJECT_NAME})
|
||||||
|
|
||||||
|
gtest_discover_tests(tests_${PROJECT_NAME})
|
||||||
|
|
105
common/logging/src/logging.cppm
Normal file
105
common/logging/src/logging.cppm
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
export module MMO.Logging;
|
||||||
|
|
||||||
|
import <spdlog/spdlog.h>;
|
||||||
|
import std;
|
||||||
|
|
||||||
|
namespace MMO::Logging {
|
||||||
|
export class Logger {
|
||||||
|
public:
|
||||||
|
virtual ~Logger() = default;
|
||||||
|
|
||||||
|
|
||||||
|
virtual void log(uint8_t level, const std::string& message) = 0;
|
||||||
|
|
||||||
|
template<class... _Types>
|
||||||
|
void log(uint8_t level, const std::format_string<_Types...> _Fmt, _Types&&... _Args) {
|
||||||
|
log(level, std::format(_Fmt, std::forward<_Types>(_Args)...));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export constexpr uint8_t LEVEL_OFF = 0;
|
||||||
|
export constexpr uint8_t LEVEL_TRACE = 1;
|
||||||
|
export constexpr uint8_t LEVEL_DEBUG = 2;
|
||||||
|
export constexpr uint8_t LEVEL_INFO = 3;
|
||||||
|
export constexpr uint8_t LEVEL_WARN = 4;
|
||||||
|
export constexpr uint8_t LEVEL_ERROR = 5;
|
||||||
|
export constexpr uint8_t LEVEL_FATAL = 6;
|
||||||
|
|
||||||
|
export template<class T>
|
||||||
|
concept LoggerConcept = requires(T a, uint8_t level, const std::string& message) {
|
||||||
|
{ a.log(level, message) } -> std::same_as<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class NullLogger : public Logger {
|
||||||
|
public:
|
||||||
|
void log(const uint8_t level, const std::string& message) override { }
|
||||||
|
};
|
||||||
|
|
||||||
|
export class ConsoleLogger : public Logger {
|
||||||
|
public:
|
||||||
|
uint8_t activeLevel = LEVEL_INFO;
|
||||||
|
|
||||||
|
explicit ConsoleLogger() = default;
|
||||||
|
explicit ConsoleLogger(const uint8_t level) : activeLevel(level) { }
|
||||||
|
|
||||||
|
void log(const uint8_t level, const std::string& message) override {
|
||||||
|
if (level < activeLevel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (level) {
|
||||||
|
case LEVEL_TRACE:
|
||||||
|
std::println("[TRACE] {}", message);
|
||||||
|
break;
|
||||||
|
case LEVEL_DEBUG:
|
||||||
|
std::println("[DEBUG] {}", message);
|
||||||
|
break;
|
||||||
|
case LEVEL_INFO:
|
||||||
|
std::println("[INFO] {}", message);
|
||||||
|
break;
|
||||||
|
case LEVEL_WARN:
|
||||||
|
std::println("[WARN] {}", message);
|
||||||
|
break;
|
||||||
|
case LEVEL_ERROR:
|
||||||
|
std::println("[ERROR] {}", message);
|
||||||
|
break;
|
||||||
|
case LEVEL_FATAL:
|
||||||
|
std::println("[FATAL] {}", message);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
std::println("[UNKNOWN ({})] {}", level, message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export class SPDLogger : public Logger {
|
||||||
|
public:
|
||||||
|
void log(const uint8_t level, const std::string& message) override {
|
||||||
|
switch (level) {
|
||||||
|
case LEVEL_TRACE:
|
||||||
|
spdlog::trace(message);
|
||||||
|
break;
|
||||||
|
case LEVEL_DEBUG:
|
||||||
|
spdlog::debug(message);
|
||||||
|
break;
|
||||||
|
case LEVEL_INFO:
|
||||||
|
spdlog::info(message);
|
||||||
|
break;
|
||||||
|
case LEVEL_WARN:
|
||||||
|
spdlog::warn(message);
|
||||||
|
break;
|
||||||
|
case LEVEL_ERROR:
|
||||||
|
spdlog::error(message);
|
||||||
|
break;
|
||||||
|
case LEVEL_FATAL:
|
||||||
|
spdlog::critical(message);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
spdlog::error("Unknown log level: {}", level);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export std::shared_ptr<Logger> DEFAULT_LOGGER = std::make_shared<NullLogger>();
|
||||||
|
}
|
10
common/logging/tests/logging_test.cppm
Normal file
10
common/logging/tests/logging_test.cppm
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
module;
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
export module Packet_test;
|
||||||
|
import MMO.Packet;
|
||||||
|
import std;
|
14
common/logging/tests/sanitizers.cpp
Normal file
14
common/logging/tests/sanitizers.cpp
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
void __ubsan_on_report() {
|
||||||
|
FAIL() << "Encountered an undefined behavior sanitizer error";
|
||||||
|
}
|
||||||
|
void __asan_on_error() {
|
||||||
|
FAIL() << "Encountered an address sanitizer error";
|
||||||
|
}
|
||||||
|
void __tsan_on_report() {
|
||||||
|
FAIL() << "Encountered a thread sanitizer error";
|
||||||
|
}
|
||||||
|
}
|
26
common/packet/CMakeLists.txt
Normal file
26
common/packet/CMakeLists.txt
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
project(packet)
|
||||||
|
|
||||||
|
|
||||||
|
set(${PROJECT_NAME}_src
|
||||||
|
src/Packet.cppm)
|
||||||
|
# this is the "object library" target: compiles the sources only once
|
||||||
|
add_library(${PROJECT_NAME}_lib STATIC ${${PROJECT_NAME}_src})
|
||||||
|
target_include_directories(${PROJECT_NAME}_lib PUBLIC ./src)
|
||||||
|
|
||||||
|
find_package(cryptopp CONFIG REQUIRED)
|
||||||
|
target_link_libraries(${PROJECT_NAME}_lib PRIVATE cryptopp::cryptopp)
|
||||||
|
# Setup tests
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
|
find_package(GTest CONFIG REQUIRED)
|
||||||
|
include(GoogleTest)
|
||||||
|
|
||||||
|
file(GLOB_RECURSE tests_${PROJECT_NAME}_src CONFIGURE_DEPENDS tests/*.cppm tests/*/*.cppm)
|
||||||
|
|
||||||
|
add_executable(tests_${PROJECT_NAME} ${tests_${PROJECT_NAME}_src}
|
||||||
|
tests/sanitizers.cpp)
|
||||||
|
target_link_libraries(tests_${PROJECT_NAME} PRIVATE GTest::gtest GTest::gtest_main GTest::gmock GTest::gmock_main ${PROJECT_NAME}_lib)
|
||||||
|
add_test(AllTestsIn${PROJECT_NAME} tests_${PROJECT_NAME})
|
||||||
|
|
||||||
|
gtest_discover_tests(tests_${PROJECT_NAME})
|
||||||
|
|
629
common/packet/src/Packet.cppm
Normal file
629
common/packet/src/Packet.cppm
Normal file
@ -0,0 +1,629 @@
|
|||||||
|
module;
|
||||||
|
|
||||||
|
#include <cryptopp/crc.h>
|
||||||
|
|
||||||
|
export module MMO.Packet;
|
||||||
|
import std;
|
||||||
|
import <cassert>;
|
||||||
|
|
||||||
|
namespace MMO::Networking {
|
||||||
|
inline namespace v1 {
|
||||||
|
template<std::integral T>
|
||||||
|
constexpr T enforceLittleEndian(T value) {
|
||||||
|
if constexpr (std::endian::native == std::endian::big) {
|
||||||
|
return std::byteswap(value);
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Container concept
|
||||||
|
*
|
||||||
|
* A type that meets the requirements of the Container concept must have an
|
||||||
|
* emplace() function that matches a vector's emplace() function.
|
||||||
|
* The type must also have a size() function that returns the number of elements in the container.
|
||||||
|
* The type must also have a begin() function that returns an iterator to the beginning of the container.
|
||||||
|
* The type must also have an end() function that returns an iterator to the end of the container.
|
||||||
|
*/
|
||||||
|
export template<typename T>
|
||||||
|
concept Container = requires(T t) {
|
||||||
|
{ t.emplace(t.begin(), std::declval<std::uint8_t>()) };
|
||||||
|
{ std::size(t) } -> std::convertible_to<std::size_t>;
|
||||||
|
{ std::begin(t) };
|
||||||
|
{ std::end(t) };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Encode a value
|
||||||
|
*
|
||||||
|
* Encodes the given value into a sequence of bytes using variable length
|
||||||
|
* encoding, and stores the resulting bytes in the given container.
|
||||||
|
*
|
||||||
|
* @param value The value to encode
|
||||||
|
* @param encoded The container to store the encoded bytes in
|
||||||
|
* @param offset The offset to start encoding at (-1 to start at the end of the container)
|
||||||
|
* @return The number of bytes added to the container
|
||||||
|
*/
|
||||||
|
|
||||||
|
// export template<typename T, Container C>
|
||||||
|
// size_t encodeValue(T value, C& encoded, std::int32_t offset = -1) = delete;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Variable length encode an integral value
|
||||||
|
*
|
||||||
|
* Encodes the given integral value into a sequence of bytes using
|
||||||
|
* variable length encoding, and stores the resulting bytes in the given
|
||||||
|
* container.
|
||||||
|
*
|
||||||
|
* @param value The value to encode
|
||||||
|
* @param encoded The container to store the encoded bytes in
|
||||||
|
* @return The number of bytes added to the container
|
||||||
|
*/
|
||||||
|
export template<std::integral T, Container C>
|
||||||
|
size_t encodeValue(const T valueO, C& encoded, std::int32_t offset = -1) {
|
||||||
|
T value = enforceLittleEndian(valueO);
|
||||||
|
|
||||||
|
auto insertPos = offset < 0 ? std::size(encoded) + offset + 1 : offset;
|
||||||
|
|
||||||
|
size_t count = 0;
|
||||||
|
|
||||||
|
T comparator = T(value < 0);
|
||||||
|
|
||||||
|
std::uint8_t data;
|
||||||
|
|
||||||
|
if constexpr (std::signed_integral<T>) {
|
||||||
|
data = value & 0b00111111u;
|
||||||
|
if (value < 0) {
|
||||||
|
data |= 0b01000000u;
|
||||||
|
}
|
||||||
|
value >>= 6;
|
||||||
|
} else {
|
||||||
|
data = value & 0b01111111u;
|
||||||
|
value >>= 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (value + comparator) {
|
||||||
|
auto writeLocation = std::begin(encoded);
|
||||||
|
std::advance(writeLocation, insertPos + count);
|
||||||
|
encoded.emplace(writeLocation, data);
|
||||||
|
|
||||||
|
|
||||||
|
++count;
|
||||||
|
|
||||||
|
data = value & 0b01111111u;
|
||||||
|
value >>= 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto writeLocation = std::begin(encoded);
|
||||||
|
std::advance(writeLocation, insertPos + count);
|
||||||
|
++count;
|
||||||
|
encoded.emplace(writeLocation, static_cast<uint8_t>(data | 0b10000000u));
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
export template<Container C>
|
||||||
|
size_t encodeValue(const float value, C& encoded, std::int32_t offset = -1) {
|
||||||
|
// std::numeric_limits<float>::digits is 24
|
||||||
|
// std::numeric_limits<float>::max_exponent is 128 (we convert this to 127)
|
||||||
|
// std::numeric_limits<float>::min_exponent is -125 (we convert this to -126)
|
||||||
|
// `exp` is -128 (IEEE 754 this would be -1) is NaN when `mantisa` is 2
|
||||||
|
// `exp` is -128 (IEEE 754 this would be -1) is Inf when `mantisa` is 0 or 1 (uses signbit in IEEE 754 but this would bloat the encoding since it would be part of `sign`)
|
||||||
|
// The final exp needs to be adjusted by adding 1 and subtracting 24 to get the correct exponent
|
||||||
|
size_t count = 0;
|
||||||
|
|
||||||
|
auto insertPos = offset < 0 ? std::size(encoded) + offset + 1 : offset;
|
||||||
|
|
||||||
|
if (std::isnan(value) || std::isinf(value)) {
|
||||||
|
count += encodeValue(
|
||||||
|
static_cast<std::int8_t>(-128), encoded,
|
||||||
|
static_cast<std::int32_t>(insertPos + count)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::isnan(value)) {
|
||||||
|
count += encodeValue(
|
||||||
|
2, encoded,
|
||||||
|
static_cast<std::int32_t>(insertPos + count)
|
||||||
|
);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
if (std::isinf(value)) {
|
||||||
|
count += encodeValue(
|
||||||
|
value < 0 ? 0 : 1,
|
||||||
|
encoded,
|
||||||
|
static_cast<std::int32_t>(insertPos + count)
|
||||||
|
);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
int exp = 0;
|
||||||
|
const auto frac = std::frexp(value, &exp);
|
||||||
|
|
||||||
|
// This adjustment is necessary to get the correct exponent. `exp` will be between 128 to -125,
|
||||||
|
// so this adjustment will force it back into being a valid int8_t
|
||||||
|
|
||||||
|
const int adjExp = exp - 1;
|
||||||
|
count = encodeValue(
|
||||||
|
static_cast<std::int8_t>(adjExp), encoded,
|
||||||
|
static_cast<std::int32_t>(insertPos + count)
|
||||||
|
);
|
||||||
|
|
||||||
|
// If the exponent is 0, then the mantissa is 0, so we can return early
|
||||||
|
if (exp == 0) {
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto sign = std::scalbn(frac, std::numeric_limits<float>::digits);
|
||||||
|
|
||||||
|
count += encodeValue(
|
||||||
|
static_cast<std::int32_t>(sign), encoded,
|
||||||
|
static_cast<std::int32_t>(insertPos + count)
|
||||||
|
);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
export template<Container C>
|
||||||
|
size_t encodeValue(const double value, C& encoded, std::int32_t offset = -1) {
|
||||||
|
// std::numeric_limits<double>::digits is 53
|
||||||
|
// std::numeric_limits<double>::max_exponent is 1024
|
||||||
|
// std::numeric_limits<double>::min_exponent is -1021
|
||||||
|
// `exp` is -2048 (IEEE 754 this would be -1) is NaN when `mantisa` is 2
|
||||||
|
// `exp` is -2048 (IEEE 754 this would be -1) is Inf when `mantisa` is 0 or 1 (uses signbit in IEEE 754 but this would bloat the encoding since it would be part of `sign`)
|
||||||
|
// The final exp needs to be adjusted by adding 1 and subtracting 24 to get the correct exponent
|
||||||
|
size_t count = 0;
|
||||||
|
|
||||||
|
auto insertPos = offset < 0 ? std::size(encoded) + offset + 1 : offset;
|
||||||
|
|
||||||
|
if (std::isnan(value) || std::isinf(value)) {
|
||||||
|
count += encodeValue(
|
||||||
|
static_cast<std::int16_t>(-2048), encoded,
|
||||||
|
static_cast<std::int32_t>(insertPos + count)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::isnan(value)) {
|
||||||
|
count += encodeValue(
|
||||||
|
static_cast<std::int64_t>(2), encoded,
|
||||||
|
static_cast<std::int32_t>(insertPos + count)
|
||||||
|
);
|
||||||
|
return count;
|
||||||
|
} else if (std::isinf(value)) {
|
||||||
|
if (value < 0) {
|
||||||
|
count += encodeValue(
|
||||||
|
static_cast<std::int64_t>(0), encoded,
|
||||||
|
static_cast<std::int32_t>(insertPos + count)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
count += encodeValue(
|
||||||
|
static_cast<std::int64_t>(1), encoded,
|
||||||
|
static_cast<std::int32_t>(insertPos + count)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
int exp = 0;
|
||||||
|
const auto frac = std::frexp(value, &exp);
|
||||||
|
|
||||||
|
// This adjustment is necessary to get the correct exponent. `exp` will be between 128 to -125,
|
||||||
|
// so this adjustment will force it back into being a valid int8_t
|
||||||
|
|
||||||
|
count = encodeValue(exp, encoded, static_cast<std::int32_t>(insertPos + count));
|
||||||
|
|
||||||
|
// If the exponent is 0, then the mantissa is 0, so we can return early
|
||||||
|
if (exp == 0) {
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto sign = std::scalbn(frac, std::numeric_limits<double>::digits);
|
||||||
|
|
||||||
|
count += encodeValue(
|
||||||
|
static_cast<std::int64_t>(sign), encoded,
|
||||||
|
static_cast<std::int32_t>(insertPos + count)
|
||||||
|
);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
export template<std::ranges::random_access_range Box, Container C>
|
||||||
|
size_t encodeValue(const Box& value, C& encoded, std::int32_t offset = -1) {
|
||||||
|
size_t count = encodeValue(static_cast<std::uint32_t>(std::size(value)), encoded, offset);
|
||||||
|
|
||||||
|
for (const auto& data : value) {
|
||||||
|
auto adjustedOffset = offset < 0
|
||||||
|
? std::min(offset + static_cast<std::int32_t>(count), -1)
|
||||||
|
: offset + static_cast<std::int32_t>(count);
|
||||||
|
count += encodeValue(data, encoded, adjustedOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ParseError : public std::runtime_error {
|
||||||
|
public:
|
||||||
|
static constexpr uint16_t NOT_ENOUGH_DATA = 1;
|
||||||
|
static constexpr uint16_t INVALID_ENCODING = 2;
|
||||||
|
const uint16_t reasonCode;
|
||||||
|
|
||||||
|
ParseError(const std::string& what, uint16_t reasonCode)
|
||||||
|
: std::runtime_error(what), reasonCode(reasonCode) { }
|
||||||
|
|
||||||
|
ParseError(const char* what, uint16_t reasonCode)
|
||||||
|
: std::runtime_error(what), reasonCode(reasonCode) { }
|
||||||
|
};
|
||||||
|
|
||||||
|
// export template<typename T, std::integral Counter>
|
||||||
|
// std::expected<T, ParseError> decodeValue(const std::span<std::uint8_t>& data, Counter& read) = delete;
|
||||||
|
|
||||||
|
|
||||||
|
export template<std::integral T, std::integral Counter>
|
||||||
|
std::expected<T, ParseError> decodeValue(const std::span<std::uint8_t>& data, Counter& read) {
|
||||||
|
if (data.size() == 0) {
|
||||||
|
return std::unexpected(ParseError("Not enough data to decode", ParseError::NOT_ENOUGH_DATA));
|
||||||
|
}
|
||||||
|
|
||||||
|
++read;
|
||||||
|
if (data[0] & 0b10000000) {
|
||||||
|
if constexpr (std::signed_integral<T>) {
|
||||||
|
if (data[0] & 0b01000000) {
|
||||||
|
return T((~data[0] & 0b00111111) ^ -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return T(data[0] & 0b01111111);
|
||||||
|
}
|
||||||
|
|
||||||
|
T value = 0;
|
||||||
|
std::uint8_t shift = 0;
|
||||||
|
|
||||||
|
if constexpr (std::signed_integral<T>) {
|
||||||
|
value |= data[0] & 0b00111111;
|
||||||
|
shift = 6;
|
||||||
|
} else {
|
||||||
|
value = data[0] & 0b01111111;
|
||||||
|
shift = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::uint8_t maxShifts = sizeof(T) * 8;
|
||||||
|
|
||||||
|
for (size_t i = 1; i < data.size(); ++i) {
|
||||||
|
++read;
|
||||||
|
value |= (static_cast<T>(data[i]) & 0b01111111) << shift;
|
||||||
|
|
||||||
|
shift += 7;
|
||||||
|
|
||||||
|
if (data[i] & 0b10000000) {
|
||||||
|
if constexpr (std::signed_integral<T>) {
|
||||||
|
if (data[0] & 0b01000000) {
|
||||||
|
// Fill the leftover bits with ones since it's a negative number
|
||||||
|
value |= T(-1) << std::min<std::uint8_t>(shift, maxShifts - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return enforceLittleEndian(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shift >= maxShifts) {
|
||||||
|
return std::unexpected(ParseError("Value too large to decode", ParseError::INVALID_ENCODING));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::unexpected(ParseError("Not enough data to decode", ParseError::NOT_ENOUGH_DATA));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
concept IsFloat = std::same_as<float, T>;
|
||||||
|
|
||||||
|
export template<IsFloat T, std::integral Counter>
|
||||||
|
std::expected<T, ParseError> decodeValue(const std::span<std::uint8_t>& data, Counter& read) {
|
||||||
|
if (data.size() < 1) {
|
||||||
|
return std::unexpected(ParseError("Not enough data to decode", ParseError::NOT_ENOUGH_DATA));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0b11111111 is -1
|
||||||
|
if (data[0] == 0b11111111) {
|
||||||
|
++read;
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.size() < 2) {
|
||||||
|
return std::unexpected(ParseError("Not enough data to decode", ParseError::NOT_ENOUGH_DATA));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Even though the encoded size is int8_t, we need to use int16_t to handle the additional adjustments if it is valid
|
||||||
|
// The encoding supports this cast/conversion
|
||||||
|
std::expected<std::int16_t, ParseError> exp = decodeValue<std::int16_t>(data, read);
|
||||||
|
if (!exp) {
|
||||||
|
return std::unexpected(exp.error());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*exp == -128) {
|
||||||
|
std::expected<std::int32_t, ParseError> mantissa = decodeValue<std::int32_t>(data.subspan(read), read);
|
||||||
|
if (!mantissa) {
|
||||||
|
return std::unexpected(mantissa.error());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*mantissa == 0) {
|
||||||
|
return -std::numeric_limits<T>::infinity();
|
||||||
|
} else if (*mantissa == 1) {
|
||||||
|
return std::numeric_limits<T>::infinity();
|
||||||
|
} else if (*mantissa == 2) {
|
||||||
|
return std::numeric_limits<T>::quiet_NaN();
|
||||||
|
} else {
|
||||||
|
return std::unexpected(ParseError("Invalid NaN/Inf encoding", ParseError::INVALID_ENCODING));
|
||||||
|
}
|
||||||
|
} else if (*exp < -126 && *exp > 127) {
|
||||||
|
return std::unexpected(ParseError("Invalid encoding", ParseError::INVALID_ENCODING));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<std::int32_t, ParseError> mantissa = decodeValue<std::int32_t>(data.subspan(read), read);
|
||||||
|
if (!mantissa) {
|
||||||
|
return std::unexpected(mantissa.error());
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::scalbn(static_cast<T>(*mantissa), *exp + 1 - std::numeric_limits<T>::digits);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
concept IsDouble = std::same_as<double, T>;
|
||||||
|
|
||||||
|
export template<IsDouble T, std::integral Counter>
|
||||||
|
std::expected<T, ParseError> decodeValue(const std::span<std::uint8_t>& data, Counter& read) {
|
||||||
|
if (data.size() == 0) {
|
||||||
|
return std::unexpected(ParseError("Not enough data to decode", ParseError::NOT_ENOUGH_DATA));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 128 is 0
|
||||||
|
if (data[0] == static_cast<std::uint8_t>(0b10000000)) {
|
||||||
|
++read;
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.size() < 2) {
|
||||||
|
return std::unexpected(ParseError("Not enough data to decode", ParseError::NOT_ENOUGH_DATA));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<std::int16_t, ParseError> exp = decodeValue<std::int16_t>(data, read);
|
||||||
|
if (!exp) {
|
||||||
|
return std::unexpected(exp.error());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*exp == -2048) {
|
||||||
|
std::expected<std::int64_t, ParseError> mantissa = decodeValue<std::int64_t>(data.subspan(read), read);
|
||||||
|
if (!mantissa) {
|
||||||
|
return std::unexpected(mantissa.error());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*mantissa == 0) {
|
||||||
|
return -std::numeric_limits<T>::infinity();
|
||||||
|
} else if (*mantissa == 1) {
|
||||||
|
return std::numeric_limits<T>::infinity();
|
||||||
|
} else if (*mantissa == 2) {
|
||||||
|
return std::numeric_limits<T>::quiet_NaN();
|
||||||
|
} else {
|
||||||
|
return std::unexpected(ParseError("Invalid NaN/Inf encoding", ParseError::INVALID_ENCODING));
|
||||||
|
}
|
||||||
|
} else if (*exp < -1021 && *exp > 1024) {
|
||||||
|
return std::unexpected(ParseError("Invalid encoding", ParseError::INVALID_ENCODING));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<std::int64_t, ParseError> mantissa = decodeValue<std::int64_t>(data.subspan(read), read);
|
||||||
|
if (!mantissa) {
|
||||||
|
return std::unexpected(mantissa.error());
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::scalbn(static_cast<T>(*mantissa), *exp - std::numeric_limits<T>::digits);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
concept IsCollection = requires(T t) {
|
||||||
|
typename T::value_type;
|
||||||
|
typename T::size_type;
|
||||||
|
{ t.reserve(std::declval<typename T::size_type>()) };
|
||||||
|
{ t.emplace_back(std::declval<typename T::value_type>()) };
|
||||||
|
};
|
||||||
|
|
||||||
|
export template<IsCollection Box, std::integral Counter>
|
||||||
|
std::expected<Box, ParseError> decodeValue(const std::span<std::uint8_t>& data, Counter& read) {
|
||||||
|
if (data.size() == 0) {
|
||||||
|
return std::unexpected(ParseError("Not enough data to decode", ParseError::NOT_ENOUGH_DATA));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[0] == static_cast<std::uint8_t>(0b10000000)) {
|
||||||
|
return Box{ };
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<std::uint32_t, ParseError> size = decodeValue<std::uint32_t>(data, read);
|
||||||
|
if (!size) {
|
||||||
|
return std::unexpected(size.error());
|
||||||
|
}
|
||||||
|
|
||||||
|
Box box;
|
||||||
|
box.reserve(static_cast<typename Box::size_type>(*size));
|
||||||
|
|
||||||
|
for (std::uint32_t i = 0; i < *size; ++i) {
|
||||||
|
std::expected<typename Box::value_type, ParseError> value = decodeValue<typename Box::value_type>(
|
||||||
|
data.subspan(read), read
|
||||||
|
);
|
||||||
|
if (!value) {
|
||||||
|
return std::unexpected(value.error());
|
||||||
|
}
|
||||||
|
|
||||||
|
box.emplace_back(*value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
|
||||||
|
export template<typename T>
|
||||||
|
constexpr std::expected<T, ParseError> decodeValue(const std::span<std::uint8_t>& data) {
|
||||||
|
int read = 0;
|
||||||
|
return decodeValue<T>(data, read);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aim for ~350 byte packets
|
||||||
|
export class Packet {
|
||||||
|
public:
|
||||||
|
static constexpr std::uint32_t VERSION = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::uint32_t packetVersion = VERSION;
|
||||||
|
std::uint32_t crcHash = 0;
|
||||||
|
std::uint32_t size = 0;
|
||||||
|
|
||||||
|
std::vector<std::uint8_t> data = { };
|
||||||
|
|
||||||
|
std::int32_t readHead = 0;
|
||||||
|
std::int32_t writeHead = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Packet() = default;
|
||||||
|
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
constexpr size_t write(const T& value) {
|
||||||
|
const auto written = encodeValue(value, data, writeHead);
|
||||||
|
size += static_cast<std::uint32_t>(written);
|
||||||
|
writeHead += static_cast<std::int32_t>(written);
|
||||||
|
return written;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
constexpr std::expected<T, ParseError> read() {
|
||||||
|
return decodeValue<T>(std::span(data).subspan(readHead), readHead);;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
data.clear();
|
||||||
|
readHead = 0;
|
||||||
|
writeHead = 0;
|
||||||
|
size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::span<std::uint8_t> bytes() {
|
||||||
|
return std::span(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::uint32_t packetSize() const {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::uint32_t crc() const {
|
||||||
|
return crcHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::uint32_t version() const {
|
||||||
|
return packetVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::int32_t readPosition() const {
|
||||||
|
return readHead;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::int32_t writePosition() const {
|
||||||
|
return writeHead;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setReadPosition(std::int32_t position) {
|
||||||
|
readHead = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setWritePosition(std::int32_t position) {
|
||||||
|
writeHead = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, ParseError> initializePacket(const std::span<std::uint8_t>& encodedData, int& read) {
|
||||||
|
if (encodedData.size() < 6) {
|
||||||
|
return std::unexpected(
|
||||||
|
ParseError("Not enough data to initialize packet", ParseError::NOT_ENOUGH_DATA)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto pVesion = decodeValue<std::uint8_t>(encodedData, read);
|
||||||
|
if (!pVesion) {
|
||||||
|
return std::unexpected(pVesion.error());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
auto crc = decodeValue<std::uint32_t>(encodedData.subspan(read), read);
|
||||||
|
if (!crc) {
|
||||||
|
return std::unexpected(crc.error());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const auto crcHead = read;
|
||||||
|
|
||||||
|
auto pSize = decodeValue<std::uint32_t>(encodedData.subspan(read), read);
|
||||||
|
if (!pSize) {
|
||||||
|
return std::unexpected(pSize.error());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (encodedData.size() < *pSize + read) {
|
||||||
|
return std::unexpected(ParseError("Not enough data to initialize packet", ParseError::NOT_ENOUGH_DATA));
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto dataOffset = read - crcHead;
|
||||||
|
|
||||||
|
const auto tempCRC = *crc;
|
||||||
|
|
||||||
|
if (CryptoPP::CRC32C crc32; !crc32.VerifyDigest(
|
||||||
|
reinterpret_cast<const CryptoPP::byte*>(&tempCRC),
|
||||||
|
encodedData.subspan(crcHead).data(), *pSize + dataOffset
|
||||||
|
)) {
|
||||||
|
return std::unexpected(ParseError("Invalid CRC", ParseError::INVALID_ENCODING));
|
||||||
|
}
|
||||||
|
|
||||||
|
packetVersion = *pVesion;
|
||||||
|
crcHash = tempCRC;
|
||||||
|
size = *pSize;
|
||||||
|
|
||||||
|
auto payload = encodedData.subspan(read, *pSize);
|
||||||
|
data = std::vector(payload.begin(), payload.end());
|
||||||
|
|
||||||
|
assert(data.size() == size);
|
||||||
|
|
||||||
|
writeHead = static_cast<std::int32_t>(size);
|
||||||
|
|
||||||
|
return { };
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, ParseError> initializePacket(const std::span<std::uint8_t>& encodedData) {
|
||||||
|
int read = 0;
|
||||||
|
return initializePacket(encodedData, read);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static std::expected<Packet, ParseError> createPacket(const std::span<std::uint8_t>& encodedData, int& read) {
|
||||||
|
Packet packet;
|
||||||
|
if (auto result = packet.initializePacket(encodedData, read)) {
|
||||||
|
return packet;
|
||||||
|
} else {
|
||||||
|
return std::unexpected(result.error());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::expected<Packet, ParseError> createPacket(const std::span<std::uint8_t>& encodedData) {
|
||||||
|
int read = 0;
|
||||||
|
return createPacket(encodedData, read);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::uint8_t> finalizePacket() {
|
||||||
|
assert(size == data.size());
|
||||||
|
|
||||||
|
std::vector<std::uint8_t> encodedData;
|
||||||
|
encodeValue(size, encodedData);
|
||||||
|
encodedData.insert(encodedData.end(), std::begin(data), std::end(data));
|
||||||
|
|
||||||
|
CryptoPP::CRC32C crc32;
|
||||||
|
crc32.Update(encodedData.data(), encodedData.size());
|
||||||
|
crc32.Final(reinterpret_cast<CryptoPP::byte*>(&crcHash));
|
||||||
|
|
||||||
|
encodeValue(crcHash, encodedData, 0);
|
||||||
|
encodeValue(packetVersion, encodedData, 0);
|
||||||
|
|
||||||
|
|
||||||
|
return encodedData;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
991
common/packet/tests/Packet_test.cppm
Normal file
991
common/packet/tests/Packet_test.cppm
Normal file
@ -0,0 +1,991 @@
|
|||||||
|
module;
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
export module Packet_test;
|
||||||
|
import MMO.Packet;
|
||||||
|
import std;
|
||||||
|
|
||||||
|
// Encodes
|
||||||
|
TEST(DataEncoding, uint8Min) {
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
EXPECT_EQ(MMO::Networking::encodeValue(std::numeric_limits<uint8_t>::min(), encoded), 1);
|
||||||
|
EXPECT_EQ(encoded, std::vector<uint8_t>{0b10000000});
|
||||||
|
encoded.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataEncoding, uint8Max) {
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
EXPECT_EQ(MMO::Networking::encodeValue(std::numeric_limits<uint8_t>::max(), encoded), 2);
|
||||||
|
EXPECT_EQ(encoded, std::vector<uint8_t>({ 0b01111111, 0b10000001}));
|
||||||
|
encoded.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataEncoding, int8Min) {
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
EXPECT_EQ(MMO::Networking::encodeValue(std::numeric_limits<int8_t>::min(), encoded), 2);
|
||||||
|
EXPECT_EQ(encoded, std::vector<uint8_t>({ 0b01000000, 0b11111110}));
|
||||||
|
encoded.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataEncoding, int8Max) {
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
EXPECT_EQ(MMO::Networking::encodeValue(std::numeric_limits<int8_t>::max(), encoded), 2);
|
||||||
|
EXPECT_EQ(encoded, std::vector<uint8_t>({ 0b00111111,0b10000001,}));
|
||||||
|
encoded.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataEncoding, uint16Min) {
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
EXPECT_EQ(MMO::Networking::encodeValue(std::numeric_limits<uint16_t>::min(), encoded), 1);
|
||||||
|
EXPECT_EQ(encoded, std::vector<uint8_t>{0b10000000});
|
||||||
|
encoded.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataEncoding, uint16Max) {
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
EXPECT_EQ(MMO::Networking::encodeValue(std::numeric_limits<uint16_t>::max(), encoded), 3);
|
||||||
|
EXPECT_EQ(encoded, std::vector<uint8_t>({ 0b01111111, 0b01111111, 0b10000011}));
|
||||||
|
encoded.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataEncoding, int16Min) {
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
EXPECT_EQ(MMO::Networking::encodeValue(std::numeric_limits<int16_t>::min(), encoded), 3);
|
||||||
|
EXPECT_EQ(encoded, std::vector<uint8_t>({ 0b01000000, 0, 0b11111100}));
|
||||||
|
encoded.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataEncoding, int16Max) {
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
EXPECT_EQ(MMO::Networking::encodeValue(std::numeric_limits<int16_t>::max(), encoded), 3);
|
||||||
|
EXPECT_EQ(encoded, std::vector<uint8_t>({ 0b00111111, 0b01111111, 0b10000011}));
|
||||||
|
encoded.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataEncoding, uint32Min) {
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
EXPECT_EQ(MMO::Networking::encodeValue(std::numeric_limits<uint32_t>::min(), encoded), 1);
|
||||||
|
EXPECT_EQ(encoded, std::vector<uint8_t>{0b10000000});
|
||||||
|
encoded.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataEncoding, uint32Max) {
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
EXPECT_EQ(MMO::Networking::encodeValue(std::numeric_limits<uint32_t>::max(), encoded), 5);
|
||||||
|
EXPECT_EQ(encoded, std::vector<uint8_t>({ 0b01111111, 0b01111111, 0b01111111, 0b01111111, 0b10001111}));
|
||||||
|
encoded.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataEncoding, int32Min) {
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
EXPECT_EQ(MMO::Networking::encodeValue(std::numeric_limits<int32_t>::min(), encoded), 5);
|
||||||
|
EXPECT_EQ(encoded, std::vector<uint8_t>({ 0b01000000, 0, 0, 0, 0b11110000}));
|
||||||
|
encoded.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataEncoding, int32Max) {
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
EXPECT_EQ(MMO::Networking::encodeValue(std::numeric_limits<int32_t>::max(), encoded), 5);
|
||||||
|
EXPECT_EQ(encoded, std::vector<uint8_t>({ 0b00111111, 0b01111111, 0b01111111, 0b01111111, 0b10001111}));
|
||||||
|
encoded.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataEncoding, uint64Min) {
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
EXPECT_EQ(MMO::Networking::encodeValue(std::numeric_limits<uint64_t>::min(), encoded), 1);
|
||||||
|
EXPECT_EQ(encoded, std::vector<uint8_t>{0b10000000});
|
||||||
|
encoded.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataEncoding, uint64Max) {
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
EXPECT_EQ(MMO::Networking::encodeValue(std::numeric_limits<uint64_t>::max(), encoded), 10);
|
||||||
|
EXPECT_EQ(encoded,
|
||||||
|
std::vector<uint8_t>({ 0b01111111, 0b01111111, 0b01111111, 0b01111111, 0b01111111, 0b01111111, 0b01111111,
|
||||||
|
0b01111111, 0b01111111, 0b10000001}));
|
||||||
|
encoded.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataEncoding, int64Min) {
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
EXPECT_EQ(MMO::Networking::encodeValue(std::numeric_limits<int64_t>::min(), encoded), 10);
|
||||||
|
EXPECT_EQ(encoded,
|
||||||
|
std::vector<uint8_t>({0b01000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
|
||||||
|
0b00000000, 0b00000000, 0b11111110}));
|
||||||
|
encoded.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataEncoding, int64Max) {
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
EXPECT_EQ(MMO::Networking::encodeValue(std::numeric_limits<int64_t>::max(), encoded), 10);
|
||||||
|
EXPECT_EQ(encoded,
|
||||||
|
std::vector<uint8_t>({ 0b00111111, 0b01111111, 0b01111111, 0b01111111, 0b01111111, 0b01111111, 0b01111111,
|
||||||
|
0b01111111,
|
||||||
|
0b01111111, 0b10000001}));
|
||||||
|
encoded.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(DataEncoding, float0) {
|
||||||
|
const auto value = 0.0f;
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
EXPECT_EQ(MMO::Networking::encodeValue(value, encoded), 1);
|
||||||
|
EXPECT_EQ(encoded,
|
||||||
|
std::vector<uint8_t>({ 0b11111111}));
|
||||||
|
encoded.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataEncoding, floatMin) {
|
||||||
|
const auto value = std::numeric_limits<float>::min();
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
EXPECT_EQ(MMO::Networking::encodeValue(value, encoded), 6);
|
||||||
|
EXPECT_EQ(encoded,
|
||||||
|
std::vector<uint8_t>({ 0b01000010, 0b11111110, 0, 0, 0, 0b10001000}));
|
||||||
|
encoded.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataEncoding, floatLowest) {
|
||||||
|
const auto value = std::numeric_limits<float>::lowest();
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
EXPECT_EQ(MMO::Networking::encodeValue(value, encoded), 6);
|
||||||
|
EXPECT_EQ(encoded,
|
||||||
|
std::vector<uint8_t>({ 0b00111111, 0b10000001, 0b01000001, 0 ,0, 0b11110000}));
|
||||||
|
encoded.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataEncoding, floatMax) {
|
||||||
|
const auto value = std::numeric_limits<float>::max();
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
EXPECT_EQ(MMO::Networking::encodeValue(value, encoded), 6);
|
||||||
|
EXPECT_EQ(encoded,
|
||||||
|
std::vector<uint8_t>({ 0b00111111, 0b10000001, 0b00111111, 0b01111111, 0b01111111, 0b10001111}));
|
||||||
|
encoded.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(DataEncoding, floatNan) {
|
||||||
|
const auto value = std::numeric_limits<float>::quiet_NaN();
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
EXPECT_EQ(MMO::Networking::encodeValue(value, encoded), 3);
|
||||||
|
EXPECT_EQ(encoded,
|
||||||
|
std::vector<uint8_t>({ 0b01000000, 0b11111110, 0b10000010}));
|
||||||
|
encoded.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(DataEncoding, floatInfPositive) {
|
||||||
|
const auto value = std::numeric_limits<float>::infinity();
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
EXPECT_EQ(MMO::Networking::encodeValue(value, encoded), 3);
|
||||||
|
EXPECT_EQ(encoded,
|
||||||
|
std::vector<uint8_t>({ 0b01000000, 0b11111110, 0b10000001}));
|
||||||
|
encoded.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(DataEncoding, floatInfNegative) {
|
||||||
|
const auto value = -std::numeric_limits<float>::infinity();
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
EXPECT_EQ(MMO::Networking::encodeValue(value, encoded), 3);
|
||||||
|
EXPECT_EQ(encoded,
|
||||||
|
std::vector<uint8_t>({ 0b01000000, 0b11111110, 0b10000000}));
|
||||||
|
encoded.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(DataEncoding, double0) {
|
||||||
|
const double value = 0.0;
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
EXPECT_EQ(MMO::Networking::encodeValue(value, encoded), 1);
|
||||||
|
EXPECT_EQ(encoded,
|
||||||
|
std::vector<uint8_t>({ 0b10000000 }));
|
||||||
|
encoded.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataEncoding, doubleMin) {
|
||||||
|
const auto value = std::numeric_limits<double>::min();
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
EXPECT_EQ(MMO::Networking::encodeValue(value, encoded), 10);
|
||||||
|
EXPECT_EQ(encoded,
|
||||||
|
std::vector<uint8_t>({ 0b01000011, 0b11110000, 0, 0, 0, 0,0,0,0,0b10010000}));
|
||||||
|
encoded.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataEncoding, doubleLowest) {
|
||||||
|
const auto value = std::numeric_limits<double>::lowest();
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
EXPECT_EQ(MMO::Networking::encodeValue(value, encoded), 10);
|
||||||
|
EXPECT_EQ(encoded,
|
||||||
|
std::vector<uint8_t>({ 0, 0b10010000, 0b01000001,0, 0 ,0,0,0,0, 0b11100000}));
|
||||||
|
encoded.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataEncoding, doubleMax) {
|
||||||
|
const auto value = std::numeric_limits<double>::max();
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
EXPECT_EQ(MMO::Networking::encodeValue(value, encoded), 10);
|
||||||
|
EXPECT_EQ(encoded,
|
||||||
|
std::vector<uint8_t>({ 0, 0b10010000, 0b00111111, 0b01111111, 0b01111111, 0b01111111, 0b01111111,
|
||||||
|
0b01111111, 0b01111111, 0b10011111}));
|
||||||
|
encoded.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(DataEncoding, doubleNan) {
|
||||||
|
const auto value = std::numeric_limits<double>::quiet_NaN();
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
EXPECT_EQ(MMO::Networking::encodeValue(value, encoded), 3);
|
||||||
|
EXPECT_EQ(encoded,
|
||||||
|
std::vector<uint8_t>({ 0b01000000, 0b11100000, 0b10000010}));
|
||||||
|
encoded.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(DataEncoding, doubleInfPositive) {
|
||||||
|
const auto value = std::numeric_limits<double>::infinity();
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
EXPECT_EQ(MMO::Networking::encodeValue(value, encoded), 3);
|
||||||
|
EXPECT_EQ(encoded,
|
||||||
|
std::vector<uint8_t>({ 0b01000000, 0b11100000, 0b10000001}));
|
||||||
|
encoded.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(DataEncoding, doubleInfNegative) {
|
||||||
|
const auto value = -std::numeric_limits<double>::infinity();
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
EXPECT_EQ(MMO::Networking::encodeValue(value, encoded), 3);
|
||||||
|
EXPECT_EQ(encoded,
|
||||||
|
std::vector<uint8_t>({ 0b01000000, 0b11100000, 0b10000000}));
|
||||||
|
encoded.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Decodes
|
||||||
|
TEST(DataDecoding, uint8Min) {
|
||||||
|
std::vector<uint8_t> encoded = { 0b10000000 };
|
||||||
|
int read = 0;
|
||||||
|
auto value = MMO::Networking::decodeValue<uint8_t>(encoded, read);
|
||||||
|
EXPECT_TRUE(value.has_value());
|
||||||
|
EXPECT_EQ(read, encoded.size());
|
||||||
|
EXPECT_EQ(value.value(), std::numeric_limits<uint8_t>::min());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataDecoding, uint8Max) {
|
||||||
|
std::vector<uint8_t> encoded = { 0b01111111, 0b10000001 };
|
||||||
|
int read = 0;
|
||||||
|
auto value = MMO::Networking::decodeValue<uint8_t>(encoded, read);
|
||||||
|
EXPECT_TRUE(value.has_value());
|
||||||
|
EXPECT_EQ(read, encoded.size());
|
||||||
|
EXPECT_EQ(value.value(), std::numeric_limits<uint8_t>::max());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataDecoding, int8Min) {
|
||||||
|
std::vector<uint8_t> encoded = { 0b01000000, 0b11111110 };
|
||||||
|
int read = 0;
|
||||||
|
auto value = MMO::Networking::decodeValue<int8_t>(encoded, read);
|
||||||
|
EXPECT_TRUE(value.has_value());
|
||||||
|
EXPECT_EQ(read, encoded.size());
|
||||||
|
EXPECT_EQ(value.value(), std::numeric_limits<int8_t>::min());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataDecoding, int8Max) {
|
||||||
|
std::vector<uint8_t> encoded = { 0b00111111, 0b10000001, };
|
||||||
|
int read = 0;
|
||||||
|
auto value = MMO::Networking::decodeValue<int8_t>(encoded, read);
|
||||||
|
EXPECT_TRUE(value.has_value());
|
||||||
|
EXPECT_EQ(read, encoded.size());
|
||||||
|
EXPECT_EQ(value.value(), std::numeric_limits<int8_t>::max());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataDecoding, uint16Min) {
|
||||||
|
std::vector<uint8_t> encoded = { 0b10000000 };
|
||||||
|
int read = 0;
|
||||||
|
auto value = MMO::Networking::decodeValue<uint16_t>(encoded, read);
|
||||||
|
EXPECT_TRUE(value.has_value());
|
||||||
|
EXPECT_EQ(read, encoded.size());
|
||||||
|
EXPECT_EQ(value.value(), std::numeric_limits<uint16_t>::min());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataDecoding, uint16Max) {
|
||||||
|
std::vector<uint8_t> encoded = { 0b01111111, 0b01111111, 0b10000011 };
|
||||||
|
int read = 0;
|
||||||
|
auto value = MMO::Networking::decodeValue<uint16_t>(encoded, read);
|
||||||
|
EXPECT_TRUE(value.has_value());
|
||||||
|
EXPECT_EQ(read, encoded.size());
|
||||||
|
EXPECT_EQ(value.value(), std::numeric_limits<uint16_t>::max());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataDecoding, int16Min) {
|
||||||
|
std::vector<uint8_t> encoded = { 0b01000000, 0, 0b11111100 };
|
||||||
|
int read = 0;
|
||||||
|
auto value = MMO::Networking::decodeValue<int16_t>(encoded, read);
|
||||||
|
EXPECT_TRUE(value.has_value());
|
||||||
|
EXPECT_EQ(read, encoded.size());
|
||||||
|
EXPECT_EQ(value.value(), std::numeric_limits<int16_t>::min());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataDecoding, int16Max) {
|
||||||
|
std::vector<uint8_t> encoded = { 0b00111111, 0b01111111, 0b10000011 };
|
||||||
|
int read = 0;
|
||||||
|
auto value = MMO::Networking::decodeValue<int16_t>(encoded, read);
|
||||||
|
EXPECT_TRUE(value.has_value());
|
||||||
|
EXPECT_EQ(read, encoded.size());
|
||||||
|
EXPECT_EQ(value.value(), std::numeric_limits<int16_t>::max());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataDecoding, uint32Min) {
|
||||||
|
std::vector<uint8_t> encoded = { 0b10000000 };
|
||||||
|
int read = 0;
|
||||||
|
auto value = MMO::Networking::decodeValue<uint32_t>(encoded, read);
|
||||||
|
EXPECT_TRUE(value.has_value());
|
||||||
|
EXPECT_EQ(read, encoded.size());
|
||||||
|
EXPECT_EQ(value.value(), std::numeric_limits<uint32_t>::min());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataDecoding, uint32Max) {
|
||||||
|
std::vector<uint8_t> encoded = { 0b01111111, 0b01111111, 0b01111111, 0b01111111, 0b10001111 };
|
||||||
|
int read = 0;
|
||||||
|
auto value = MMO::Networking::decodeValue<uint32_t>(encoded, read);
|
||||||
|
EXPECT_TRUE(value.has_value());
|
||||||
|
EXPECT_EQ(read, encoded.size());
|
||||||
|
EXPECT_EQ(value.value(), std::numeric_limits<uint32_t>::max());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataDecoding, int32Min) {
|
||||||
|
std::vector<uint8_t> encoded = { 0b01000000, 0, 0, 0, 0b11110000 };
|
||||||
|
int read = 0;
|
||||||
|
auto value = MMO::Networking::decodeValue<int32_t, int>(encoded, read);
|
||||||
|
EXPECT_TRUE(value.has_value());
|
||||||
|
EXPECT_EQ(read, encoded.size());
|
||||||
|
EXPECT_EQ(value.value(), std::numeric_limits<int32_t>::min());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataDecoding, int32Max) {
|
||||||
|
std::vector<uint8_t> encoded = { 0b00111111, 0b01111111, 0b01111111, 0b01111111, 0b10001111 };
|
||||||
|
int read = 0;
|
||||||
|
auto value = MMO::Networking::decodeValue<int32_t, int>(encoded, read);
|
||||||
|
EXPECT_TRUE(value.has_value());
|
||||||
|
EXPECT_EQ(read, encoded.size());
|
||||||
|
EXPECT_EQ(value.value(), std::numeric_limits<int32_t>::max());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataDecoding, uint64Min) {
|
||||||
|
std::vector<uint8_t> encoded = { 0b10000000 };
|
||||||
|
int read = 0;
|
||||||
|
auto value = MMO::Networking::decodeValue<uint64_t>(encoded, read);
|
||||||
|
EXPECT_TRUE(value.has_value());
|
||||||
|
EXPECT_EQ(read, encoded.size());
|
||||||
|
EXPECT_EQ(value.value(), std::numeric_limits<uint64_t>::min());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataDecoding, uint64Max) {
|
||||||
|
std::vector<uint8_t> encoded = {
|
||||||
|
0b01111111,
|
||||||
|
0b01111111,
|
||||||
|
0b01111111,
|
||||||
|
0b01111111,
|
||||||
|
0b01111111,
|
||||||
|
0b01111111,
|
||||||
|
0b01111111,
|
||||||
|
0b01111111,
|
||||||
|
0b01111111,
|
||||||
|
0b10000001
|
||||||
|
};
|
||||||
|
int read = 0;
|
||||||
|
auto value = MMO::Networking::decodeValue<uint64_t>(encoded, read);
|
||||||
|
EXPECT_TRUE(value.has_value());
|
||||||
|
EXPECT_EQ(read, encoded.size());
|
||||||
|
EXPECT_EQ(value.value(), std::numeric_limits<uint64_t>::max());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataDecoding, int64Min) {
|
||||||
|
std::vector<uint8_t> encoded = {
|
||||||
|
0b01000000,
|
||||||
|
0b00000000,
|
||||||
|
0b00000000,
|
||||||
|
0b00000000,
|
||||||
|
0b00000000,
|
||||||
|
0b00000000,
|
||||||
|
0b00000000,
|
||||||
|
0b00000000,
|
||||||
|
0b00000000,
|
||||||
|
0b11111110
|
||||||
|
};
|
||||||
|
int read = 0;
|
||||||
|
auto value = MMO::Networking::decodeValue<int64_t>(encoded, read);
|
||||||
|
EXPECT_TRUE(value.has_value());
|
||||||
|
EXPECT_EQ(read, encoded.size());
|
||||||
|
EXPECT_EQ(value.value(), std::numeric_limits<int64_t>::min());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataDecoding, int64Max) {
|
||||||
|
std::vector<uint8_t> encoded = {
|
||||||
|
0b00111111,
|
||||||
|
0b01111111,
|
||||||
|
0b01111111,
|
||||||
|
0b01111111,
|
||||||
|
0b01111111,
|
||||||
|
0b01111111,
|
||||||
|
0b01111111,
|
||||||
|
0b01111111,
|
||||||
|
0b01111111,
|
||||||
|
0b10000001
|
||||||
|
};
|
||||||
|
int read = 0;
|
||||||
|
auto value = MMO::Networking::decodeValue<int64_t>(encoded, read);
|
||||||
|
EXPECT_TRUE(value.has_value());
|
||||||
|
EXPECT_EQ(read, encoded.size());
|
||||||
|
EXPECT_EQ(value.value(), std::numeric_limits<int64_t>::max());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataDecoding, float0) {
|
||||||
|
std::vector<uint8_t> encoded = { 0b11111111 };
|
||||||
|
int read = 0;
|
||||||
|
auto value = MMO::Networking::decodeValue<float>(encoded, read);
|
||||||
|
EXPECT_TRUE(value.has_value());
|
||||||
|
EXPECT_EQ(read, encoded.size());
|
||||||
|
EXPECT_EQ(value.value(), 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataDecoding, floatMin) {
|
||||||
|
std::vector<uint8_t> encoded = {
|
||||||
|
0b01000010,
|
||||||
|
0b11111110,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0b10001000
|
||||||
|
};
|
||||||
|
int read = 0;
|
||||||
|
auto value = MMO::Networking::decodeValue<float>(encoded, read);
|
||||||
|
EXPECT_TRUE(value.has_value());
|
||||||
|
EXPECT_EQ(read, encoded.size());
|
||||||
|
EXPECT_EQ(value.value(), std::numeric_limits<float>::min());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataDecoding, floatLowest) {
|
||||||
|
std::vector<uint8_t> encoded = {
|
||||||
|
0b00111111,
|
||||||
|
0b10000001,
|
||||||
|
0b01000001,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0b11110000
|
||||||
|
};
|
||||||
|
int read = 0;
|
||||||
|
auto value = MMO::Networking::decodeValue<float>(encoded, read);
|
||||||
|
EXPECT_TRUE(value.has_value());
|
||||||
|
EXPECT_EQ(read, encoded.size());
|
||||||
|
EXPECT_EQ(value.value(), std::numeric_limits<float>::lowest());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataDecoding, floatMax) {
|
||||||
|
std::vector<uint8_t> encoded = {
|
||||||
|
0b00111111,
|
||||||
|
0b10000001,
|
||||||
|
0b00111111,
|
||||||
|
0b01111111,
|
||||||
|
0b01111111,
|
||||||
|
0b10001111
|
||||||
|
};
|
||||||
|
int read = 0;
|
||||||
|
auto value = MMO::Networking::decodeValue<float>(encoded, read);
|
||||||
|
EXPECT_TRUE(value.has_value());
|
||||||
|
EXPECT_EQ(read, encoded.size());
|
||||||
|
EXPECT_EQ(value.value(), std::numeric_limits<float>::max());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataDecoding, floatNan) {
|
||||||
|
std::vector<uint8_t> encoded = {
|
||||||
|
0b01000000,
|
||||||
|
0b11111110,
|
||||||
|
0b10000010
|
||||||
|
};
|
||||||
|
int read = 0;
|
||||||
|
auto value = MMO::Networking::decodeValue<float>(encoded, read);
|
||||||
|
EXPECT_TRUE(value.has_value());
|
||||||
|
EXPECT_EQ(read, encoded.size());
|
||||||
|
EXPECT_TRUE(std::isnan(value.value()));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataDecoding, floatInfPositive) {
|
||||||
|
std::vector<uint8_t> encoded = {
|
||||||
|
0b01000000,
|
||||||
|
0b11111110,
|
||||||
|
0b10000001
|
||||||
|
};
|
||||||
|
int read = 0;
|
||||||
|
auto value = MMO::Networking::decodeValue<float>(encoded, read);
|
||||||
|
EXPECT_TRUE(value.has_value());
|
||||||
|
EXPECT_EQ(read, encoded.size());
|
||||||
|
EXPECT_EQ(value.value(), std::numeric_limits<float>::infinity());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataDecoding, floatInfNegative) {
|
||||||
|
std::vector<uint8_t> encoded = {
|
||||||
|
0b01000000,
|
||||||
|
0b11111110,
|
||||||
|
0b10000000
|
||||||
|
};
|
||||||
|
int read = 0;
|
||||||
|
auto value = MMO::Networking::decodeValue<float>(encoded, read);
|
||||||
|
EXPECT_TRUE(value.has_value());
|
||||||
|
EXPECT_EQ(read, encoded.size());
|
||||||
|
EXPECT_EQ(value.value(), -std::numeric_limits<float>::infinity());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataDecoding, double0) {
|
||||||
|
std::vector<uint8_t> encoded = { 0b10000000 };
|
||||||
|
int read = 0;
|
||||||
|
auto value = MMO::Networking::decodeValue<double>(encoded, read);
|
||||||
|
EXPECT_TRUE(value.has_value());
|
||||||
|
EXPECT_EQ(read, encoded.size());
|
||||||
|
EXPECT_EQ(value.value(), 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataDecoding, doubleMin) {
|
||||||
|
std::vector<uint8_t> encoded = { 0b01000011, 0b11110000, 0, 0, 0, 0, 0, 0, 0, 0b10010000 };
|
||||||
|
int read = 0;
|
||||||
|
auto value = MMO::Networking::decodeValue<double>(encoded, read);
|
||||||
|
EXPECT_TRUE(value.has_value());
|
||||||
|
EXPECT_EQ(read, encoded.size());
|
||||||
|
EXPECT_EQ(value.value(), std::numeric_limits<double>::min());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataDecoding, doubleLowest) {
|
||||||
|
std::vector<uint8_t> encoded = { 0, 0b10010000, 0b01000001, 0, 0, 0, 0, 0, 0, 0b11100000 };
|
||||||
|
int read = 0;
|
||||||
|
auto value = MMO::Networking::decodeValue<double>(encoded, read);
|
||||||
|
EXPECT_TRUE(value.has_value());
|
||||||
|
EXPECT_EQ(read, encoded.size());
|
||||||
|
EXPECT_EQ(value.value(), std::numeric_limits<double>::lowest());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataDecoding, doubleMax) {
|
||||||
|
std::vector<uint8_t> encoded = {
|
||||||
|
0,
|
||||||
|
0b10010000,
|
||||||
|
0b00111111,
|
||||||
|
0b01111111,
|
||||||
|
0b01111111,
|
||||||
|
0b01111111,
|
||||||
|
0b01111111,
|
||||||
|
0b01111111,
|
||||||
|
0b01111111,
|
||||||
|
0b10011111
|
||||||
|
};
|
||||||
|
int read = 0;
|
||||||
|
auto value = MMO::Networking::decodeValue<double>(encoded, read);
|
||||||
|
EXPECT_TRUE(value.has_value());
|
||||||
|
EXPECT_EQ(read, encoded.size());
|
||||||
|
EXPECT_EQ(value.value(), std::numeric_limits<double>::max());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataDecoding, doubleNan) {
|
||||||
|
std::vector<uint8_t> encoded = {
|
||||||
|
0b01000000,
|
||||||
|
0b11100000,
|
||||||
|
0b10000010
|
||||||
|
};
|
||||||
|
int read = 0;
|
||||||
|
auto value = MMO::Networking::decodeValue<double>(encoded, read);
|
||||||
|
EXPECT_TRUE(value.has_value());
|
||||||
|
EXPECT_EQ(read, encoded.size());
|
||||||
|
EXPECT_TRUE(std::isnan(value.value()));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataDecoding, doubleInfPositive) {
|
||||||
|
std::vector<uint8_t> encoded = {
|
||||||
|
0b01000000,
|
||||||
|
0b11100000,
|
||||||
|
0b10000001
|
||||||
|
};
|
||||||
|
int read = 0;
|
||||||
|
auto value = MMO::Networking::decodeValue<double>(encoded, read);
|
||||||
|
EXPECT_TRUE(value.has_value());
|
||||||
|
EXPECT_EQ(read, encoded.size());
|
||||||
|
EXPECT_EQ(value.value(), std::numeric_limits<double>::infinity());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataDecoding, doubleInfNegative) {
|
||||||
|
std::vector<uint8_t> encoded = {
|
||||||
|
0b01000000,
|
||||||
|
0b11100000,
|
||||||
|
0b10000000
|
||||||
|
};
|
||||||
|
int read = 0;
|
||||||
|
auto value = MMO::Networking::decodeValue<double>(encoded, read);
|
||||||
|
EXPECT_TRUE(value.has_value());
|
||||||
|
EXPECT_EQ(read, encoded.size());
|
||||||
|
EXPECT_EQ(value.value(), -std::numeric_limits<double>::infinity());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<std::integral T>
|
||||||
|
T randomValue() {
|
||||||
|
std::random_device rd;
|
||||||
|
std::mt19937_64 gen(rd());
|
||||||
|
|
||||||
|
std::uniform_int_distribution<std::conditional_t<std::signed_integral<T>, int64_t, uint64_t>> dis(
|
||||||
|
std::numeric_limits<T>::min(), std::numeric_limits<T>::max());
|
||||||
|
return static_cast<T>(dis(gen));
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto valueAttempts = 100;
|
||||||
|
|
||||||
|
TEST(DataEnDecoding, uint8) {
|
||||||
|
for (int i = 0; i < valueAttempts; ++i) {
|
||||||
|
const auto value = randomValue<uint8_t>();
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
MMO::Networking::encodeValue(value, encoded);
|
||||||
|
auto decoded = MMO::Networking::decodeValue<uint8_t>(encoded);
|
||||||
|
EXPECT_TRUE(decoded.has_value());
|
||||||
|
EXPECT_EQ(decoded.value(), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataEnDecoding, int8) {
|
||||||
|
for (int i = 0; i < valueAttempts; ++i) {
|
||||||
|
const auto value = randomValue<int8_t>();
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
MMO::Networking::encodeValue(value, encoded);
|
||||||
|
auto decoded = MMO::Networking::decodeValue<int8_t>(encoded);
|
||||||
|
EXPECT_TRUE(decoded.has_value());
|
||||||
|
EXPECT_EQ(decoded.value(), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataEnDecoding, uint16) {
|
||||||
|
for (int i = 0; i < valueAttempts; ++i) {
|
||||||
|
const auto value = randomValue<uint16_t>();
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
MMO::Networking::encodeValue(value, encoded);
|
||||||
|
auto decoded = MMO::Networking::decodeValue<uint16_t>(encoded);
|
||||||
|
EXPECT_TRUE(decoded.has_value());
|
||||||
|
EXPECT_EQ(decoded.value(), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataEnDecoding, int16) {
|
||||||
|
for (int i = 0; i < valueAttempts; ++i) {
|
||||||
|
const auto value = randomValue<int16_t>();
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
MMO::Networking::encodeValue(value, encoded);
|
||||||
|
auto decoded = MMO::Networking::decodeValue<int16_t>(encoded);
|
||||||
|
EXPECT_TRUE(decoded.has_value());
|
||||||
|
EXPECT_EQ(decoded.value(), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataEnDecoding, uint32) {
|
||||||
|
for (int i = 0; i < valueAttempts; ++i) {
|
||||||
|
const auto value = randomValue<uint32_t>();
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
MMO::Networking::encodeValue(value, encoded);
|
||||||
|
auto decoded = MMO::Networking::decodeValue<uint32_t>(encoded);
|
||||||
|
EXPECT_TRUE(decoded.has_value());
|
||||||
|
EXPECT_EQ(decoded.value(), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataEnDecoding, int32) {
|
||||||
|
for (int i = 0; i < valueAttempts; ++i) {
|
||||||
|
const auto value = randomValue<int32_t>();
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
MMO::Networking::encodeValue(value, encoded);
|
||||||
|
auto decoded = MMO::Networking::decodeValue<int32_t>(encoded);
|
||||||
|
EXPECT_TRUE(decoded.has_value());
|
||||||
|
EXPECT_EQ(decoded.value(), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataEnDecoding, uint64) {
|
||||||
|
for (int i = 0; i < valueAttempts; ++i) {
|
||||||
|
const auto value = randomValue<uint64_t>();
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
MMO::Networking::encodeValue(value, encoded);
|
||||||
|
auto decoded = MMO::Networking::decodeValue<uint64_t>(encoded);
|
||||||
|
EXPECT_TRUE(decoded.has_value());
|
||||||
|
EXPECT_EQ(decoded.value(), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataEnDecoding, int64) {
|
||||||
|
for (int i = 0; i < valueAttempts; ++i) {
|
||||||
|
const auto value = randomValue<int64_t>();
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
MMO::Networking::encodeValue(value, encoded);
|
||||||
|
auto decoded = MMO::Networking::decodeValue<int64_t>(encoded);
|
||||||
|
EXPECT_TRUE(decoded.has_value());
|
||||||
|
EXPECT_EQ(decoded.value(), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataEnDecoding, floats) {
|
||||||
|
for (int i = 0; i < valueAttempts; ++i) {
|
||||||
|
const auto value = static_cast<float>(randomValue<int64_t>()) / (static_cast<float>(randomValue<uint32_t>()) +
|
||||||
|
1);
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
MMO::Networking::encodeValue(value, encoded);
|
||||||
|
auto decoded = MMO::Networking::decodeValue<float>(encoded);
|
||||||
|
EXPECT_TRUE(decoded.has_value());
|
||||||
|
EXPECT_EQ(decoded.value(), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(DataEnDecoding, float0) {
|
||||||
|
const auto value = 0.0f;
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
MMO::Networking::encodeValue(value, encoded);
|
||||||
|
auto decoded = MMO::Networking::decodeValue<float>(encoded);
|
||||||
|
EXPECT_TRUE(decoded.has_value());
|
||||||
|
EXPECT_EQ(decoded.value(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(DataEnDecoding, floatMin) {
|
||||||
|
const auto value = std::numeric_limits<float>::min();
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
MMO::Networking::encodeValue(value, encoded);
|
||||||
|
auto decoded = MMO::Networking::decodeValue<float>(encoded);
|
||||||
|
EXPECT_TRUE(decoded.has_value());
|
||||||
|
EXPECT_EQ(decoded.value(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(DataEnDecoding, floatLowest) {
|
||||||
|
const auto value = std::numeric_limits<float>::lowest();
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
MMO::Networking::encodeValue(value, encoded);
|
||||||
|
auto decoded = MMO::Networking::decodeValue<float>(encoded);
|
||||||
|
EXPECT_TRUE(decoded.has_value());
|
||||||
|
EXPECT_EQ(decoded.value(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataEnDecoding, floatMax) {
|
||||||
|
const auto value = std::numeric_limits<float>::min();
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
MMO::Networking::encodeValue(value, encoded);
|
||||||
|
auto decoded = MMO::Networking::decodeValue<float>(encoded);
|
||||||
|
EXPECT_TRUE(decoded.has_value());
|
||||||
|
EXPECT_EQ(decoded.value(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(DataEnDecoding, floatNan) {
|
||||||
|
const auto value = std::numeric_limits<float>::quiet_NaN();
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
MMO::Networking::encodeValue(value, encoded);
|
||||||
|
auto decoded = MMO::Networking::decodeValue<float>(encoded);
|
||||||
|
EXPECT_TRUE(decoded.has_value());
|
||||||
|
EXPECT_TRUE(std::isnan(decoded.value()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(DataEnDecoding, floatInfPositive) {
|
||||||
|
const auto value = std::numeric_limits<float>::infinity();
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
MMO::Networking::encodeValue(value, encoded);
|
||||||
|
auto decoded = MMO::Networking::decodeValue<float>(encoded);
|
||||||
|
EXPECT_TRUE(decoded.has_value());
|
||||||
|
EXPECT_EQ(decoded.value(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(DataEnDecoding, floatInfNegative) {
|
||||||
|
const auto value = -std::numeric_limits<float>::infinity();
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
MMO::Networking::encodeValue(value, encoded);
|
||||||
|
auto decoded = MMO::Networking::decodeValue<float>(encoded);
|
||||||
|
EXPECT_TRUE(decoded.has_value());
|
||||||
|
EXPECT_EQ(decoded.value(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataEnDecoding, doubles) {
|
||||||
|
for (int i = 0; i < valueAttempts; ++i) {
|
||||||
|
const auto value = static_cast<double>(randomValue<int64_t>()) / (static_cast<double>(randomValue<uint32_t>()) +
|
||||||
|
1);
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
MMO::Networking::encodeValue(value, encoded);
|
||||||
|
auto decoded = MMO::Networking::decodeValue<double>(encoded);
|
||||||
|
EXPECT_TRUE(decoded.has_value());
|
||||||
|
EXPECT_EQ(decoded.value(), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(DataEnDecoding, double0) {
|
||||||
|
const auto value = 0.0;
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
MMO::Networking::encodeValue(value, encoded);
|
||||||
|
auto decoded = MMO::Networking::decodeValue<double>(encoded);
|
||||||
|
EXPECT_TRUE(decoded.has_value());
|
||||||
|
EXPECT_EQ(decoded.value(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(DataEnDecoding, doubleMin) {
|
||||||
|
const auto value = std::numeric_limits<double>::min();
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
MMO::Networking::encodeValue(value, encoded);
|
||||||
|
auto decoded = MMO::Networking::decodeValue<double>(encoded);
|
||||||
|
EXPECT_TRUE(decoded.has_value());
|
||||||
|
EXPECT_EQ(decoded.value(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(DataEnDecoding, doubleLowest) {
|
||||||
|
const auto value = std::numeric_limits<double>::lowest();
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
MMO::Networking::encodeValue(value, encoded);
|
||||||
|
auto decoded = MMO::Networking::decodeValue<double>(encoded);
|
||||||
|
EXPECT_TRUE(decoded.has_value());
|
||||||
|
EXPECT_EQ(decoded.value(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DataEnDecoding, doubleMax) {
|
||||||
|
const auto value = std::numeric_limits<double>::min();
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
MMO::Networking::encodeValue(value, encoded);
|
||||||
|
auto decoded = MMO::Networking::decodeValue<double>(encoded);
|
||||||
|
EXPECT_TRUE(decoded.has_value());
|
||||||
|
EXPECT_EQ(decoded.value(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(DataEnDecoding, doubleNan) {
|
||||||
|
const auto value = std::numeric_limits<double>::quiet_NaN();
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
MMO::Networking::encodeValue(value, encoded);
|
||||||
|
auto decoded = MMO::Networking::decodeValue<double>(encoded);
|
||||||
|
EXPECT_TRUE(decoded.has_value());
|
||||||
|
EXPECT_TRUE(std::isnan(decoded.value()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(DataEnDecoding, doubleInfPositive) {
|
||||||
|
const auto value = std::numeric_limits<double>::infinity();
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
MMO::Networking::encodeValue(value, encoded);
|
||||||
|
auto decoded = MMO::Networking::decodeValue<double>(encoded);
|
||||||
|
EXPECT_TRUE(decoded.has_value());
|
||||||
|
EXPECT_EQ(decoded.value(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(DataEnDecoding, doubleInfNegative) {
|
||||||
|
const auto value = -std::numeric_limits<double>::infinity();
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
MMO::Networking::encodeValue(value, encoded);
|
||||||
|
auto decoded = MMO::Networking::decodeValue<double>(encoded);
|
||||||
|
EXPECT_TRUE(decoded.has_value());
|
||||||
|
EXPECT_EQ(decoded.value(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(DataEnDecoding, arrayOfValue) {
|
||||||
|
const std::vector<int32_t> value{
|
||||||
|
randomValue<int32_t>(),
|
||||||
|
randomValue<int32_t>(),
|
||||||
|
randomValue<int32_t>(),
|
||||||
|
randomValue<int32_t>(),
|
||||||
|
randomValue<int32_t>(),
|
||||||
|
randomValue<int32_t>(),
|
||||||
|
};
|
||||||
|
std::vector<uint8_t> encoded;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
MMO::Networking::encodeValue(value, encoded);
|
||||||
|
auto decoded = MMO::Networking::decodeValue<std::vector<int32_t>>(encoded);
|
||||||
|
EXPECT_TRUE(decoded.has_value());
|
||||||
|
EXPECT_EQ(decoded.value(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MultiWriteTest, outOfOrderIsInOrder) {
|
||||||
|
const std::vector<int32_t> value{
|
||||||
|
randomValue<int32_t>(),
|
||||||
|
randomValue<int32_t>(),
|
||||||
|
randomValue<int32_t>(),
|
||||||
|
randomValue<int32_t>(),
|
||||||
|
randomValue<int32_t>(),
|
||||||
|
randomValue<int32_t>(),
|
||||||
|
};
|
||||||
|
std::vector<uint8_t> encodedInOrder;
|
||||||
|
std::vector<uint8_t> encodedOutOrder;
|
||||||
|
// Length is variable so we can't predict it (easily, there is math for it but I've lost it)
|
||||||
|
for (const auto& v : value) {
|
||||||
|
MMO::Networking::encodeValue(v, encodedInOrder);
|
||||||
|
}
|
||||||
|
for (int i = static_cast<int>(value.size()) - 1; i >= 0; --i) {
|
||||||
|
MMO::Networking::encodeValue(value[i], encodedOutOrder, 0);
|
||||||
|
}
|
||||||
|
EXPECT_EQ(encodedInOrder, encodedOutOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(Packet, CreateFrom) {
|
||||||
|
MMO::Networking::Packet packet;
|
||||||
|
|
||||||
|
packet.write<uint32_t>(0xDEADBEEF);
|
||||||
|
packet.write<int32_t>(-1);
|
||||||
|
|
||||||
|
|
||||||
|
auto packetData = packet.finalizePacket();
|
||||||
|
|
||||||
|
auto packet2Exp = MMO::Networking::Packet::createPacket(packetData);
|
||||||
|
|
||||||
|
EXPECT_TRUE(packet2Exp.has_value());
|
||||||
|
auto packet2 = packet2Exp.value();
|
||||||
|
{
|
||||||
|
auto readValue = packet2.read<uint32_t>();
|
||||||
|
EXPECT_TRUE(readValue.has_value());
|
||||||
|
EXPECT_EQ(*readValue, 0xDEADBEEF);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto readValue = packet2.read<int32_t>();
|
||||||
|
EXPECT_TRUE(readValue.has_value());
|
||||||
|
EXPECT_EQ(*readValue, -1);
|
||||||
|
}
|
||||||
|
}
|
14
common/packet/tests/sanitizers.cpp
Normal file
14
common/packet/tests/sanitizers.cpp
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
void __ubsan_on_report() {
|
||||||
|
FAIL() << "Encountered an undefined behavior sanitizer error";
|
||||||
|
}
|
||||||
|
void __asan_on_error() {
|
||||||
|
FAIL() << "Encountered an address sanitizer error";
|
||||||
|
}
|
||||||
|
void __tsan_on_report() {
|
||||||
|
FAIL() << "Encountered a thread sanitizer error";
|
||||||
|
}
|
||||||
|
}
|
32
vcpkg.json
Normal file
32
vcpkg.json
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"name": "mmo",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "MMOServers",
|
||||||
|
"builtin-baseline": "4a9af5e5efcb24d0c8fcdf82dacf3b2b780b99f8",
|
||||||
|
"dependencies": [
|
||||||
|
{
|
||||||
|
"name": "msquic",
|
||||||
|
"features": [
|
||||||
|
"0-rtt"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"gtest",
|
||||||
|
{
|
||||||
|
"name": "openssl",
|
||||||
|
"features": [
|
||||||
|
"tools"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"cryptopp",
|
||||||
|
"cli11",
|
||||||
|
{
|
||||||
|
"name": "redis-plus-plus",
|
||||||
|
"features": [
|
||||||
|
"cxx17",
|
||||||
|
"tls"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"yyjson",
|
||||||
|
"spdlog"
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user