Compare commits

...

100 Commits

Author SHA1 Message Date
WolverinDEV
f127894110 Some minor updates 2021-07-23 14:59:43 +02:00
WolverinDEV
9f03f33a23 Fixed a query rename bug 2021-05-26 11:49:07 +02:00
WolverinDEV
b155bf8d5f Fixed a client disconnect bug 2021-05-19 11:42:09 +02:00
WolverinDEV
616149d5dd Fixed some bugs 2021-05-09 18:28:26 +02:00
WolverinDEV
d2154a8541 Merge remote-tracking branch 'origin/1.4.10' into 1.4.10 2021-05-03 10:42:39 +02:00
WolverinDEV
f48b904935 Fixed error spam and a memory leak 2021-05-03 10:42:03 +02:00
WolverinDEV
857ec0b05d Fixed build for linux 2021-05-01 22:43:03 +00:00
WolverinDEV
c7985c4dd1 Fixed permissions 2021-04-28 18:43:30 +02:00
WolverinDEV
6ad9f7d3f0 Properly counting group name lengths and fixed a crash on server create when the group creation process failed 2021-04-28 18:31:59 +02:00
WolverinDEV
bc8f314623 Some minor changes 2021-04-22 00:14:34 +02:00
WolverinDEV
f008d3a711 Starting attempt to implement the new rust time transfer server 2021-04-21 13:19:45 +02:00
WolverinDEV
f4aaec6481 Fixed compilation for windows and hide task executor impl details 2021-04-19 19:29:46 +02:00
WolverinDEV
444dea13c5 Fixed missing generation estimator reset within the packet decoder 2021-04-19 19:11:43 +02:00
WolverinDEV
7a7a24ee2c Fixed recurring tasks sometimes getting totally dropped when canceling a recurring task 2021-04-18 21:20:11 +02:00
WolverinDEV
a23393b135 Fixed the permission definitions 2021-04-17 13:12:13 +02:00
WolverinDEV
1eaa9bb371 Reworked the voice IP part 2021-04-15 17:14:46 +02:00
WolverinDEV
0726cd6c95 Using one global event loop for the query and web client 2021-04-15 12:54:52 +02:00
WolverinDEV
2747c67f44 Merge remote-tracking branch 'origin/1.4.10' into 1.4.10
# Conflicts:
#	CMakeLists.txt
2021-04-15 03:28:52 +02:00
WolverinDEV
0a73e4c10c Heavily improving the clients join and leave performance 2021-04-15 03:28:08 +02:00
WolverinDEV
88158742e8 Some general code cleanup 2021-04-14 23:12:50 +02:00
WolverinDEV
393bc621c0 Fixed the query client view power 2021-04-14 16:00:02 +02:00
WolverinDEV
8fb93d1d9d Some code refactoring 2021-04-14 14:57:03 +02:00
WolverinDEV
222129b594 Fixed the ed25519 include path and using proper c include 2021-03-27 14:37:46 +01:00
WolverinDEV
1a14882632 Fixed compilation for windows 2021-03-27 14:35:53 +01:00
WolverinDEV
2aba908be6 Only using the static non debug runtime library 2021-03-27 14:24:49 +01:00
WolverinDEV
8dde5b1c23 SOme updates 2021-03-21 23:51:36 +01:00
WolverinDEV
eef0144e77 Some more work for the new group manager 2021-03-07 19:17:19 +01:00
WolverinDEV
26981b95f7 Fixed some compile errors 2021-03-01 14:50:57 +01:00
WolverinDEV
206e0052d1 Using property wrapper for all property accesses 2021-03-01 14:37:02 +01:00
WolverinDEV
465975e9ca Reworked the property system and fixed a crash 2021-03-01 14:16:43 +01:00
WolverinDEV
b26132fa54 Temporary stashing group changes 2021-02-28 19:03:40 +01:00
WolverinDEV
71443bc4d3 Introduced the new group manager 2021-02-26 21:32:56 +01:00
WolverinDEV
799bf8d26b Removed the whole TS3 WebList feature 2021-02-25 11:54:46 +01:00
WolverinDEV
7ff7d01cd3 Updates for the 1.5.1 version 2021-02-25 11:13:09 +01:00
WolverinDEV
dd8ea1c8d9 fixed the task executor 2021-02-24 20:41:26 +01:00
WolverinDEV
539d94e724 Adding support to get the last inserted row 2021-02-22 18:55:35 +01:00
WolverinDEV
6e99fc1ab4 Using tasks instead of inlining certain client updates 2021-02-21 21:56:51 +01:00
WolverinDEV
84a08c469b Fixed an internal command handler hangup per client 2021-02-14 20:36:55 +01:00
WolverinDEV
b785cd52ba Using __x86_64__ 2021-02-12 14:56:25 +01:00
WolverinDEV
d9e2462848 Fixed long long unsigned int 2021-02-12 14:51:43 +01:00
WolverinDEV
e58b123f4a Fixed compile errors 2021-02-12 14:44:30 +01:00
WolverinDEV
7c82007b4e Improved packet allocation 2021-02-07 18:14:14 +01:00
WolverinDEV
261a237291 Merge remote-tracking branch 'origin/1.4.10' into 1.4.10 2021-02-07 18:05:09 +01:00
WolverinDEV
2aed7708ee Adding a flag to the packet decoder whatever we're on the server or client side 2021-02-07 18:04:56 +01:00
WolverinDEV
075ac4cb27 Fixed client compile 2021-02-07 15:04:01 +01:00
WolverinDEV
16aca3a6bf Fixed compile for Windows 2021-02-07 12:33:56 +01:00
WolverinDEV
4367736c77 Moved some voice udp utils to the shared teaspeak part 2021-02-07 12:27:27 +01:00
WolverinDEV
f6f90f1196 Fixed windows standalone build 2021-02-06 23:18:29 +01:00
WolverinDEV
1c448ed41c Fixed windows 2021-02-06 22:35:43 +01:00
WolverinDEV
6e2d901bf6 Made all non required libraries for the client optional 2021-02-06 21:00:05 +01:00
WolverinDEV
ac48d30696 Some smaller changes and code cleanups 2021-02-05 17:20:38 +01:00
WolverinDEV
d213c0ade0 Using long long unsigned only on 64 bit 2021-02-05 14:23:29 +01:00
WolverinDEV
c0790984ee Some minor code refactors and fixed converter for 332bit builds 2021-02-05 14:20:10 +01:00
WolverinDEV
c1085c84cf Using uint32_t as reconnect value 2021-01-29 09:58:29 +01:00
WolverinDEV
0cd49a6a2b A lot of query reworking 2021-01-28 20:59:14 +01:00
WolverinDEV
eb77a7fefb Some minor changes 2020-12-29 18:26:08 +01:00
WolverinDEV
12c2a1592d Fixed a crash related to the whisper system and some minor updates 2020-12-19 10:52:23 +01:00
WolverinDEV
d42dba7983 Fixed libnice 2020-12-12 20:37:13 +01:00
WolverinDEV
1ae3b59e14 Video now has to be manually activated in order to watch it 2020-12-10 13:33:08 +01:00
WolverinDEV
c8c6738569 Fixed missing permission 2020-12-08 23:48:08 +01:00
WolverinDEV
83023f2fa6 Fixed some minor speaking bugs 2020-12-05 21:50:01 +01:00
WolverinDEV
d2720897c4 Some minor and small fixes 2020-12-05 16:27:40 +01:00
WolverinDEV
38cebf23a1 b_channel_create_private REMOVAL 2020-12-05 10:11:19 +01:00
WolverinDEV
20f9295fbe Removing one permission 2020-12-05 10:09:33 +01:00
WolverinDEV
b271cdd6ff Added a new channel property 2020-12-03 10:49:20 +01:00
WolverinDEV
569b360a8e Fixed default channel conversation mode 2020-12-03 10:39:22 +01:00
WolverinDEV
3eedb7d772 Some minor fixes 2020-12-02 21:43:03 +01:00
WolverinDEV
2ba9288cc2 Fixed invalid property default value change 2020-12-01 10:11:50 +01:00
WolverinDEV
5b74992beb Updated the channel edit and create functions 2020-12-01 10:07:36 +01:00
WolverinDEV
2cc8a42ce7 Supporting voice whisper again 2020-11-28 11:09:24 +01:00
WolverinDEV
23db0edd22 Improved the code quality and TeaSpeak now builds with clang 2020-11-26 10:34:59 +01:00
WolverinDEV
7b51bcc5e8 Initial video commit 2020-11-07 13:17:45 +01:00
WolverinDEV
f5c643129e Updated the property list command and added a channel description only mode 2020-10-04 15:04:23 +02:00
WolverinDEV
0a960e4148 Some more updates 2020-09-06 21:00:24 +02:00
WolverinDEV
4d7fabe2ea Fixed MySQL 2020-08-22 21:34:34 +02:00
WolverinDEV
f80f629496 Fixing some bugs for 1.4.19b1 2020-08-18 22:03:05 +02:00
WolverinDEV
59ec412fea Adding a basic skillet to the new voice server - fixed some connection issues 2020-08-03 13:51:44 +02:00
WolverinDEV
c3188cd9e5 Updating the deploy algorithm 2020-07-31 17:35:11 +02:00
WolverinDEV
a634396845 First draft of the new snapshot system and database changes 2020-07-30 20:25:42 +02:00
WolverinDEV
1c6482262f Fixed some too long messages 2020-07-30 11:50:30 +02:00
WolverinDEV
143322575d Finalizing the voice connection cleanup 2020-07-29 22:53:36 +02:00
WolverinDEV
a15eb9d25c reworked the voice client connection part 1 2020-07-29 19:05:35 +02:00
WolverinDEV
b1f5620760 A lot of changes to 1.4.17 2020-07-28 18:36:30 +02:00
WolverinDEV
a4d7c90945 Fixed some basic stuff 2020-07-23 20:28:41 +02:00
WolverinDEV
7a3854843f Some minor bugfixes 2020-07-13 11:13:06 +02:00
WolverinDEV
809fa82b31 Added the action logging system 2020-06-28 14:01:11 +02:00
WolverinDEV
f11bee75c1 Some minor fixes and added the listfeaturesupport command 2020-06-16 12:57:18 +02:00
WolverinDEV
d772aa050c File transfer server now respects the port and host settings set via the config.yml 2020-06-13 01:08:46 +02:00
WolverinDEV
32dc9423c2 Appropriate linking some libraries 2020-06-11 13:24:05 +02:00
WolverinDEV
9b41ca5dca Some minor changes 2020-06-10 18:17:29 +02:00
WolverinDEV
246e57e69d Fixed YatQa rror 2020-05-19 09:51:52 +02:00
WolverinDEV
f404d5e1fa Some updates 2020-05-14 15:08:26 +02:00
WolverinDEV
a4febf7b5a A lot of file transfer updates 2020-05-13 11:32:01 +02:00
WolverinDEV
b60608ff94 A lot of updates 2020-05-07 21:28:11 +02:00
WolverinDEV
ee4c7540f0 Some updates 2020-04-28 18:27:46 +02:00
WolverinDEV
56e4ca45a9 Fixed some bugs 2020-04-26 19:41:40 +02:00
WolverinDEV
bcbff04979 Fixed the invalid packet splitting algorithm 2020-04-25 11:25:59 +02:00
WolverinDEV
5842bbe067 A lot of updates (Speed improvement) 2020-04-24 22:04:04 +02:00
WolverinDEV
2725c57f2e 1.4.14 ;) 2020-04-23 15:36:56 +02:00
WolverinDEV
0b06a87fa4 Removed the reuse port options 2020-04-18 12:56:00 +02:00
90 changed files with 5371 additions and 4746 deletions

View File

@ -2,16 +2,6 @@ cmake_minimum_required(VERSION 3.6)
project(TeaSpeak-Shared)
set(CMAKE_CXX_STANDARD 20)
if(NOT WIN32)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -Wall -Wno-reorder -Wno-sign-compare -fpermissive -ftemplate-depth=1000 ${MEMORY_DEBUG_FLAGS}")
set(CMAKE_INCLUDE_CURRENT_DIR ON)
else()
#For Windows
add_definitions(-D_SILENCE_CXX17_OLD_ALLOCATOR_MEMBERS_DEPRECATION_WARNING)
add_compile_options(/wd4996) #'std::result_of_t': warning STL4014: std::result_of and std::result_of_t are deprecated in C++17.
endif()
if(CMAKE_PLATFORM_INCLUDE AND NOT CMAKE_PLATFORM_INCLUDE STREQUAL "")
include(${CMAKE_PLATFORM_INCLUDE})
endif()
@ -25,47 +15,68 @@ include_directories(${TomCrypt_INCLUDE_DIR})
find_package(DataPipes REQUIRED)
include_directories(${DataPipes_INCLUDE_DIR})
# LibEvent fucks up the CMAKE_FIND_LIBRARY_SUFFIXES variable
if (NOT find_event)
function(find_event static)
if(static)
set(LIBEVENT_STATIC_LINK TRUE)
endif()
find_package(Libevent REQUIRED)
include_directories(${LIBEVENT_INCLUDE_DIRS})
endfunction()
endif ()
find_event(ON)
find_package(StringVariable REQUIRED)
include_directories(${StringVariable_INCLUDE_DIR})
find_package(Ed25519 REQUIRED)
include_directories(${ed25519_INCLUDE_DIR})
find_package(ThreadPool REQUIRED)
include_directories(${ThreadPool_INCLUDE_DIR})
if(WIN32)
add_definitions(-DWINDOWS) #Required for ThreadPool
set(TARGET_LIBRARIES)
set(FEATURE_LOGGING ON)
set(FEATURE_DATABASE ON)
find_package(ThreadPool)
if(ThreadPool_FOUND)
include_directories(${ThreadPool_INCLUDE_DIR})
if(WIN32)
add_definitions(-DWINDOWS) #Required for ThreadPool
endif()
else()
message("Missing ThreadPool. Skipping database support.")
set(FEATURE_DATABASE OFF)
endif()
find_package(StringVariable)
if(StringVariable_FOUND)
include_directories(${StringVariable_INCLUDE_DIR})
else()
message("Missing StringVariable. Disabling logging support")
set(FEATURE_LOGGING OFF)
endif()
find_package(spdlog)
if(spdlog_FOUND)
#Its a header only lib so we should be fine :)
list(APPEND TARGET_LIBRARIES spdlog::spdlog_header_only)
else()
message("Missing spdlog. Disabling logging support")
set(FEATURE_LOGGING OFF)
endif()
find_package(CXXTerminal)
if(CXXTerminal_FOUND)
add_definitions(-DHAVE_CXX_TERMINAL)
else()
message("Missing CXXTerminal. Disabling logging support")
set(FEATURE_LOGGING OFF)
endif()
find_package(spdlog REQUIRED)
link_libraries(spdlog::spdlog_header_only) #Its a header only lib so we should be fine :)
if(NOT TEASPEAK_SERVER)
add_definitions(-DNO_OPEN_SSL)
add_definitions(-D_HAS_STD_BYTE)
#FML
else()
find_package(CXXTerminal REQUIRED)
add_definitions(-DHAVE_CXX_TERMINAL)
add_definitions(-DHAVE_JSON)
set(HAVE_SQLITE3 ON)
set(HAVE_OPEN_SSL ON)
message("HAVE JSON!")
endif()
if(FEATURE_DATABASE)
if(NOT FEATURE_LOGGING)
message("Disabling database support because logging support is omitted")
set(FEATURE_DATABASE OFF)
endif()
endif()
if (MSVC)
add_definitions(-D_SILENCE_CXX17_OLD_ALLOCATOR_MEMBERS_DEPRECATION_WARNING)
add_compile_options(/wd4996) #'std::result_of_t': warning STL4014: std::result_of and std::result_of_t are deprecated in C++17.
set(CompilerFlags
CMAKE_CXX_FLAGS
CMAKE_CXX_FLAGS_DEBUG
@ -77,36 +88,26 @@ if (MSVC)
CMAKE_C_FLAGS_RELEASE
)
foreach(CompilerFlag ${CompilerFlags})
string(REPLACE "/MD" "/MT" ${CompilerFlag} "${${CompilerFlag}}")
string(REGEX REPLACE "/M[DT]d?" "/MT" ${CompilerFlag} "${${CompilerFlag}}")
endforeach()
add_compile_options("/EHsc") #We require exception handling
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -Wall -Wno-reorder -Wno-sign-compare -fpermissive -ftemplate-depth=1000 ${MEMORY_DEBUG_FLAGS}")
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_CXX_FLAGS_RELEASE "-O3") #-DNDEBUG We want assert!
endif()
# TODO: Reenable for the TeaClient!
#add_definitions(-DUSE_BORINGSSL)
#include_directories(${LIBRARY_PATH}/boringssl/include/)
set(SOURCE_FILES
src/misc/rnd.cpp
src/misc/time.cpp
src/misc/memtracker.cpp
src/misc/duration_utils.cpp
src/misc/digest.cpp
src/misc/base64.cpp
src/misc/net.cpp
src/misc/task_executor.cpp
src/lock/rw_mutex.cpp
#Logger
src/log/LogUtils.cpp
src/log/LogSinks.cpp
src/qlz/QuickLZ.cpp
src/qlz/QuickLZ_L3.cpp
src/qlz/QuickLZ_L1.cpp
src/converters/converter.cpp
src/query/command3.cpp
@ -118,12 +119,18 @@ set(SOURCE_FILES
src/protocol/Packet.cpp
src/protocol/buffers.cpp
src/protocol/buffers_allocator_c.cpp
src/PermissionManager.cpp
src/Properties.cpp
src/BasicChannel.cpp
src/Error.cpp
src/protocol/CryptHandler.cpp
src/protocol/CompressionHandler.cpp
src/protocol/ringbuffer.cpp
src/protocol/AcknowledgeManager.cpp
src/protocol/PacketLossCalculator.cpp
src/protocol/RawCommand.cpp
src/protocol/PacketDecoder.cpp
src/protocol/PingHandler.cpp
src/protocol/PacketStatistics.cpp
src/Properties.cpp
src/Error.cpp
src/Variable.cpp
src/linked_helper.cpp
src/EventLoop.cpp
@ -134,9 +141,6 @@ set(SOURCE_FILES
src/bbcode/bbcodes.cpp
src/channel/TreeView.cpp
src/protocol/ringbuffer.cpp
src/protocol/AcknowledgeManager.cpp
src/protocol/PacketLossCalculator.cpp
)
set(HEADER_FILES
@ -150,17 +154,12 @@ set(HEADER_FILES
src/misc/lambda.h
src/misc/hex.h
src/misc/advanced_mutex.h
src/misc/memtracker.h
src/misc/strobf.h
src/misc/task_executor.h
src/log/translation.h
src/log/LogUtils.h
src/PermissionManager.h
src/protocol/buffers.h
src/protocol/Packet.h
src/Properties.h
src/BasicChannel.h
src/Definitions.h
src/Error.h
src/protocol/CryptHandler.h
@ -174,7 +173,35 @@ set(HEADER_FILES
src/channel/TreeView.h
)
if(HAVE_SQLITE3)
if(NOT ThreadPool_FOUND)
set(SOURCE_FILES ${SOURCE_FILES}
src/misc/threads.cpp
)
endif()
if(FEATURE_LOGGING)
set(SOURCE_FILES ${SOURCE_FILES}
src/log/LogUtils.cpp
src/log/LogSinks.cpp
)
set(HEADER_FILES ${HEADER_FILES}
src/log/LogUtils.h
src/log/LogSinks.h
)
add_definitions(-DFEATURE_LOGGING)
endif()
if(FEATURE_LOGGING)
set(SOURCE_FILES ${SOURCE_FILES} src/misc/memtracker.cpp)
set(HEADER_FILES ${HEADER_FILES} src/misc/memtracker.h)
add_definitions(-DFEATURE_MEMTRACK)
else()
message("Missing logging support. Don't build mem tracker")
endif()
if(FEATURE_DATABASE)
set(SOURCE_FILES ${SOURCE_FILES}
src/sql/SqlQuery.cpp
src/sql/sqlite/SqliteSQL.cpp
@ -188,137 +215,57 @@ if(HAVE_SQLITE3)
endif()
if(HAVE_OPEN_SSL)
set(SOURCE_FILES ${SOURCE_FILES}
src/ssl/SSLManager.cpp
)
set(HEADER_FILES ${HEADER_FILES}
src/ssl/SSLManager.h
)
set(OPENSSL_LIBRARIES
openssl::ssl::static
openssl::crypto::static)
set(SOURCE_FILES ${SOURCE_FILES} src/ssl/SSLManager.cpp)
set(HEADER_FILES ${HEADER_FILES} src/ssl/SSLManager.h)
set(OPENSSL_LIBRARIES openssl::ssl::shared openssl::crypto::shared)
endif()
if (TEASPEAK_SERVER)
# TODO: Remove such stuff and move it into the server!
message("Adding TeaSpeak server only related files")
set(SOURCE_FILES ${SOURCE_FILES}
src/PermissionManager.cpp
src/BasicChannel.cpp
src/lookup/ip.cpp
)
set(HEADER_FILES ${HEADER_FILES}
src/BasicChannel.h
src/PermissionManager.h
)
endif ()
add_library(TeaSpeak STATIC ${SOURCE_FILES} ${HEADER_FILES})
target_link_libraries(TeaSpeak PUBLIC
threadpool::static jsoncpp_lib
${OPENSSL_LIBRARIES}
tomcrypt::static
tommath::static
ed25519::static
${OPENSSL_LIBRARIES}
${TARGET_LIBRARIES}
dl
)
find_package(mysql REQUIRED)
set(mysql_FOUND ON)
message("${mysql_FOUND}")
if(mysql_FOUND)
if (TEASPEAK_SERVER)
find_package(mysql REQUIRED)
message("Found MySQL")
target_link_libraries(TeaSpeak PUBLIC
mysql::client::static
)
target_compile_options(TeaSpeak PRIVATE "-Wall" "-DHAVE_MYSQL_H")
else()
message("Building without MySQL Support")
endif()
if (TEASPEAK_SERVER)
target_link_libraries(TeaSpeak PUBLIC CXXTerminal::static)
endif ()
target_include_directories(TeaSpeak PUBLIC src/)
install(TARGETS TeaSpeak
ARCHIVE DESTINATION lib
)
INSTALL (
INSTALL(
DIRECTORY ${CMAKE_SOURCE_DIR}/src/
DESTINATION include
FILES_MATCHING PATTERN "*.h*"
)
set(TEST_LIBRARIES
threadpool::static #Static
TeaSpeak #Static
TeaLicenseHelper #Static
TeaMusic #Static
CXXTerminal::static #Static
${StringVariable_LIBRARIES_STATIC}
${YAML_CPP_LIBRARIES}
pthread
stdc++fs
libevent::core libevent::pthreads
opus::static
yaml-cpp
${LIBRARY_PATH_PROTOBUF}
#We're forsed to use boringssl caused by the fact that boringssl is already within webrtc!
#Require a so
sqlite3
breakpad::static
protobuf::libprotobuf
jemalloc::shared
tomcrypt::static
tommath::static
mysqlclient.a
jsoncpp_lib
${ed25519_LIBRARIES_STATIC}
${DataPipes_LIBRARIES_SHARED} # Also includes glib2.0
openssl::ssl::shared
openssl::crypto::shared
dl
z
)
include_directories(src/)
option(BUILD_TESTS "Enable/disable test building" ON)
if(BUILD_TESTS)
add_executable(RingTest test/RingTest.cpp ${SOURCE_FILES})
target_link_libraries(RingTest ${TEST_LIBRARIES})
if(NOT WIN32)
add_executable(CommandTest test/CommandTest.cpp src/query/command3.cpp src/query/Command.cpp src/query/escape.cpp src/converters/converter.cpp src/Variable.cpp)
target_link_libraries(CommandTest DataPipes::core::shared jsoncpp_lib ${glib20_DIR}/lib/x86_64-linux-gnu/libffi.so.7 ${nice_DIR}/lib/libnice.so.10)
add_executable(WebsocketTest ${SOURCE_FILES} ${HEADER_FILES} test/WSSTest.cpp src/log/LogSinks.cpp src/log/LogSinks.h)
target_link_libraries(WebsocketTest ${TEST_LIBRARIES})
#add_executable(SQLTest ${SOURCE_FILES} ${HEADER_FILES} test/SQLTest.cpp src/log/LogSinks.cpp src/log/LogSinks.h)
#target_link_libraries(SQLTest ${TEST_LIBRARIES})
add_executable(SQL2Test test/SQL2Test.cpp src/Variable.cpp src/misc/net.cpp)
target_link_libraries(SQL2Test sqlite3)
add_executable(ChannelTest ${SOURCE_FILES} ${HEADER_FILES} test/ChannelTest.cpp src/log/LogSinks.cpp src/log/LogSinks.h)
target_link_libraries(ChannelTest ${TEST_LIBRARIES})
add_executable(EndianessTest ${SOURCE_FILES} ${HEADER_FILES} test/EndianessTest.cpp src/log/LogSinks.cpp src/log/LogSinks.h)
target_link_libraries(EndianessTest ${TEST_LIBRARIES})
include_directories(/usr/local/include/breakpad)
add_executable(CrashTest test/CrashTest.cpp ${SOURCE_FILES})
target_link_libraries(CrashTest ${TEST_LIBRARIES})
add_executable(PorpertyTest test/PropertyTest.cpp src/Properties.cpp src/PropertyDefinitions.cpp)
target_link_libraries(PorpertyTest ${TEST_LIBRARIES})
add_executable(BBTest test/BBTest.cpp ${SOURCE_FILES} src/query/command_unused.h)
target_link_libraries(BBTest ${TEST_LIBRARIES})
add_executable(LinkedTest test/LinkedTest.cpp ${SOURCE_FILES})
target_link_libraries(LinkedTest ${TEST_LIBRARIES})
add_executable(PermissionTest test/PermissionTest.cpp ${SOURCE_FILES})
target_link_libraries(PermissionTest ${TEST_LIBRARIES})
add_executable(GenerationTest test/generationTest.cpp src/protocol/generation.cpp)
target_link_libraries(GenerationTest ${TEST_LIBRARIES})
add_executable(RW-Lock-Test test/rw_lock.cpp src/lock/rw_mutex.cpp)
target_link_libraries(GenerationTest ${TEST_LIBRARIES})
add_executable(PacketLossTest src/protocol/PacketLossCalculator.cpp test/PacketLossCalculateTest.cpp)
target_link_libraries(PacketLossTest)
endif()
endif()

View File

@ -2,7 +2,6 @@
#include <algorithm>
#include <iostream>
#include <mutex>
#include <sstream>
#include <misc/base64.h>
#include "query/Command.h"
#include "misc/digest.h"
@ -14,68 +13,84 @@ using namespace std::chrono;
using namespace ts;
BasicChannel::BasicChannel(ChannelId parentId, ChannelId channelId) {
this->setProperties(make_shared<Properties>());
{
auto properties = std::make_shared<PropertyManager>();
properties->register_property_type<property::ChannelProperties>();
this->setProperties(properties);
}
this->_properties->register_property_type<property::ChannelProperties>();
this->properties()[property::CHANNEL_ID] = channelId;
this->properties()[property::CHANNEL_PID] = parentId;
}
void BasicChannel::setPermissionManager(const std::shared_ptr<permission::v2::PermissionManager>& manager) {
this->_permissions = manager;
this->update_properties_from_permissions();
bool flag_view_update;
this->update_properties_from_permissions(flag_view_update);
}
void BasicChannel::setProperties(const std::shared_ptr<ts::Properties>& props) {
void BasicChannel::setProperties(const std::shared_ptr<ts::PropertyManager>& props) {
if(this->_properties) {
(*props)[property::CHANNEL_ID] = this->channelId();
(*props)[property::CHANNEL_PID] = this->properties()[property::CHANNEL_PID].value();
}
this->_properties = props;
this->properties().registerNotifyHandler([&](Property& prop){
if(prop.type() == property::CHANNEL_FLAG_DEFAULT)
this->properties()->registerNotifyHandler([&](Property& prop){
if(prop.type() == property::CHANNEL_FLAG_DEFAULT) {
this->properties()[property::CHANNEL_FLAG_PASSWORD] = false;
else if(prop.type() == property::CHANNEL_ID)
} else if(prop.type() == property::CHANNEL_ID) {
this->_channel_id = prop;
else if(prop.type() == property::CHANNEL_ORDER)
} else if(prop.type() == property::CHANNEL_ORDER) {
this->_channel_order = prop;
}
});
//Update cached variables
if(props->has(property::CHANNEL_ORDER)) this->_channel_order = this->properties()[property::CHANNEL_ORDER];
else this->_channel_order = 0;
this->_channel_order = this->properties()[property::CHANNEL_ORDER];
this->_channel_id = this->channelId();
}
std::vector<property::ChannelProperties> BasicChannel::update_properties_from_permissions() {
std::vector<property::ChannelProperties> BasicChannel::update_properties_from_permissions(bool& need_view_update) {
std::vector<property::ChannelProperties> result;
result.reserve(2);
auto permission_manager = this->permissions(); /* keeps the manager until we've finished our calculations */
/* update the icon id */
{
IconId target_icon_id = 0;
auto& permission_icon_flags = permission_manager->permission_flags(permission::i_icon_id);
if(permission_icon_flags.value_set)
target_icon_id = (IconId) permission_manager->permission_values(permission::i_icon_id).value;
if(this->properties()[property::CHANNEL_ICON_ID] != target_icon_id) {
IconId target_icon_id{0};
auto fvalue = permission_manager->permission_value_flagged(permission::i_icon_id);
if(fvalue.has_value)
target_icon_id = (IconId) fvalue.value;
if(this->properties()[property::CHANNEL_ICON_ID].as_or(0) != target_icon_id) {
this->properties()[property::CHANNEL_ICON_ID] = target_icon_id;
result.push_back(property::CHANNEL_ICON_ID);
}
}
/* update the channel talk power */
{
permission::PermissionValue talk_power{0};
auto& permission_tp_flags = permission_manager->permission_flags(permission::i_client_needed_talk_power);
if(permission_tp_flags.value_set)
talk_power = permission_manager->permission_values(permission::i_client_needed_talk_power).value;
if(this->properties()[property::CHANNEL_NEEDED_TALK_POWER] != talk_power) {
auto fvalue = permission_manager->permission_value_flagged(permission::i_client_needed_talk_power);
if(fvalue.has_value)
talk_power = fvalue.value;
if(this->properties()[property::CHANNEL_NEEDED_TALK_POWER].as_or(0) != talk_power) {
this->properties()[property::CHANNEL_NEEDED_TALK_POWER] = talk_power;
result.push_back(property::CHANNEL_NEEDED_TALK_POWER);
}
}
/* needed view power */
{
auto fvalue = permission_manager->permission_value_flagged(permission::i_channel_needed_view_power);
if(this->last_view_power.has_value != fvalue.has_value)
need_view_update = true;
else
need_view_update = fvalue.value != this->last_view_power.value;
this->last_view_power = fvalue;
}
return result;
}
@ -96,9 +111,13 @@ BasicChannel::BasicChannel(std::shared_ptr<BasicChannel> parent, ChannelId chann
BasicChannel::~BasicChannel() { }
ChannelType::ChannelType BasicChannel::channelType() {
if (this->properties()[property::CHANNEL_FLAG_PERMANENT].as<bool>()) return ChannelType::ChannelType::permanent;
else if (this->properties()[property::CHANNEL_FLAG_SEMI_PERMANENT].as<bool>()) return ChannelType::ChannelType::semipermanent;
else return ChannelType::ChannelType::temporary;
if(this->properties()[property::CHANNEL_FLAG_PERMANENT].as_or<bool>(true)) {
return ChannelType::ChannelType::permanent;
} else if (this->properties()[property::CHANNEL_FLAG_SEMI_PERMANENT].as_or<bool>(false)) {
return ChannelType::ChannelType::semipermanent;
} else {
return ChannelType::ChannelType::temporary;
}
}
void BasicChannel::setChannelType(ChannelType::ChannelType type) {
@ -106,23 +125,52 @@ void BasicChannel::setChannelType(ChannelType::ChannelType type) {
properties()[property::CHANNEL_FLAG_SEMI_PERMANENT] = type == ChannelType::semipermanent;
}
bool BasicChannel::passwordMatch(std::string password, bool hashed) {
if (!this->properties()[property::CHANNEL_FLAG_PASSWORD].as<bool>() || this->properties()[property::CHANNEL_PASSWORD].value().empty()) return true;
if (password.empty()) return false;
void BasicChannel::updateChannelType(std::vector<property::ChannelProperties> &properties, ChannelType::ChannelType type) {
if(this->properties()[property::CHANNEL_FLAG_PERMANENT].update_value(type == ChannelType::permanent)) {
properties.push_back(property::CHANNEL_FLAG_PERMANENT);
}
if(this->properties()[property::CHANNEL_PASSWORD].as<string>() == password)
return true;
password = base64::encode(digest::sha1(password));
return this->properties()[property::CHANNEL_PASSWORD].as<string>() == password;
if(this->properties()[property::CHANNEL_FLAG_SEMI_PERMANENT].update_value(type == ChannelType::semipermanent)) {
properties.push_back(property::CHANNEL_FLAG_SEMI_PERMANENT);
}
}
int64_t BasicChannel::emptySince() {
if (!properties().hasProperty(property::CHANNEL_LAST_LEFT))
return 0;
bool BasicChannel::verify_password(const std::optional<std::string> &password, bool password_hashed) {
if(!this->properties()[property::CHANNEL_FLAG_PASSWORD].as_or<bool>(false)) {
return true;
}
time_point<system_clock> lastLeft = time_point<system_clock>() + milliseconds(properties()[property::CHANNEL_LAST_LEFT].as<uint64_t>());
return (int64_t) duration_cast<seconds>(system_clock::now() - lastLeft).count();
auto channel_password = this->properties()[property::CHANNEL_PASSWORD].value();
if(channel_password.empty()) {
return true;
}
if(!password.has_value()) {
return false;
}
if(password_hashed) {
return *password == channel_password;
}
/* We might have supplied the raw password */
return base64::encode(digest::sha1(*password)) == channel_password;
}
uint64_t BasicChannel::empty_seconds() {
using std::chrono::system_clock;
using std::chrono::milliseconds;
using std::chrono::seconds;
using std::chrono::floor;
auto last_channel_leave = system_clock::time_point{} + milliseconds{properties()[property::CHANNEL_LAST_LEFT].as_or<uint64_t>(0)};
auto current_timestamp = system_clock::now();
if(current_timestamp < last_channel_leave) {
/* clock seems to have gone backwards */
return 0;
}
return (uint64_t) floor<seconds>(current_timestamp - last_channel_leave).count();
}
void BasicChannel::setLinkedHandle(const std::weak_ptr<TreeView::LinkedTreeEntry> &ptr) {
@ -179,9 +227,11 @@ std::shared_ptr<BasicChannel> BasicChannelTree::findChannel(ChannelId channelId,
std::shared_ptr<BasicChannel> BasicChannelTree::findChannel(const std::string &name, const shared_ptr<BasicChannel> &layer) {
for (auto elm : this->channels())
if (elm->name() == name && elm->parent() == layer)
for (auto elm : this->channels()) {
if (elm->name() == name && elm->parent() == layer) {
return elm;
}
}
return nullptr;
}
@ -257,16 +307,24 @@ deque<std::shared_ptr<ts::BasicChannel>> BasicChannelTree::delete_channel_root(c
}
bool BasicChannelTree::setDefaultChannel(const shared_ptr<BasicChannel> &ch) {
if (!ch) return false;
for (const auto &elm : this->channels())
elm->properties()[property::CHANNEL_FLAG_DEFAULT] = false;
if (!ch) {
return false;
}
for (const auto &elm : this->channels()) {
elm->properties()[property::CHANNEL_FLAG_DEFAULT].update_value(false);
}
ch->properties()[property::CHANNEL_FLAG_DEFAULT] = true;
return true;
}
std::shared_ptr<BasicChannel> BasicChannelTree::getDefaultChannel() {
for (auto elm : this->channels())
if (elm->properties()[property::CHANNEL_FLAG_DEFAULT].as<bool>()) return elm;
for (auto elm : this->channels()) {
if (elm->properties()[property::CHANNEL_FLAG_DEFAULT].as_or<bool>(false)) {
return elm;
}
}
return nullptr;
}

View File

@ -45,29 +45,34 @@ namespace ts {
inline std::string name(){ return properties()[property::CHANNEL_NAME]; }
inline ChannelId channelOrder(){ return this->previousChannelId(); }
inline Properties& properties() const { return *this->_properties; }
inline PropertyWrapper properties() { return PropertyWrapper{this->_properties}; }
inline const PropertyWrapper properties() const { return PropertyWrapper{this->_properties}; }
ChannelType::ChannelType channelType();
void setChannelType(ChannelType::ChannelType);
void updateChannelType(std::vector<property::ChannelProperties>& /* updates */, ChannelType::ChannelType /* target type */);
[[nodiscard]] bool verify_password(const std::optional<std::string>&, bool /* password already hashed */);
bool passwordMatch(std::string password, bool hashed = false);
bool defaultChannel() { return (*this->_properties)[property::CHANNEL_FLAG_DEFAULT]; }
int64_t emptySince();
uint64_t empty_seconds();
inline std::chrono::system_clock::time_point createdTimestamp() {
return std::chrono::system_clock::time_point() + std::chrono::milliseconds(this->properties()[property::CHANNEL_CREATED_AT].as<int64_t>());
return std::chrono::system_clock::time_point() + std::chrono::milliseconds(
this->properties()[property::CHANNEL_CREATED_AT].as_unchecked<int64_t>());
}
ts_always_inline bool permission_require_property_update(const permission::PermissionType& permission) {
return permission == permission::i_icon_id || permission == permission::i_client_needed_talk_power;
return permission == permission::i_icon_id || permission == permission::i_client_needed_talk_power || permission == permission::i_channel_needed_view_power;
}
std::vector<property::ChannelProperties> update_properties_from_permissions();
std::vector<property::ChannelProperties> update_properties_from_permissions(bool& /* need view update */);
inline bool permission_granted(const permission::PermissionType& permission, const permission::v2::PermissionFlaggedValue& granted_value, bool require_granted_value) {
auto permission_manager = this->permissions(); /* copy the manager */
assert(permission_manager);
const auto data = permission_manager->permission_value_flagged(permission);
return BasicChannel::permission_granted(data,granted_value, require_granted_value);
return BasicChannel::permission_granted(data, granted_value, require_granted_value);
}
ts_always_inline bool talk_power_granted(const permission::v2::PermissionFlaggedValue& granted_value) {
@ -76,12 +81,12 @@ namespace ts {
ts_always_inline std::shared_ptr<permission::v2::PermissionManager> permissions(){ return this->_permissions; }
virtual void setPermissionManager(const std::shared_ptr<permission::v2::PermissionManager>&);
virtual void setProperties(const std::shared_ptr<Properties>&);
virtual void setProperties(const std::shared_ptr<PropertyManager>&);
private:
ts_always_inline
static bool permission_granted(const permission::v2::PermissionFlaggedValue& channel_permission_value, const permission::v2::PermissionFlaggedValue& granted_value, bool require_granted_value) {
if(!channel_permission_value.has_value || channel_permission_value.value == 0) {
return !require_granted_value || granted_value.has_value;
return granted_value.has_value ? granted_value.has_power() : !require_granted_value;
}
if(channel_permission_value.value == -1) {
return granted_value.value == -1;
@ -97,9 +102,11 @@ namespace ts {
void setLinkedHandle(const std::weak_ptr<TreeView::LinkedTreeEntry> &) override;
protected:
std::weak_ptr<TreeView::LinkedTreeEntry> _link;
std::shared_ptr<Properties> _properties;
std::shared_ptr<PropertyManager> _properties;
std::shared_ptr<permission::v2::PermissionManager> _permissions;
permission::v2::PermissionFlaggedValue last_view_power{0, false};
ChannelId _channel_order = 0;
ChannelId _channel_id = 0;
};

View File

@ -103,11 +103,22 @@ namespace ts {
INIT_HIGH, //Web -> Auth
CONNECTED,
DISCONNECTING,
DISCONNECTING_FLUSHING,
DISCONNECTED
};
}
enum ChannelConversationMode : uint8_t {
CHANNELCONVERSATIONMODE_PUBLIC = 0,
CHANNELCONVERSATIONMODE_PRIVATE = 1,
CHANNELCONVERSATIONMODE_NONE = 2
};
enum ChannelSidebarMode : uint8_t {
CHANNELSIDEBARMODE_CONVERSATION = 0,
CHANNELSIDEBARMODE_DESCRIPTION = 1,
CHANNELSIDEBARMODE_FILE_TRANSFER = 2
};
enum QueryEventGroup : int {
QEVENTGROUP_MIN = 0,
QEVENTGROUP_SERVER = 0,
@ -167,6 +178,8 @@ DEFINE_TRANSFORMS(ts::LicenseType, uint8_t);
DEFINE_TRANSFORMS(ts::PluginTargetMode, uint8_t);
DEFINE_TRANSFORMS(ts::ViewReasonId, uint8_t);
DEFINE_TRANSFORMS(ts::ChatMessageMode, uint8_t);
DEFINE_TRANSFORMS(ts::ChannelConversationMode, uint8_t);
DEFINE_TRANSFORMS(ts::ChannelSidebarMode, uint8_t);
#ifdef WIN32
#define ts_always_inline __forceinline

View File

@ -2,11 +2,17 @@
// Created by wolverindev on 17.10.17.
//
#include "./query/command3.h"
#include "Error.h"
using namespace ts;
const std::vector<ErrorType> ts::avariableErrors = {
#define str(x) #x
#define define_error_description(type, description) \
{ error::type, str(type), description }
const std::vector<ErrorType> ts::availableErrors = {
{0x0000, "ok" , "ok" },
{0x0001, "undefined" , "undefined error" },
{0x0002, "not_implemented" , "not implemented" },
@ -58,6 +64,8 @@ const std::vector<ErrorType> ts::avariableErrors = {
{0x0310, "channel_is_deleted" , "target channel is deleted" },
{0x0311, "channel_name_invalid" , "channel name is invalid" },
{0x0312, "channel_limit_reached" , "the virtualserver channel limit has been reached" },
define_error_description(channel_family_not_visible, "the channel family isn't visible by default"),
define_error_description(channel_default_require_visible, "the channel family contains the default channel"),
{0x0400, "server_invalid_id" , "invalid serverID" },
{0x0401, "server_running" , "server is running" },
@ -95,6 +103,8 @@ const std::vector<ErrorType> ts::avariableErrors = {
{0x0605, "parameter_invalid_size" , "invalid parameter size" },
{0x0606, "parameter_missing" , "missing required parameter" },
{0x0607, "parameter_checksum" , "invalid checksum" },
define_error_description(parameter_constraint_violation, "parameter does not fits its constraint"),
/* example: all name= parameter in a bulk must start with /icon_ */
{0x0700, "vs_critical" , "virtual server got a critical error" },
{0x0701, "connection_lost" , "Connection lost" },
@ -109,7 +119,6 @@ const std::vector<ErrorType> ts::avariableErrors = {
{0x070A, "serverlibrary_not_initialised" , "server library not initialized" },
{0x070B, "whisper_too_many_targets" , "too many whisper targets" },
{0x070C, "whisper_no_targets" , "no whisper targets found" },
{0x0800, "file_invalid_name" , "invalid file name" },
{0x0801, "file_invalid_permissions" , "invalid file permissions" },
{0x0802, "file_already_exists" , "file already exists" },
@ -134,7 +143,11 @@ const std::vector<ErrorType> ts::avariableErrors = {
{0x0815, "file_transfer_client_quota_exceeded" , "file transfer client quota exceeded" },
{0x0816, "file_transfer_reset" , "file transfer reset" },
{0x0817, "file_transfer_limit_reached" , "file transfer limit reached" },
define_error_description(file_api_timeout, "the file API call has been timed out"),
define_error_description(file_virtual_server_not_registered, "the file server does not know our virtual server"),
define_error_description(file_server_transfer_limit_reached, "the file server reached his max concurrent transfers limit"),
define_error_description(file_client_transfer_limit_reached, "you reached your max concurrent transfers limit"),
{0x0A08, "server_insufficeient_permissions" , "insufficient client permissions" },
@ -143,7 +156,9 @@ const std::vector<ErrorType> ts::avariableErrors = {
{0x0D01, "server_connect_banned" , "connection failed, you are banned" },
{0x0D03, "ban_flooding" , "flood ban" },
{0x0F00, "token_invalid_id" , "invalid privilege key" },
define_error_description(token_invalid_id, "token unknown"),
define_error_description(token_expired, "token has been expired"),
define_error_description(token_use_limit_exceeded, "token has reached its use limit"),
{0x1000, "web_handshake_invalid" , "Invalid handshake" },
{0x1001, "web_handshake_unsupported" , "Handshake intention unsupported" },
@ -167,6 +182,9 @@ const std::vector<ErrorType> ts::avariableErrors = {
{0x2200, "conversation_invalid_id" , "invalid conversation id" },
{0x2201, "conversation_more_data" , "there are more messages to send" },
{0x2202, "conversation_is_private" , "the target conversation is private" },
{0x2203, "conversation_not_exists" , "the target conversation does not exists" },
{0x2300, "rtc_missing_target_channel" , "the target channel does not exists" },
{0x1200, "query_not_exists" , "query account does not exists" },
{0x1201, "query_already_exists" , "query account already exists" },
@ -175,33 +193,86 @@ const std::vector<ErrorType> ts::avariableErrors = {
{0x1300, "group_invalid_id" , "Invalid group id" },
{0x1301, "group_name_inuse" , "Group name is already in use" },
{0x1302, "group_not_assigned_over_this_server" , "the group hasn't been assigned over this server" },
define_error_description(group_not_assigned_over_this_server, "the group hasn't been assigned over this server"),
define_error_description(group_not_empty, "the target group isn't empty"),
{0xE000, "resource_limit_reached" , "resource limit reached" },
define_error_description(broadcast_invalid_id, "the broadcast does not exists"),
define_error_description(broadcast_invalid_type, "the broadcast type is invalid"),
define_error_description(broadcast_invalid_client, "the broadcasting client does not exists"),
{0xFFFF, "custom_error" , "costume" },
};
ErrorType ErrorType::Success = avariableErrors[0];
ErrorType ErrorType::Success = availableErrors[0];
ErrorType ErrorType::Costume = findError("custom_error");
ErrorType ErrorType::VSError = findError("vs_critical");
ErrorType ErrorType::DBEmpty = findError("database_empty_result");
ErrorType ts::findError(uint16_t errorId){
for(auto elm : avariableErrors)
for(auto elm : availableErrors)
if(elm.errorId == errorId) return elm;
return ErrorType{errorId, "undefined", "undefined"};
}
ErrorType ts::findError(std::string key){
for(auto elm : avariableErrors)
for(auto elm : availableErrors)
if(elm.name == key) return elm;
return ErrorType{1, key, "undefined"};
}
CommandResult CommandResult::Success = {avariableErrors[0], ""};
CommandResult CommandResult::NotImplemented = {avariableErrors[2], ""};
inline void write_command_result_error(ts::command_builder_bulk bulk, const command_result& result, const std::string_view& id_key) {
bulk.put_unchecked(id_key, (uint32_t) result.error_code());
bulk.put_unchecked("msg", findError(result.error_code()).message);
if(result.is_permission_error())
bulk.put_unchecked("failed_permid", (uint32_t) result.permission_id());
}
CommandResultPermissionError::CommandResultPermissionError(permission::PermissionType error, const std::string &extraMsg) : CommandResult(findError(0x0A08), "") {
this->extraProperties["failed_permid"] = std::to_string((int16_t) error);
this->_type = PERM_ERROR;
inline void write_command_result_detailed(ts::command_builder_bulk bulk, const command_result& result, const std::string_view& id_key) {
auto details = result.details();
bulk.put_unchecked(id_key, (uint32_t) details->error_id);
bulk.put_unchecked("msg", findError(details->error_id).message);
for(const auto& extra : details->extra_properties)
bulk.put(extra.first, extra.second);
}
void command_result::build_error_response(ts::command_builder &builder, const std::string_view &idKey) const {
switch(this->type()) {
case command_result_type::error:
write_command_result_error(builder.bulk(0), *this, idKey);
break;
case command_result_type::detailed:
write_command_result_detailed(builder.bulk(0), *this, idKey);
break;
case command_result_type::bulked: {
auto bulks = this->bulks();
builder.reserve_bulks(bulks->size());
for(size_t index{0}; index < bulks->size(); index++) {
auto& entry = bulks->at(index);
switch (entry.type()) {
case command_result_type::error:
write_command_result_error(builder.bulk(index), entry, idKey);
break;
case command_result_type::detailed:
write_command_result_detailed(builder.bulk(index), entry, idKey);
break;
case command_result_type::bulked:
assert(false);
break;
}
}
if(bulks->empty()) {
assert(false);
builder.put_unchecked(0, idKey, (uint32_t) error::ok);
builder.put_unchecked(0, "msg", findError(error::ok).message);
}
break;
}
default:
assert(false);
break;
}
}

View File

@ -10,10 +10,7 @@
#include <vector>
#include <map>
#define _NDEBUG
namespace ts {
struct CommandResult;
namespace permission {
enum PermissionType : uint16_t;
}
@ -24,9 +21,15 @@ namespace ts {
undefined = 0x1,
not_implemented = 0x2,
lib_time_limit_reached = 0x5,
command_not_found = 0x100,
unable_to_bind_network_port = 0x101,
no_network_port_available = 0x102,
/* Mainly used by the TeaClient */
command_timed_out = 0x110,
command_aborted_connection_closed = 0x111,
client_invalid_id = 0x200,
client_nickname_inuse = 0x201,
invalid_error_code = 0x202,
@ -70,6 +73,9 @@ namespace ts {
channel_is_deleted = 0x310,
channel_name_invalid = 0x311,
channel_limit_reached = 0x312,
channel_family_not_visible = 0x320,
channel_default_require_visible = 0x321,
server_invalid_id = 0x400,
server_running = 0x401,
@ -97,6 +103,7 @@ namespace ts {
database_no_modifications = 0x503,
database_constraint = 0x504,
database_reinvoke = 0x505,
parameter_quote = 0x600,
parameter_invalid_count = 0x601,
parameter_invalid = 0x602,
@ -105,6 +112,8 @@ namespace ts {
parameter_invalid_size = 0x605,
parameter_missing = 0x606,
parameter_checksum = 0x607,
parameter_constraint_violation = 0x6010,
vs_critical = 0x700,
connection_lost = 0x701,
not_connected = 0x702,
@ -116,8 +125,10 @@ namespace ts {
could_not_initialise_input_client = 0x708,
clientlibrary_not_initialised = 0x709,
serverlibrary_not_initialised = 0x70a,
whisper_too_many_targets = 0x70b,
whisper_no_targets = 0x70c,
file_invalid_name = 0x800,
file_invalid_permissions = 0x801,
file_already_exists = 0x802,
@ -142,11 +153,21 @@ namespace ts {
file_transfer_client_quota_exceeded = 0x815,
file_transfer_reset = 0x816,
file_transfer_limit_reached = 0x817,
file_api_timeout = 0x820,
file_virtual_server_not_registered = 0x821,
file_server_transfer_limit_reached = 0x822,
file_client_transfer_limit_reached = 0x823,
server_insufficeient_permissions = 0xa08,
accounting_slot_limit_reached = 0xb01,
server_connect_banned = 0xd01,
ban_flooding = 0xd03,
token_invalid_id = 0xf00,
token_expired = 0xf10,
token_use_limit_exceeded = 0xf11,
web_handshake_invalid = 0x1000,
web_handshake_unsupported = 0x1001,
web_handshake_identity_unsupported = 0x1002,
@ -170,29 +191,44 @@ namespace ts {
group_invalid_id = 0x1300,
group_name_inuse = 0x1301,
group_not_assigned_over_this_server = 0x1302,
group_not_empty = 0x1303,
conversation_invalid_id = 0x2200,
conversation_more_data = 0x2201,
conversation_is_private = 0x2202,
conversation_not_exists = 0x2203,
rtc_missing_target_channel = 0x2300,
broadcast_invalid_id = 0x2400,
broadcast_invalid_type = 0x2401,
broadcast_invalid_client = 0x2402,
custom_error = 0xffff
};
};
struct detailed_command_result {
error::type error_id;
std::map<std::string, std::string> extra_properties;
error::type error_id{};
std::map<std::string, std::string> extra_properties{};
};
enum struct command_result_type {
error = 0b00, /* must be 0 because 0 is the default value! */
detailed = 0b01,
bulked = 0b11
};
/*
* return command_result{permission::b_virtualserver_select_godmode}; => movabs rax,0xa08001700000001; ret; (Only if there is no destructor!)
* return command_result{permission::b_virtualserver_select_godmode}; => movabs rax,0xa08001700000001; ret; (Only if there is no destructor!)
* return command_result{error::vs_critical, "unknown error"}; => To a lot of code
*/
struct command_result_bulk;
struct command_result { /* fixed size of 8 (64 bits) */
static constexpr uint64_t MASK_ERROR = ~((uint64_t) 1 << (sizeof(error::type) * 8));
static constexpr uint64_t MASK_PERMISSION = ~((uint64_t) 1 << (sizeof(permission::PermissionType) * 8));
static constexpr uint64_t MASK_PTR = ~((uint64_t) 0b111U);
static constexpr uint8_t OFFSET_ERROR = (8 - sizeof(error::type)) * 8;
static constexpr uint8_t OFFSET_PERMISSION = (8 - sizeof(permission::PermissionType) - sizeof(error::type)) * 8;
@ -205,57 +241,111 @@ namespace ts {
* bits [64 - sizeof(error::type);64] => error type | Usually evaluates to [48;64]
* bits [64 - sizeof(error::type) - sizeof(permission::PermissionType);64 - sizeof(error::type)] => permission id | Usually evaluates to [32;48]
*/
uint64_t data = 0;
uint64_t data{0};
/* Test for mode 1 as described before */
static_assert(sizeof(permission::PermissionType) * 8 + sizeof(error::type) * 8 <= 62);
static_assert(sizeof(permission::PermissionType) * 8 + sizeof(error::type) * 8 <= 60);
[[nodiscard]] inline command_result_type type() const {
return (command_result_type) (this->data & 0b11U);
}
[[nodiscard]] inline bool has_error() const {
switch (this->type()) {
case command_result_type::bulked:
for(const auto& bulk : *this->bulks()) {
if(bulk.has_error()) {
return true;
}
}
return false;
case command_result_type::error:
return this->error_code() != error::ok;
case command_result_type::detailed:
return this->details()->error_id != error::ok;
default:
assert(false);
return false;
}
}
/* only valid for command_result_type::error */
[[nodiscard]] inline error::type error_code() const {
if(this->is_detailed()) return this->details()->error_id;
assert(this->type() == command_result_type::error);
return (error::type) ((this->data >> OFFSET_ERROR) & MASK_ERROR);
}
/* only valid for command_result_type::error */
[[nodiscard]] inline bool is_permission_error() const {
assert(this->type() == command_result_type::error);
return this->error_code() == error::server_insufficeient_permissions;
}
/* only valid for command_result_type::error */
[[nodiscard]] inline permission::PermissionType permission_id() const {
if(this->is_detailed()) return (permission::PermissionType) -1; /* not supported */
assert(this->type() == command_result_type::error);
return (permission::PermissionType) ((this->data >> OFFSET_PERMISSION) & MASK_PERMISSION);
}
[[nodiscard]] inline const detailed_command_result* details() const { return (detailed_command_result*) this->data; }
[[nodiscard]] inline detailed_command_result* details() { return (detailed_command_result*) this->data; }
[[nodiscard]] inline bool is_detailed() const {
return (this->data & 0x1UL) == 0;
/* only valid for command_result_type::detailed */
[[nodiscard]] inline const detailed_command_result* details() const {
assert(this->type() == command_result_type::detailed);
return (detailed_command_result*) (this->data & MASK_PTR);
}
inline std::unique_ptr<detailed_command_result> release_details() {
if(!this->is_detailed()) return nullptr;
auto result = this->details();
this->data = 0;
return std::unique_ptr<detailed_command_result>{result};
/* only valid for command_result_type::detailed */
[[nodiscard]] inline detailed_command_result* details() {
assert(this->type() == command_result_type::detailed);
return (detailed_command_result*) (this->data & MASK_PTR);
}
/* Attention: Releases the internal detailed pointer! */
inline CommandResult as_command_result();
#ifndef _NDEBUG /* We dont need to secure that because gcc deduct us to an uint64_t and the only advantage is the mem leak test which is deactivated anyways */
command_result(command_result&) = delete;
command_result(const command_result&) = delete;
command_result(command_result&& other) : data(other.data) {
other.data = 0;
/* only valid for command_result_type::bulked */
[[nodiscard]] inline const std::vector<command_result>* bulks() const {
assert(this->type() == command_result_type::bulked);
return (std::vector<command_result>*) (this->data & MASK_PTR);
}
/* only valid for command_result_type::bulked */
[[nodiscard]] inline std::vector<command_result>* bulks() {
assert(this->type() == command_result_type::bulked);
return (std::vector<command_result>*) (this->data & MASK_PTR);
}
#ifdef COMMAND_BUILDER_DEFINED
void build_error_response(ts::command_builder& /* result */, const std::string_view& /* id key */) const;
#endif
inline command_result& reset(command_result&& other) {
this->release_data();
std::exchange(this->data, other.data);
return *this;
}
inline void release_data() {
auto type = this->type();
if(type == command_result_type::bulked) {
auto bulks = this->bulks();
for(auto& bulk : *bulks) {
bulk.release_data();
}
delete bulks;
} else if(type == command_result_type::detailed) {
auto details = this->details();
delete details;
}
this->data = 0;
}
command_result() = default;
command_result(const command_result&) = delete;
command_result(command_result&& other) noexcept {
/* we've to specify a move constructor since, we're a "trivial" type, which means that our "data" will just get copied */
std::swap(this->data, other.data);
}
explicit command_result(permission::PermissionType permission) {
this->data = 0x01; /* the the type to 1 */
this->data = (uint64_t) command_result_type::error;
this->data |= (uint64_t) error::server_insufficeient_permissions << OFFSET_ERROR;
this->data |= (uint64_t) permission << OFFSET_PERMISSION;
@ -263,7 +353,7 @@ namespace ts {
explicit command_result(error::type error) {
this->data = 0x01; /* the the type to 1 */
this->data = (uint64_t) command_result_type::error;
this->data |= (uint64_t) error << (8 - sizeof(error::type)) * 8;
}
@ -273,28 +363,97 @@ namespace ts {
assert(((uintptr_t) details_ptr & 0x03U) == 0); // must be aligned!
this->data = (uintptr_t) details_ptr;
this->data |= (uint64_t) command_result_type::detailed;
details_ptr->error_id = error;
details_ptr->extra_properties["extra_msg"] = message;
}
command_result(error::type error, const std::map<std::string, std::string>& properties) : command_result{error, std::string{""}} {
assert(this->is_detailed());
assert(this->type() == command_result_type::detailed);
this->details()->extra_properties = properties;
}
#ifndef _NDEBUG
/* if we're not using any debug we dont have to use a deconstructor. A deconstructor prevent a direct uint64_t return as described above */
explicit command_result(command_result_bulk&&);
#if !defined(_NDEBUG) && false
/* if we're not using any debug we dont have to use a destructor. A destructor prevent a direct uint64_t return as described above */
~command_result() {
if((this->data & 0x01) == 0x00) {
// this->details needs to be removed 'till this gets destructed
assert(this->data == 0);
}
assert(this->data == 0);
}
#endif
};
static_assert(sizeof(command_result) == 8);
struct command_result_bulk {
friend struct command_result;
public:
command_result_bulk() = default;
explicit command_result_bulk(command_result&& result) { this->results.push_back(std::forward<command_result>(result)); }
~command_result_bulk() {
for(auto& result : this->results) {
result.release_data();
}
}
inline size_t length() const {
return this->results.size();
}
inline const ts::command_result& response(size_t index) const {
return this->results[index];
}
inline void reserve(size_t length) {
this->results.reserve(length);
}
inline void insert_result(ts::command_result&& result) {
this->results.push_back(std::forward<ts::command_result>(result));
}
inline void set_result(size_t index, ts::command_result&& result) {
auto& result_container = this->results[index];
result_container.reset(std::forward<ts::command_result>(result));
}
inline void emplace_result(permission::PermissionType permission) {
this->results.emplace_back(permission);
}
inline void emplace_result(error::type error) {
this->results.emplace_back(error);
}
inline void emplace_result(error::type error, const std::string& message) {
this->results.emplace_back(error, message);
}
template <typename... Args>
inline void emplace_result_n(size_t times, const Args&&... args) {
while(times-- > 0)
this->results.emplace_back(args...);
}
[[nodiscard]] inline auto begin() { return this->results.begin(); }
[[nodiscard]] inline auto end() { return this->results.end(); }
[[nodiscard]] inline auto cbegin() const { return this->results.cbegin(); }
[[nodiscard]] inline auto cend() const { return this->results.cend(); }
private:
std::vector<command_result> results{};
};
inline command_result::command_result(ts::command_result_bulk &&bulk) {
auto bulks = new std::vector<command_result>{};
assert(((uintptr_t) bulks & 0x03U) == 0); // must be aligned!
this->data = (uintptr_t) bulks;
this->data |= (uint64_t) command_result_type::bulked;
bulks->swap(bulk.results);
}
struct ErrorType {
public:
static ErrorType Success;
@ -311,15 +470,15 @@ namespace ts {
std::string message;
bool operator==(const ErrorType& ref) const {
return errorId == ref.errorId;
return this->errorId == ref.errorId;
}
bool operator!=(const ErrorType& ref) const { return !operator==(ref); }
ErrorType& operator=(const ErrorType& ref) {
errorId = ref.errorId;
name = ref.name;
message = ref.message;
this->errorId = ref.errorId;
this->name = ref.name;
this->message = ref.message;
return *this;
}
@ -331,66 +490,7 @@ namespace ts {
}
};
extern const std::vector<ErrorType> avariableErrors;
extern const std::vector<ErrorType> availableErrors;
extern ErrorType findError(uint16_t errorId);
extern ErrorType findError(std::string key);
enum CommandResultType {
GENERAL,
PERM_ERROR
};
struct CommandResult {
public:
static CommandResult Success;
static CommandResult NotImplemented;
CommandResult(const CommandResult& ref) : _type(ref._type), error(ref.error), extraProperties(ref.extraProperties) {}
CommandResult(CommandResult&& ref) : _type(ref._type), error(ref.error), extraProperties(ref.extraProperties) {}
CommandResult(ErrorType error, const std::string &extraMsg = "") : error(std::move(error)) { if(extraMsg.empty()) return; /*extraProperties["extramsg"] = extraMsg; */extraProperties["extra_msg"] = extraMsg; }
CommandResult(std::string error, const std::string &extraMsg = "") : error(findError(std::move(error))) { if(extraMsg.empty()) return; /*extraProperties["extramsg"] = extraMsg; */extraProperties["extra_msg"] = extraMsg; }
CommandResult() : CommandResult(ErrorType::Success, ""){}
CommandResult(ErrorType error, std::map<std::string, std::string> details) : error(error), extraProperties(std::move(details)) {}
bool operator==(const CommandResult& ref){
return this->error == ref.error && ref.extraProperties == this->extraProperties;
}
CommandResult& operator=(const CommandResult& ref)= default;
/**
* @return true if fail
*/
bool operator!() const {
return this->error != ErrorType::Success;
}
virtual CommandResultType type(){ return _type; }
ErrorType error;
std::map<std::string, std::string> extraProperties;
CommandResultType _type = CommandResultType::GENERAL;
};
struct CommandResultPermissionError : public CommandResult {
public:
CommandResultPermissionError(permission::PermissionType error, const std::string &extraMsg = "");
};
CommandResult command_result::as_command_result() {
if(this->is_detailed()) {
const auto details = this->details();
auto result = CommandResult{findError(details->error_id), details->extra_properties};
this->release_details();
return result;
} else {
const auto code = this->error_code();
auto error = findError(this->error_code());
if(code == error::server_insufficeient_permissions)
return CommandResultPermissionError{(permission::PermissionType) this->permission_id()};
else
return CommandResult{error};
}
}
}
#undef _NDEBUG
}

View File

@ -1,11 +1,7 @@
#include <thread>
#include <utility>
#include <vector>
#include <condition_variable>
#include <cassert>
#include <algorithm>
#include "./log/LogUtils.h"
#include "./misc/sassert.h"
#include <condition_variable>
#include "./EventLoop.h"
using namespace std;
@ -161,14 +157,13 @@ void EventExecutor::_reassign_thread_names(std::unique_lock<std::mutex> &lock) {
void EventExecutor::_executor(ts::event::EventExecutor *loop) {
while(true) {
sassert(std::addressof(loop->lock) != nullptr);
unique_lock lock(loop->lock);
unique_lock lock{loop->lock};
loop->condition.wait(lock, [&] {
return loop->should_shutdown || loop->should_adjust || loop->head != nullptr;
});
if(loop->should_shutdown)
if(loop->should_shutdown) {
break;
}
if(loop->should_adjust) {
const auto current_threads = loop->_threads.size();
@ -201,10 +196,10 @@ void EventExecutor::_executor(ts::event::EventExecutor *loop) {
auto linked_entry = loop->head;
loop->head = linked_entry->next;
if(loop->head) {
sassert(linked_entry == loop->head->previous);
assert(linked_entry == loop->head->previous);
loop->head->previous = nullptr;
} else {
sassert(linked_entry == loop->tail);
assert(linked_entry == loop->tail);
loop->tail = nullptr;
}
@ -215,7 +210,7 @@ void EventExecutor::_executor(ts::event::EventExecutor *loop) {
continue;
}
sassert(event_handler->_event_ptr == linked_entry);
assert(event_handler->_event_ptr == linked_entry);
event_handler->_event_ptr = nullptr;
lock.unlock();

View File

@ -7,84 +7,83 @@
#include <thread>
#include <condition_variable>
namespace ts {
namespace event {
class EventExecutor;
namespace ts::event {
class EventExecutor;
class EventEntry {
friend class EventExecutor;
public:
virtual void event_execute(const std::chrono::system_clock::time_point& /* scheduled timestamp */) = 0;
class EventEntry {
friend class EventExecutor;
public:
virtual void event_execute(const std::chrono::system_clock::time_point& /* scheduled timestamp */) = 0;
private:
void* _event_ptr = nullptr;
};
private:
void* _event_ptr = nullptr;
};
template <typename class_t>
class ProxiedEventEntry : public event::EventEntry {
public:
using callback_t = void(class_t::*)(const std::chrono::system_clock::time_point &);
using static_callback_t = void(*)(class_t *, const std::chrono::system_clock::time_point &);
template <typename class_t>
class ProxiedEventEntry : public event::EventEntry {
public:
using callback_t = void(class_t::*)(const std::chrono::system_clock::time_point &);
using static_callback_t = void(*)(class_t *, const std::chrono::system_clock::time_point &);
ProxiedEventEntry(const std::shared_ptr<class_t>& _instance, callback_t callback) : instance(_instance), callback(callback) { }
ProxiedEventEntry(const std::shared_ptr<class_t>& _instance, callback_t callback) : instance(_instance), callback(callback) { }
std::weak_ptr<class_t> instance;
callback_t callback;
std::weak_ptr<class_t> instance;
callback_t callback;
void event_execute(const std::chrono::system_clock::time_point &point) override {
auto _instance = this->instance.lock();
if(!_instance)
return;
auto callback_ptr = (void**) &this->callback;
(*(static_callback_t*) callback_ptr)(&*_instance, point);
void event_execute(const std::chrono::system_clock::time_point &point) override {
auto _instance = this->instance.lock();
if(!_instance) {
return;
}
};
class EventExecutor {
public:
explicit EventExecutor(std::string /* thread prefix */);
virtual ~EventExecutor();
auto callback_ptr = (void**) &this->callback;
(*(static_callback_t*) callback_ptr)(&*_instance, point);
}
};
bool initialize(int /* num threads */);
bool schedule(const std::shared_ptr<EventEntry>& /* entry */);
bool cancel(const std::shared_ptr<EventEntry>& /* entry */); /* Note: Will not cancel already running executes */
void shutdown();
class EventExecutor {
public:
explicit EventExecutor(std::string /* thread prefix */);
virtual ~EventExecutor();
inline const std::string& thread_prefix() const { return this->_thread_prefix; }
bool initialize(int /* num threads */);
bool schedule(const std::shared_ptr<EventEntry>& /* entry */);
bool cancel(const std::shared_ptr<EventEntry>& /* entry */); /* Note: Will not cancel already running executes */
void shutdown();
void threads(int /* num threads */);
inline int threads() const { return this->target_threads; }
private:
struct LinkedEntry {
LinkedEntry* previous;
LinkedEntry* next;
inline const std::string& thread_prefix() const { return this->_thread_prefix; }
std::chrono::system_clock::time_point scheduled;
std::weak_ptr<EventEntry> entry;
};
void threads(int /* num threads */);
inline int threads() const { return this->target_threads; }
private:
struct LinkedEntry {
LinkedEntry* previous;
LinkedEntry* next;
static void _executor(EventExecutor*);
void _spawn_executor(std::unique_lock<std::mutex>&);
void _shutdown(std::unique_lock<std::mutex>&);
void _reset_events(std::unique_lock<std::mutex>&);
std::chrono::system_clock::time_point scheduled;
std::weak_ptr<EventEntry> entry;
};
static void _executor(EventExecutor*);
void _spawn_executor(std::unique_lock<std::mutex>&);
void _shutdown(std::unique_lock<std::mutex>&);
void _reset_events(std::unique_lock<std::mutex>&);
#ifndef WIN32
void _reassign_thread_names(std::unique_lock<std::mutex>&);
void _reassign_thread_names(std::unique_lock<std::mutex>&);
#endif
bool should_shutdown = true;
bool should_adjust = false; /* thread adjustments */
int target_threads = 0;
bool should_shutdown = true;
bool should_adjust = false; /* thread adjustments */
int target_threads = 0;
std::vector<std::thread> _threads;
std::mutex lock;
std::condition_variable condition;
std::vector<std::thread> _threads;
std::mutex lock;
std::condition_variable condition;
LinkedEntry* head = nullptr;
LinkedEntry* tail = nullptr;
LinkedEntry* head = nullptr;
LinkedEntry* tail = nullptr;
std::string _thread_prefix;
};
}
std::string _thread_prefix;
};
}

View File

@ -1,6 +1,4 @@
#include <log/LogUtils.h>
#include <cstring>
#include <misc/base64.h>
#include "misc/endianness.h"
#include <misc/digest.h>
#define FIXEDINT_H_INCLUDED
@ -10,7 +8,6 @@
#include <iomanip>
#include "License.h"
using namespace ts;
using namespace license::teamspeak;
using namespace std;
using namespace std::chrono;
@ -70,7 +67,7 @@ LicensePublicKey license::teamspeak::Anonymous::root_key = {
};
size_t license::teamspeak::Anonymous::root_index = 1;
shared_ptr<LicenseChain> license::teamspeak::Anonymous::chain = []() -> shared_ptr<LicenseChain> {
std::shared_ptr<LicenseChain> default_anonymous_chain() {
string error;
auto str = istringstream(string((const char*) default_chain, sizeof(default_chain)));
auto chain = LicenseChain::parse(str, error);
@ -80,7 +77,9 @@ shared_ptr<LicenseChain> license::teamspeak::Anonymous::chain = []() -> shared_p
}
return chain;
}();
}
shared_ptr<LicenseChain> license::teamspeak::Anonymous::chain{default_anonymous_chain()};
#define IOERROR(message) \
do {\
@ -263,7 +262,7 @@ void LicenseChain::print() {
auto key = this->generatePublicKey();
cout << "Public key: " << endl;
hexDump((char*) key.data(), (int) key.length(), (int) key.length(), (int) key.length(), [](string message) { cout << message << endl; });
//hexDump((char*) key.data(), (int) key.length(), (int) key.length(), (int) key.length(), [](string message) { cout << message << endl; });
}
std::string LicenseChain::exportChain() {
@ -309,7 +308,7 @@ std::shared_ptr<LicenseEntry> LicenseChain::addServerEntry(ServerLicenseType typ
}
void LicenseChain::addEphemeralEntry() {
auto entry = make_shared<EphemeralLicenseEntry>();
auto entry = std::make_shared<EphemeralLicenseEntry>();
_ed25519_create_keypair(entry->key.publicKeyData, entry->key.privateKeyData);
entry->key.privateKey = true;
entry->_begin = system_clock::now() - hours(6);
@ -328,9 +327,9 @@ inline string importHash(const std::string& hash) {
memset(buffer, 0, 64);
memcpy(buffer, (void*) hash.data(), 32);
buffer[0] &= 0xF8;
buffer[31] &= 0x3F;
buffer[31] |= 0x40;
buffer[0] &= (uint8_t) 0xF8;
buffer[31] &= (uint8_t) 0x3F;
buffer[31] |= (uint8_t) 0x40;
sc_reduce(buffer);
return string((char*) buffer, 32);
}

View File

@ -34,6 +34,8 @@ namespace license {
return "MyTsIdSign";
case LicenseType::EPHEMERAL:
return "Ephemeral";
case LicenseType::TOKEN:
return "Token";
default:
return "Unknown";
}

View File

@ -1,8 +1,8 @@
#include <algorithm>
#include <cstring>
#include "misc/memtracker.h"
#include "BasicChannel.h"
#include "log/LogUtils.h"
#include "./PermissionManager.h"
#include "./BasicChannel.h"
using namespace std;
using namespace ts;
@ -32,7 +32,6 @@ deque<std::shared_ptr<PermissionTypeEntry>> ts::permission::availablePermissions
make_shared<PermissionTypeEntry>(PermissionType::b_serverinstance_modify_querygroup, PermissionGroup::global_settings, "b_serverinstance_modify_querygroup", "Edit global ServerQuery groups"),
make_shared<PermissionTypeEntry>(PermissionType::b_serverinstance_modify_templates, PermissionGroup::global_settings, "b_serverinstance_modify_templates", "Edit global template groups"),
make_shared<PermissionTypeEntry>(PermissionType::b_virtualserver_select, PermissionGroup::vs_info, "b_virtualserver_select", "Select a virtual server"),
make_shared<PermissionTypeEntry>(PermissionType::b_virtualserver_select_godmode, PermissionGroup::vs_info, "b_virtualserver_select_godmode", "Select a virtual server but be invisible"),
make_shared<PermissionTypeEntry>(PermissionType::b_virtualserver_info_view, PermissionGroup::vs_info, "b_virtualserver_info_view", "Retrieve virtual server information"),
make_shared<PermissionTypeEntry>(PermissionType::b_virtualserver_connectioninfo_view, PermissionGroup::vs_info, "b_virtualserver_connectioninfo_view", "Retrieve virtual server connection information"),
make_shared<PermissionTypeEntry>(PermissionType::b_virtualserver_channel_list, PermissionGroup::vs_info, "b_virtualserver_channel_list", "List channels on a virtual server"),
@ -46,10 +45,11 @@ deque<std::shared_ptr<PermissionTypeEntry>> ts::permission::availablePermissions
make_shared<PermissionTypeEntry>(PermissionType::b_virtualserver_custom_search, PermissionGroup::vs_info, "b_virtualserver_custom_search", "Find custom fields"),
make_shared<PermissionTypeEntry>(PermissionType::b_virtualserver_start, PermissionGroup::vs_admin, "b_virtualserver_start", "Start own virtual server"),
make_shared<PermissionTypeEntry>(PermissionType::b_virtualserver_stop, PermissionGroup::vs_admin, "b_virtualserver_stop", "Stop own virtual server"),
make_shared<PermissionTypeEntry>(PermissionType::b_virtualserver_token_list, PermissionGroup::vs_admin, "b_virtualserver_token_list", "List privilege keys available"),
make_shared<PermissionTypeEntry>(PermissionType::b_virtualserver_token_add, PermissionGroup::vs_admin, "b_virtualserver_token_add", "Create new privilege keys"),
make_shared<PermissionTypeEntry>(PermissionType::b_virtualserver_token_list_all, PermissionGroup::vs_admin, "b_virtualserver_token_list_all", "Allows the client to list all tokens and not only his own"),
make_shared<PermissionTypeEntry>(PermissionType::b_virtualserver_token_edit_all, PermissionGroup::vs_admin, "b_virtualserver_token_edit_all", "Edit all generated tokens"),
make_shared<PermissionTypeEntry>(PermissionType::i_virtualserver_token_limit, PermissionGroup::vs_admin, "i_virtualserver_token_limit", "Max number of pending tokens a client could have"),
make_shared<PermissionTypeEntry>(PermissionType::b_virtualserver_token_use, PermissionGroup::vs_admin, "b_virtualserver_token_use", "Use a privilege keys to gain access to groups"),
make_shared<PermissionTypeEntry>(PermissionType::b_virtualserver_token_delete, PermissionGroup::vs_admin, "b_virtualserver_token_delete", "Delete a privilege key"),
make_shared<PermissionTypeEntry>(PermissionType::b_virtualserver_token_delete_all, PermissionGroup::vs_admin, "b_virtualserver_token_delete_all", "Allows the client to delete all tokens and not only the owned ones"),
make_shared<PermissionTypeEntry>(PermissionType::b_virtualserver_log_view, PermissionGroup::vs_admin, "b_virtualserver_log_view", "Retrieve virtual server log"),
make_shared<PermissionTypeEntry>(PermissionType::b_virtualserver_log_add, PermissionGroup::vs_admin, "b_virtualserver_log_add", "Write to virtual server log"),
make_shared<PermissionTypeEntry>(PermissionType::b_virtualserver_join_ignore_password, PermissionGroup::vs_admin, "b_virtualserver_join_ignore_password", "Join virtual server ignoring its password"),
@ -86,7 +86,6 @@ deque<std::shared_ptr<PermissionTypeEntry>> ts::permission::availablePermissions
make_shared<PermissionTypeEntry>(PermissionType::b_virtualserver_modify_log_settings, PermissionGroup::vs_settings, "b_virtualserver_modify_log_settings", "Modify log settings"),
make_shared<PermissionTypeEntry>(PermissionType::b_virtualserver_modify_min_client_version, PermissionGroup::vs_settings, "b_virtualserver_modify_min_client_version", "Modify min client version"),
make_shared<PermissionTypeEntry>(PermissionType::b_virtualserver_modify_icon_id, PermissionGroup::vs_settings, "b_virtualserver_modify_icon_id", "Modify server icon"),
make_shared<PermissionTypeEntry>(PermissionType::b_virtualserver_modify_weblist, PermissionGroup::vs_settings, "b_virtualserver_modify_weblist", "Modify web server list reporting settings"),
make_shared<PermissionTypeEntry>(PermissionType::b_virtualserver_modify_country_code, PermissionGroup::vs_settings, "b_virtualserver_modify_country_code", "Modify servers country code property"),
make_shared<PermissionTypeEntry>(PermissionType::b_virtualserver_modify_codec_encryption_mode, PermissionGroup::vs_settings, "b_virtualserver_modify_codec_encryption_mode", "Modify codec encryption mode"),
make_shared<PermissionTypeEntry>(PermissionType::b_virtualserver_modify_temporary_passwords, PermissionGroup::vs_settings, "b_virtualserver_modify_temporary_passwords", "Modify temporary serverpasswords"),
@ -104,14 +103,9 @@ deque<std::shared_ptr<PermissionTypeEntry>> ts::permission::availablePermissions
make_shared<PermissionTypeEntry>(PermissionType::b_channel_create_permanent, PermissionGroup::channel_create, "b_channel_create_permanent", "Create permanent channels"),
make_shared<PermissionTypeEntry>(PermissionType::b_channel_create_semi_permanent, PermissionGroup::channel_create, "b_channel_create_semi_permanent", "Create semi-permanent channels"),
make_shared<PermissionTypeEntry>(PermissionType::b_channel_create_temporary, PermissionGroup::channel_create, "b_channel_create_temporary", "Create temporary channels"),
make_shared<PermissionTypeEntry>(PermissionType::b_channel_create_private, PermissionGroup::channel_create, "b_channel_create_private", "Create private channel"),
make_shared<PermissionTypeEntry>(PermissionType::b_channel_create_with_topic, PermissionGroup::channel_create, "b_channel_create_with_topic", "Create channels with a topic"),
make_shared<PermissionTypeEntry>(PermissionType::b_channel_create_with_description, PermissionGroup::channel_create, "b_channel_create_with_description", "Create channels with a description"),
make_shared<PermissionTypeEntry>(PermissionType::b_channel_create_with_password, PermissionGroup::channel_create, "b_channel_create_with_password", "Create password protected channels"),
make_shared<PermissionTypeEntry>(PermissionType::b_channel_create_modify_with_codec_speex8, PermissionGroup::channel_create, "b_channel_create_modify_with_codec_speex8", "Create channels using Speex Narrowband (8 kHz) codecs"),
make_shared<PermissionTypeEntry>(PermissionType::b_channel_create_modify_with_codec_speex16, PermissionGroup::channel_create, "b_channel_create_modify_with_codec_speex16", "Create channels using Speex Wideband (16 kHz) codecs"),
make_shared<PermissionTypeEntry>(PermissionType::b_channel_create_modify_with_codec_speex32, PermissionGroup::channel_create, "b_channel_create_modify_with_codec_speex32", "Create channels using Speex Ultra-Wideband (32 kHz) codecs"),
make_shared<PermissionTypeEntry>(PermissionType::b_channel_create_modify_with_codec_celtmono48, PermissionGroup::channel_create, "b_channel_create_modify_with_codec_celtmono48", "Create channels using the CELT Mono (48 kHz) codec"),
make_shared<PermissionTypeEntry>(PermissionType::b_channel_create_modify_with_codec_opusvoice, PermissionGroup::channel_create, "b_channel_create_modify_with_codec_opusvoice", "Create channels using OPUS (voice) codec"),
make_shared<PermissionTypeEntry>(PermissionType::b_channel_create_modify_with_codec_opusmusic, PermissionGroup::channel_create, "b_channel_create_modify_with_codec_opusmusic", "Create channels using OPUS (music) codec"),
make_shared<PermissionTypeEntry>(PermissionType::i_channel_create_modify_with_codec_maxquality, PermissionGroup::channel_create, "i_channel_create_modify_with_codec_maxquality", "Create channels with custom codec quality"),
@ -125,7 +119,11 @@ deque<std::shared_ptr<PermissionTypeEntry>> ts::permission::availablePermissions
make_shared<PermissionTypeEntry>(PermissionType::i_channel_create_modify_with_temp_delete_delay, PermissionGroup::channel_create, "i_channel_create_modify_with_temp_delete_delay", "Max delete delay for temporary channels"),
make_shared<PermissionTypeEntry>(PermissionType::i_channel_create_modify_conversation_history_length, PermissionGroup::channel_create, "i_channel_create_modify_conversation_history_length", "Upper limmit for the setting of the max conversation history limit"),
make_shared<PermissionTypeEntry>(PermissionType::b_channel_create_modify_conversation_history_unlimited, PermissionGroup::channel_create, "b_channel_create_modify_conversation_history_unlimited", "Allows the user to set the channel conversation history to unlimited"),
make_shared<PermissionTypeEntry>(PermissionType::b_channel_create_modify_conversation_private, PermissionGroup::channel_create, "b_channel_create_modify_conversation_private", "Allows the user to set the channel conversation to private"),
make_shared<PermissionTypeEntry>(PermissionType::b_channel_create_modify_conversation_mode_public, PermissionGroup::channel_create, "b_channel_create_modify_conversation_mode_public", "Allows the user to set the channel conversation mode to public"),
make_shared<PermissionTypeEntry>(PermissionType::b_channel_create_modify_conversation_mode_private, PermissionGroup::channel_create, "b_channel_create_modify_conversation_mode_private", "Allows the user to set the channel conversation mode to private"),
make_shared<PermissionTypeEntry>(PermissionType::b_channel_create_modify_conversation_mode_none, PermissionGroup::channel_create, "b_channel_create_modify_conversation_mode_none", "Allows the user to set the channel conversation mode to none"),
make_shared<PermissionTypeEntry>(PermissionType::b_channel_create_modify_sidebar_mode, PermissionGroup::channel_create, "b_channel_create_modify_sidebar_mode", "Allows the user to change the channels sidebar apperiance"),
make_shared<PermissionTypeEntry>(PermissionType::b_channel_modify_parent, PermissionGroup::channel_modify, "b_channel_modify_parent", "Move channels"),
make_shared<PermissionTypeEntry>(PermissionType::b_channel_modify_make_default, PermissionGroup::channel_modify, "b_channel_modify_make_default", "Make channel default"),
make_shared<PermissionTypeEntry>(PermissionType::b_channel_modify_make_permanent, PermissionGroup::channel_modify, "b_channel_modify_make_permanent", "Make channel permanent"),
@ -321,9 +319,18 @@ deque<std::shared_ptr<PermissionTypeEntry>> ts::permission::availablePermissions
make_shared<PermissionTypeEntry>(PermissionType::i_client_needed_talk_power, PermissionGroup::client_basic, "i_client_needed_talk_power", "Needed client talk power"),
make_shared<PermissionTypeEntry>(PermissionType::i_client_poke_power, PermissionGroup::client_basic, "i_client_poke_power", "Client poke power"),
make_shared<PermissionTypeEntry>(PermissionType::i_client_needed_poke_power, PermissionGroup::client_basic, "i_client_needed_poke_power", "Needed client poke power"),
make_shared<PermissionTypeEntry>(PermissionType::i_client_poke_max_clients, PermissionGroup::client_basic, "i_client_poke_max_clients", "Max amount of clients which could be poked at once"),
make_shared<PermissionTypeEntry>(PermissionType::b_client_set_flag_talker, PermissionGroup::client_basic, "b_client_set_flag_talker", "Set the talker flag for clients and allow them to speak"),
make_shared<PermissionTypeEntry>(PermissionType::i_client_whisper_power, PermissionGroup::client_basic, "i_client_whisper_power", "Client whisper power"),
make_shared<PermissionTypeEntry>(PermissionType::i_client_needed_whisper_power, PermissionGroup::client_basic, "i_client_needed_whisper_power", "Client needed whisper power"),
make_shared<PermissionTypeEntry>(PermissionType::b_video_screen, PermissionGroup::client_basic, "b_video_screen", "Client can show his screen"),
make_shared<PermissionTypeEntry>(PermissionType::b_video_camera, PermissionGroup::client_basic, "b_video_camera", "Client can show his video camera"),
make_shared<PermissionTypeEntry>(PermissionType::i_video_max_kbps, PermissionGroup::client_basic, "i_video_max_kbps", "The maximal bandwidth used by the client to transmit video"),
make_shared<PermissionTypeEntry>(PermissionType::i_video_max_streams, PermissionGroup::client_basic, "i_video_max_streams", "The maximal number of streams a client can simultaneously receive"),
make_shared<PermissionTypeEntry>(PermissionType::i_video_max_screen_streams, PermissionGroup::client_basic, "i_video_max_screen_streams", "The maximal number of video streams a client can simultaneously receive"),
make_shared<PermissionTypeEntry>(PermissionType::i_video_max_camera_streams, PermissionGroup::client_basic, "i_video_max_camera_streams", "The maximal number of camera streams a client can simultaneously receive"),
make_shared<PermissionTypeEntry>(PermissionType::b_client_modify_description, PermissionGroup::client_modify, "b_client_modify_description", "Edit a clients description"),
make_shared<PermissionTypeEntry>(PermissionType::b_client_modify_own_description, PermissionGroup::client_modify, "b_client_modify_own_description", "Allow client to edit own description"),
make_shared<PermissionTypeEntry>(PermissionType::b_client_modify_dbproperties, PermissionGroup::client_modify, "b_client_modify_dbproperties", "Edit a clients properties in the sql"),
@ -396,7 +403,10 @@ deque<std::shared_ptr<PermissionTypeEntry>> ts::permission::availablePermissions
make_shared<PermissionTypeEntry>(PermissionType::i_ft_directory_create_power, PermissionGroup::ft, "i_ft_directory_create_power", "Create directory power"),
make_shared<PermissionTypeEntry>(PermissionType::i_ft_needed_directory_create_power, PermissionGroup::ft, "i_ft_needed_directory_create_power", "Needed create directory power"),
make_shared<PermissionTypeEntry>(PermissionType::i_ft_quota_mb_download_per_client, PermissionGroup::ft, "i_ft_quota_mb_download_per_client", "Download quota per client in MByte"),
make_shared<PermissionTypeEntry>(PermissionType::i_ft_quota_mb_upload_per_client, PermissionGroup::ft, "i_ft_quota_mb_upload_per_client", "Upload quota per client in MByte")
make_shared<PermissionTypeEntry>(PermissionType::i_ft_quota_mb_upload_per_client, PermissionGroup::ft, "i_ft_quota_mb_upload_per_client", "Upload quota per client in MByte"),
make_shared<PermissionTypeEntry>(PermissionType::i_ft_max_bandwidth_download, PermissionGroup::ft, "i_ft_max_bandwidth_download", "Maximal download bandwidth allowed for the client"),
make_shared<PermissionTypeEntry>(PermissionType::i_ft_max_bandwidth_upload, PermissionGroup::ft, "i_ft_max_bandwidth_upload", "Maximal download bandwidth allowed for the client")
};
deque<PermissionType> ts::permission::neededPermissions = {
@ -473,7 +483,10 @@ i_group_modify_power,
b_virtualserver_modify_default_messages,
i_channel_create_modify_conversation_history_length,
b_channel_create_modify_conversation_history_unlimited,
b_channel_create_modify_conversation_private,
b_channel_create_modify_conversation_mode_public,
b_channel_create_modify_conversation_mode_private,
b_channel_create_modify_conversation_mode_none,
b_channel_create_modify_sidebar_mode,
b_channel_modify_name,
b_channel_modify_password,
b_channel_modify_topic,
@ -499,10 +512,6 @@ i_group_modify_power,
b_channel_create_with_maxfamilyclients,
b_channel_create_with_sortorder,
b_channel_create_with_default,
b_channel_create_modify_with_codec_speex8,
b_channel_create_modify_with_codec_speex16,
b_channel_create_modify_with_codec_speex32,
b_channel_create_modify_with_codec_celtmono48,
b_channel_create_modify_with_codec_opusvoice,
b_channel_create_modify_with_codec_opusmusic,
i_channel_create_modify_with_codec_maxquality,
@ -517,10 +526,17 @@ i_group_modify_power,
b_channel_delete_flag_force,
b_client_set_flag_talker,
b_channel_create_with_needed_talk_power,
b_virtualserver_token_list,
b_virtualserver_token_add,
b_virtualserver_token_list_all,
i_virtualserver_token_limit,
b_virtualserver_token_use,
b_virtualserver_token_delete,
b_virtualserver_token_delete_all,
b_video_screen,
b_video_camera,
i_video_max_kbps,
i_video_max_streams,
i_video_max_screen_streams,
i_video_max_camera_streams,
/* ban functions */
b_client_ban_create,
@ -564,7 +580,6 @@ i_group_modify_power,
b_client_permissionoverview_own,
i_ft_quota_mb_upload_per_client,
i_ft_quota_mb_download_per_client,
b_virtualserver_modify_weblist,
b_virtualserver_modify_country_code,
b_virtualserver_channelgroup_delete,
b_virtualserver_servergroup_delete,
@ -577,7 +592,6 @@ i_group_modify_power,
b_client_request_talker,
b_client_avatar_delete_other,
b_channel_create_modify_with_force_password,
b_channel_create_private,
b_channel_join_ignore_maxclients,
b_virtualserver_modify_channel_temp_delete_delay_default,
i_channel_create_modify_with_temp_delete_delay,
@ -875,7 +889,6 @@ teamspeak::MapType build_mapping(){
{"", {"b_virtualserver_modify_default_messages"}},
{"b_client_ban_list", {"b_client_ban_list", "b_client_ban_trigger_list"}},
{"b_virtualserver_select", {"b_virtualserver_select", "b_virtualserver_select_godmode"}},
{"b_virtualserver_modify_default_servergroup", {"b_virtualserver_modify_default_servergroup", "b_virtualserver_modify_default_musicgroup"}},
{"b_client_ban_create", {"b_client_ban_create", "b_client_ban_name", "b_client_ban_ip", "b_client_ban_hwid"}}
@ -964,6 +977,9 @@ inline std::deque<std::string> map_entry(std::string key, teamspeak::GroupType t
result.insert(result.end(), mapped_general.begin(), mapped_general.end());
}
if(result.empty())
result.push_back("x_" + key);
for(auto& entry : result)
entry = "i_needed_modify_power_" + entry.substr(2);
return result;
@ -1069,7 +1085,6 @@ deque<update::UpdateEntry> update::migrate = {
AQB("b_virtualserver_modify_default_messages")
AQB("b_virtualserver_modify_default_musicgroup")
AQB("b_channel_ignore_join_power")
AQB("b_virtualserver_select_godmode")
AQB("b_client_ban_trigger_list")
};
@ -1201,14 +1216,41 @@ const v2::PermissionContainer v2::PermissionManager::channel_permission(const Pe
return empty_channel_permission;
}
void v2::PermissionManager::set_permission(const PermissionType &permission, const v2::PermissionValues &values, const v2::PermissionUpdateType &action_value, const v2::PermissionUpdateType &action_grant, int flag_skip, int flag_negate) {
inline v2::PermissionContainer duplicate_permission_container(const v2::PermissionContainer& original) {
v2::PermissionContainer result{};
result.flags = original.flags;
result.values.grant = original.values.grant;
result.values.value = original.values.value;
return result;
}
static v2::PermissionContainer kEmptyPermissionContainer{
.flags = v2::PermissionFlags{
.database_reference = false,
.channel_specific = false,
.value_set = false,
.grant_set = false,
.skip = false,
.negate = false,
.flag_value_update = false,
.flag_grant_update = false
},
.values = v2::PermissionValues{0, 0}
};
v2::PermissionContainer v2::PermissionManager::set_permission(const PermissionType &permission, const v2::PermissionValues &values, const v2::PermissionUpdateType &action_value, const v2::PermissionUpdateType &action_grant, int flag_skip, int flag_negate) {
if(permission < 0 || permission >= PermissionType::permission_id_max)
return;
return kEmptyPermissionContainer;
const auto block = this->calculate_block(permission);
this->ref_allocate_block(block);
auto& data = this->block_containers[block]->permissions[this->calculate_block_index(permission)];
auto old_state = duplicate_permission_container(data);
if(action_value == v2::PermissionUpdateType::set_value) {
data.flags.value_set = true;
data.flags.flag_value_update = true;
@ -1241,11 +1283,13 @@ void v2::PermissionManager::set_permission(const PermissionType &permission, con
this->unref_block(block);
this->trigger_db_update();
return old_state;
}
void v2::PermissionManager::set_channel_permission(const PermissionType &permission, ChannelId channel_id, const v2::PermissionValues &values, const v2::PermissionUpdateType &action_value, const v2::PermissionUpdateType &action_grant, int flag_skip, int flag_negate) {
v2::PermissionContainer v2::PermissionManager::set_channel_permission(const PermissionType &permission, ChannelId channel_id, const v2::PermissionValues &values, const v2::PermissionUpdateType &action_value, const v2::PermissionUpdateType &action_grant, int flag_skip, int flag_negate) {
if(permission < 0 || permission >= PermissionType::permission_id_max)
return;
return kEmptyPermissionContainer;
unique_lock channel_perm_lock(this->channel_list_lock);
ChannelPermissionContainer* permission_container = nullptr;
@ -1255,13 +1299,13 @@ void v2::PermissionManager::set_channel_permission(const PermissionType &permiss
break;
}
/* register a new permission if we have no permission already*/
if(!permission_container || !permission_container->flags.permission_set()) { /* if the permission isn't set then we have to register it again */
if(action_value != v2::PermissionUpdateType::set_value && action_grant == v2::PermissionUpdateType::set_value) {
return; /* we were never willing to set this permission */
/* register a new permission if we have no permission already */
if(!permission_container) { /* if the permission isn't set then we have to register it again */
if(action_value != v2::PermissionUpdateType::set_value && action_grant != v2::PermissionUpdateType::set_value) {
return kEmptyPermissionContainer; /* we were never willing to set this permission */
}
if(!permission_container) {
{
auto container = make_unique<ChannelPermissionContainer>();
container->permission = permission;
container->channel_id = channel_id;
@ -1270,13 +1314,16 @@ void v2::PermissionManager::set_channel_permission(const PermissionType &permiss
}
/* now set the channel flag for that permission */
const auto block = this->calculate_block(permission);
this->ref_allocate_block(block);
{
const auto block = this->calculate_block(permission);
this->ref_allocate_block(block);
auto& data = this->block_containers[block]->permissions[this->calculate_block_index(permission)];
data.flags.channel_specific = true;
this->unref_block(block);
auto& data = this->block_containers[block]->permissions[this->calculate_block_index(permission)];
data.flags.channel_specific = true;
this->unref_block(block);
}
}
auto old_state = duplicate_permission_container(*permission_container);
if(action_value == v2::PermissionUpdateType::set_value) {
permission_container->flags.value_set = true;
@ -1317,6 +1364,7 @@ void v2::PermissionManager::set_channel_permission(const PermissionType &permiss
}
}
this->trigger_db_update();
return old_state;
}
const std::vector<std::tuple<PermissionType, const v2::PermissionContainer>> v2::PermissionManager::permissions() {
@ -1390,9 +1438,11 @@ const std::vector<v2::PermissionDBUpdateEntry> v2::PermissionManager::flush_db_u
{
lock_guard use_lock(this->block_use_count_lock);
size_t block_count = 0;
for (auto &block_container : block_containers)
if (block_container)
for (auto &block_container : block_containers) {
if (block_container) {
block_count++;
}
}
result.reserve(block_count * PERMISSIONS_BULK_ENTRY_COUNT);
for(size_t block_index = 0; block_index < BULK_COUNT; block_index++) {

View File

@ -12,10 +12,9 @@
#include <shared_mutex>
#include <cassert>
#include <cstring> /* for memset */
#include "./misc/spin_lock.h"
#include "misc/spin_mutex.h"
#include "Definitions.h"
#include "Variable.h"
#include "spdlog/fmt/ostr.h" // must be included
#define permNotGranted (-2)
#define PERM_ID_GRANT ((ts::permission::PermissionType) (1U << 15U))
@ -49,7 +48,6 @@ namespace ts {
b_serverinstance_binding_list,
b_serverinstance_permission_list,
b_serverinstance_permission_find,
//b_serverinstance_allow_teaspeak_plugin,
/* global::vs_management */
b_virtualserver_create,
@ -75,7 +73,6 @@ namespace ts {
/* virtual_server::information */
b_virtualserver_select,
b_virtualserver_select_godmode,
b_virtualserver_info_view,
b_virtualserver_connectioninfo_view,
b_virtualserver_channel_list,
@ -91,10 +88,11 @@ namespace ts {
/* virtual_server::administration */
b_virtualserver_start,
b_virtualserver_stop,
b_virtualserver_token_list,
b_virtualserver_token_add,
b_virtualserver_token_list_all,
i_virtualserver_token_limit,
b_virtualserver_token_edit_all,
b_virtualserver_token_use,
b_virtualserver_token_delete,
b_virtualserver_token_delete_all,
b_virtualserver_log_view,
b_virtualserver_log_add,
b_virtualserver_join_ignore_password,
@ -134,7 +132,6 @@ namespace ts {
b_virtualserver_modify_log_settings,
b_virtualserver_modify_min_client_version,
b_virtualserver_modify_icon_id,
b_virtualserver_modify_weblist,
b_virtualserver_modify_country_code,
b_virtualserver_modify_codec_encryption_mode,
b_virtualserver_modify_temporary_passwords,
@ -158,14 +155,9 @@ namespace ts {
b_channel_create_permanent,
b_channel_create_semi_permanent,
b_channel_create_temporary,
b_channel_create_private,
b_channel_create_with_topic,
b_channel_create_with_description,
b_channel_create_with_password,
b_channel_create_modify_with_codec_speex8,
b_channel_create_modify_with_codec_speex16,
b_channel_create_modify_with_codec_speex32,
b_channel_create_modify_with_codec_celtmono48,
b_channel_create_modify_with_codec_opusvoice,
b_channel_create_modify_with_codec_opusmusic,
i_channel_create_modify_with_codec_maxquality,
@ -179,7 +171,10 @@ namespace ts {
i_channel_create_modify_with_temp_delete_delay,
i_channel_create_modify_conversation_history_length,
b_channel_create_modify_conversation_history_unlimited,
b_channel_create_modify_conversation_private,
b_channel_create_modify_conversation_mode_private,
b_channel_create_modify_conversation_mode_public,
b_channel_create_modify_conversation_mode_none,
b_channel_create_modify_sidebar_mode,
/* channel::modify */
b_channel_modify_parent,
@ -424,9 +419,16 @@ namespace ts {
i_client_needed_talk_power,
i_client_poke_power,
i_client_needed_poke_power,
i_client_poke_max_clients,
b_client_set_flag_talker,
i_client_whisper_power,
i_client_needed_whisper_power,
b_video_screen,
b_video_camera,
i_video_max_kbps,
i_video_max_streams,
i_video_max_screen_streams,
i_video_max_camera_streams,
/* client::modify */
b_client_modify_description,
@ -466,6 +468,8 @@ namespace ts {
i_ft_needed_directory_create_power,
i_ft_quota_mb_download_per_client,
i_ft_quota_mb_upload_per_client,
i_ft_max_bandwidth_download,
i_ft_max_bandwidth_upload,
permission_id_max
};
@ -490,7 +494,7 @@ namespace ts {
channel = i_channel_needed_permission_modify_power,
channel_info = b_virtualserver_channel_permission_list,
channel_create = b_channel_create_modify_conversation_private,
channel_create = b_channel_create_modify_conversation_mode_none,
channel_modify = b_channel_modify_temp_delete_delay,
channel_delete = i_channel_needed_delete_power,
channel_access = b_channel_ignore_description_view_power,
@ -513,7 +517,7 @@ namespace ts {
client_admin = i_client_ban_max_bantime,
client_basic = i_client_needed_whisper_power,
client_modify = b_client_query_delete_own,
ft = i_ft_quota_mb_upload_per_client,
ft = i_ft_max_bandwidth_upload,
group_end
};
@ -552,6 +556,8 @@ namespace ts {
bool clientSupported = true;
[[nodiscard]] inline bool is_invalid() const { return this->type == permission::undefined || this->type == permission::unknown; }
// PermissionTypeEntry(PermissionTypeEntry&& ref) : type(ref.type), group(ref.group), name(ref.name), description(ref.description), clientSupported(ref.clientSupported) {}
//PermissionTypeEntry(const PermissionTypeEntry& ref) : type(ref.type), group(ref.group), name(ref.name), description(ref.description), clientSupported(ref.clientSupported) {}
PermissionTypeEntry(PermissionTypeEntry&& ref) = delete;
@ -563,7 +569,7 @@ namespace ts {
name(std::move(name)),
description(std::move(description)),
clientSupported(clientSupported) {
this->grant_name = std::string() + (this->name[0] == 'i' ? "i" : "i") + "_needed_modify_power_" + this->name.substr(2);
this->grant_name = "i_needed_modify_power_" + this->name.substr(2);
}
};
@ -752,7 +758,7 @@ namespace ts {
bool flag_value_update: 1;
bool flag_grant_update: 1;
ts_always_inline bool permission_set() {
[[nodiscard]] ts_always_inline bool permission_set() const {
return this->value_set || this->grant_set;
}
};
@ -818,6 +824,28 @@ namespace ts {
[[nodiscard]] constexpr bool has_power() const { return this->has_value && (this->value > 0 || this->value == -1); }
[[nodiscard]] constexpr bool has_infinite_power() const { return this->has_value && this->value == -1; }
constexpr bool clear_flag_on_zero() {
if(this->has_value && this->value == 0) {
this->has_value = false;
return true;
}
return false;
}
/**
* Set the permission value to zero if the permission hasn't been set.
* This could be used to check if a client could do an action on another client
* but the client requires at least some power.
* @return
*/
constexpr auto& zero_if_unset() {
if(!this->has_value) {
this->has_value = true;
this->value = 0;
}
return *this;
}
inline bool operator==(const PermissionFlaggedValue& other) const { return other.value == this->value && other.has_value == this->has_value; }
inline bool operator!=(const PermissionFlaggedValue& other) const { return !(*this == other); }
@ -825,9 +853,10 @@ namespace ts {
static constexpr PermissionFlaggedValue empty_permission_flagged_value{0, false};
static constexpr bool permission_granted(const PermissionFlaggedValue& required, const PermissionFlaggedValue& given, bool requires_given = true) {
static constexpr bool permission_granted(const PermissionFlaggedValue& required, const PermissionFlaggedValue& given) {
if(!required.has_value) {
return !requires_given || given.has_power();
/* The target permission hasn't been set so just check if we've not negated the target */
return !given.has_value || given.value >= 0;
} else if(!given.has_power()) {
return false;
} else if(given.has_infinite_power()) {
@ -838,8 +867,8 @@ namespace ts {
return given.value >= required.value;
}
}
static constexpr bool permission_granted(const PermissionValue& required, const PermissionFlaggedValue& given, bool requires_given = true) {
return permission_granted({required, true}, given, requires_given);
static constexpr bool permission_granted(const PermissionValue& required, const PermissionFlaggedValue& given) {
return permission_granted({required, true}, given);
}
class PermissionManager {
@ -858,24 +887,24 @@ namespace ts {
/* general getters/setters */
const PermissionFlags permission_flags(const PermissionType&); /* we return a "copy" because the actual permission could be deleted while we're analyzing the flags */
ts_always_inline const PermissionFlags permission_flags(const std::shared_ptr<PermissionTypeEntry>& permission_info) { return this->permission_flags(permission_info->type); }
ts_always_inline PermissionFlags permission_flags(const std::shared_ptr<PermissionTypeEntry>& permission_info) { return this->permission_flags(permission_info->type); }
const PermissionValues permission_values(const PermissionType&);
ts_always_inline const PermissionValues permission_values(const std::shared_ptr<PermissionTypeEntry>& permission_info) { return this->permission_values(permission_info->type); }
ts_always_inline PermissionValues permission_values(const std::shared_ptr<PermissionTypeEntry>& permission_info) { return this->permission_values(permission_info->type); }
const PermissionFlaggedValue permission_value_flagged(const PermissionType&);
ts_always_inline const PermissionFlaggedValue permission_value_flagged(const std::shared_ptr<PermissionTypeEntry>& permission_info) { return this->permission_value_flagged(permission_info->type); }
ts_always_inline PermissionFlaggedValue permission_value_flagged(const std::shared_ptr<PermissionTypeEntry>& permission_info) { return this->permission_value_flagged(permission_info->type); }
const PermissionFlaggedValue permission_granted_flagged(const PermissionType&);
ts_always_inline const PermissionFlaggedValue permission_granted_flagged(const std::shared_ptr<PermissionTypeEntry>& permission_info) { return this->permission_granted_flagged(permission_info->type); }
ts_always_inline PermissionFlaggedValue permission_granted_flagged(const std::shared_ptr<PermissionTypeEntry>& permission_info) { return this->permission_granted_flagged(permission_info->type); }
/* only worth looking up if channel_specific is set */
const PermissionContainer channel_permission(const PermissionType& /* permission */, ChannelId /* channel id */);
ts_always_inline const PermissionContainer channel_permission(const std::shared_ptr<PermissionTypeEntry>& permission_info, ChannelId channel_id) { return this->channel_permission(permission_info->type, channel_id); }
ts_always_inline PermissionContainer channel_permission(const std::shared_ptr<PermissionTypeEntry>& permission_info, ChannelId channel_id) { return this->channel_permission(permission_info->type, channel_id); }
/* modifiers */
void set_permission(const PermissionType& /* permission */, const PermissionValues& /* values */, const PermissionUpdateType& /* update value */, const PermissionUpdateType& /* update grant */, int /* flag skip */ = -1, int /* flag negate */ = -1);
void set_channel_permission(const PermissionType& /* permission */, ChannelId /* channel id */, const PermissionValues& /* values */, const PermissionUpdateType& /* update value */, const PermissionUpdateType& /* update grant */, int /* flag skip */ = -1, int /* flag negate */ = -1);
PermissionContainer set_permission(const PermissionType& /* permission */, const PermissionValues& /* values */, const PermissionUpdateType& /* update value */, const PermissionUpdateType& /* update grant */, int /* flag skip */ = -1, int /* flag negate */ = -1);
PermissionContainer set_channel_permission(const PermissionType& /* permission */, ChannelId /* channel id */, const PermissionValues& /* values */, const PermissionUpdateType& /* update value */, const PermissionUpdateType& /* update grant */, int /* flag skip */ = -1, int /* flag negate */ = -1);
/* bulk info */
const std::vector<std::tuple<PermissionType, const PermissionContainer>> permissions();
@ -893,7 +922,7 @@ namespace ts {
bool requires_db_save = false;
ts_always_inline void trigger_db_update() { this->requires_db_save = true; }
spin_lock block_use_count_lock{};
spin_mutex block_use_count_lock{};
int16_t block_use_count[BULK_COUNT];
PermissionContainerBulk<PERMISSIONS_BULK_ENTRY_COUNT>* block_containers[BULK_COUNT];

View File

@ -1,8 +1,6 @@
#include <algorithm>
#include <mutex>
#include <array>
#include <utility>
#include "log/LogUtils.h"
#include "misc/memtracker.h"
#include "Properties.h"
@ -10,68 +8,76 @@ using namespace ts;
using namespace ts::property;
using namespace std;
Properties::Properties() {
memtrack::allocated<Properties>(this);
PropertyManager::PropertyManager() {
memtrack::allocated<PropertyManager>(this);
}
Properties::~Properties() {
memtrack::freed<Properties>(this);
PropertyManager::~PropertyManager() {
memtrack::freed<PropertyManager>(this);
}
bool Properties::has(property::PropertyType type, int index) {
bool PropertyManager::has(property::PropertyType type, int index) {
for(auto it = this->properties.begin(); it != this->properties.end(); it++) {
if(!*it) continue;
if(it->get()->type != type) continue;
auto& bundle = *it;
if(bundle->type != type) {
continue;
}
return index < it->get()->length;
return index < bundle->property_count;
}
return false;
}
PropertyWrapper Properties::find(property::PropertyType type, int index) {
Property PropertyManager::get(property::PropertyType type, int index) {
for (auto &bulk : this->properties) {
if(!bulk) continue;
if(bulk->type != type)
if(bulk->type != type) {
continue;
}
if(index >= bulk->length)
if(index >= bulk->property_count) {
break;
}
return PropertyWrapper{this, &bulk->properties[index], bulk};
return Property{this->weak_from_this().lock(), &bulk->properties[index], bulk};
}
throw std::invalid_argument("missing property type");
}
std::vector<PropertyWrapper> Properties::list_properties(ts::property::flag_type flagMask, ts::property::flag_type negatedFlagMask) {
vector<PropertyWrapper> result;
std::vector<Property> PropertyManager::list_properties(ts::property::flag_type flagMask, ts::property::flag_type negatedFlagMask) {
std::vector<Property> result{};
result.reserve(this->properties_count);
auto self_ref = this->weak_from_this().lock();
for (auto &bulk : this->properties) {
for(int index = 0; index < bulk->length; index++) {
for(int index = 0; index < bulk->property_count; index++) {
auto& property = bulk->properties[index];
if((property.description->flags & flagMask) > 0 && (property.description->flags & negatedFlagMask) == 0)
result.emplace_back(this, &property, bulk);
if((property.description->flags & flagMask) > 0 && (property.description->flags & negatedFlagMask) == 0) {
result.emplace_back(self_ref, &property, bulk);
}
}
}
return result;
}
std::vector<PropertyWrapper> Properties::all_properties() {
vector<PropertyWrapper> result;
std::vector<Property> PropertyManager::all_properties() {
std::vector<Property> result{};
result.reserve(this->properties_count);
auto self_ref = this->weak_from_this().lock();
for (auto &bulk : this->properties) {
for(int index = 0; index < bulk->length; index++)
result.emplace_back(this, &bulk->properties[index], bulk);
for(int index = 0; index < bulk->property_count; index++) {
result.emplace_back(self_ref, &bulk->properties[index], bulk);
}
}
return result;
}
PropertyWrapper::PropertyWrapper(ts::Properties* handle, ts::PropertyData *data, std::shared_ptr<ts::PropertyBundle> bundle_lock) : handle{handle}, data_ptr{data}, bundle_lock{std::move(bundle_lock)} {
Property::Property(std::shared_ptr<PropertyManager> handle, ts::PropertyData *data, std::shared_ptr<ts::PropertyBundle> bundle_lock)
: handle{std::move(handle)}, property_data{data}, bundle_lock{std::move(bundle_lock)} {
}
void PropertyWrapper::trigger_update() {
this->data_ptr->flag_modified = true;
void Property::trigger_update() {
this->property_data->flag_modified = true;
if(this->handle) {
for(const auto& elm : this->handle->notifyFunctions)
@ -79,43 +85,48 @@ void PropertyWrapper::trigger_update() {
}
}
bool Properties::register_property_type(ts::property::PropertyType type, size_t length) {
for(auto& bulk : this->properties)
if(bulk->type == type)
return false;
void PropertyManager::do_register_property_type(ts::property::PropertyType type, size_t length) {
for(auto& bulk : this->properties) {
if(bulk->type == type) {
return;
}
}
const auto alloc_length = sizeof(PropertyBundle) + sizeof(PropertyData) * length;
auto ptr = shared_ptr<PropertyBundle>((PropertyBundle*) malloc(alloc_length), [](PropertyBundle* bundle) {
if(!bundle) return;
if(!bundle) {
return;
}
for(int index = 0; index < bundle->length; index++) {
for(int index = 0; index < bundle->property_count; index++) {
auto& property = bundle->properties[index];
property.value.~string();
property.value_lock.~spin_lock();
property.casted_value.~any();
}
bundle->value_mutex.~mutex();
::free(bundle);
});
new (&ptr->value_mutex) std::mutex{};
ptr->type = type;
ptr->length = length;
ptr->property_count = length;
for(int index = 0; index < length; index++) {
auto& property = ptr->properties[index];
new (&property.casted_value) any();
new (&property.value_lock) spin_lock();
new (&property.value) string();
new (&property.casted_value) any{};
new (&property.value) string{};
property.description = &property::describe(type, index);
property.flag_modified = false;
property.flag_db_reference = false;
property.flag_database_reference = false;
property.value = property.description->default_value;
this->properties_count++;
}
this->properties.push_back(ptr);
return false;
return;
}
namespace ts {

View File

@ -2,7 +2,6 @@
#include <utility>
#include "misc/memtracker.h"
#include <ThreadPool/Mutex.h>
#include "Variable.h"
#include <map>
#include <deque>
@ -14,8 +13,10 @@
#include <functional>
#include <any>
#include <array>
#include <optional>
#include <type_traits>
#include "misc/spin_lock.h"
#include "misc/spin_mutex.h"
#include "converters/converter.h"
#ifdef NDEBUG
@ -38,13 +39,14 @@ namespace ts {
PROP_TYPE_MAX
};
static constexpr const char *PropertyType_Names[7] = {
static constexpr const char *PropertyType_Names[8] = {
"SERVER",
"CHANNEL",
"GROUP",
"CLIENT",
"INSTANCE",
"CONNECTION",
"PLAYLIST",
"UNKNOWN"
};
@ -57,6 +59,15 @@ namespace ts {
TYPE_FLOAT
};
static constexpr const char *ValueType_Names[6] = {
"UNKNOWN",
"STRING",
"BOOL",
"SIGNED_NUMBER",
"UNSIGNED_NUMBER",
"FLOAT"
};
typedef uint32_t flag_type;
enum flag : flag_type {
FLAG_BEGIN = 0b1,
@ -201,7 +212,6 @@ namespace ts {
VIRTUALSERVER_TOTAL_PACKETLOSS_CONTROL, //only available on request (=> requestServerVariables)
VIRTUALSERVER_TOTAL_PACKETLOSS_TOTAL, //only available on request (=> requestServerVariables)
VIRTUALSERVER_TOTAL_PING, //only available on request (=> requestServerVariables)
VIRTUALSERVER_WEBLIST_ENABLED, //only available on request (=> requestServerVariables)
VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY, //internal use
VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY, //available when connected
VIRTUALSERVER_CHANNEL_TEMP_DELETE_DELAY_DEFAULT, //available when connected, always up-to-date
@ -237,7 +247,7 @@ namespace ts {
CHANNEL_BEGINMARKER,
CHANNEL_ID = CHANNEL_BEGINMARKER,
CHANNEL_PID,
CHANNEL_NAME, //Available for all channels that are "in view", always up-to-date
CHANNEL_NAME, //Available for all channels that are "in view", always up-to-date
CHANNEL_TOPIC, //Available for all channels that are "in view", always up-to-date
CHANNEL_DESCRIPTION, //Must be requested (=> requestChannelDescription)
CHANNEL_PASSWORD, //not available client side
@ -270,7 +280,9 @@ namespace ts {
CHANNEL_CREATED_BY,
CHANNEL_CONVERSATION_HISTORY_LENGTH,
CHANNEL_FLAG_CONVERSATION_PRIVATE,
CHANNEL_CONVERSATION_MODE,
CHANNEL_SIDEBAR_MODE,
CHANNEL_ENDMARKER
};
@ -303,18 +315,18 @@ namespace ts {
CLIENT_OUTPUT_HARDWARE, //automatically up-to-date for any client "in view", this clients headphone/speakers hardware status (is the playback device opened?)
CLIENT_DEFAULT_CHANNEL, //only usable for ourself, the default channel we used to connect on our last connection attempt
CLIENT_DEFAULT_CHANNEL_PASSWORD, //internal use
CLIENT_SERVER_PASSWORD, //internal use
CLIENT_SERVER_PASSWORD, //internal use (Passed within the clientinit, will not be stored)
CLIENT_META_DATA, //automatically up-to-date for any client "in view", not used by TeamSpeak, free storage for sdk users
CLIENT_IS_RECORDING, //automatically up-to-date for any client "in view"
CLIENT_VERSION_SIGN, //sign
CLIENT_VERSION_SIGN, //sign (will currently not be set within the clientinit method!)
CLIENT_SECURITY_HASH, //SDK use, not used by teamspeak. Hash is provided by an outside source. A channel will use the security salt + other client data to calculate a hash, which must be the same as the one provided here.
//Rare properties
CLIENT_KEY_OFFSET, //internal use
CLIENT_KEY_OFFSET, //internal use (Passed within the clientinit, will not be stored)
CLIENT_LOGIN_NAME, //used for serverquery clients, makes no sense on normal clients currently
CLIENT_LOGIN_PASSWORD, //used for serverquery clients, makes no sense on normal clients currently
CLIENT_DATABASE_ID, //automatically up-to-date for any client "in view", only valid with PERMISSION feature, holds database client id
CLIENT_ID, //clid!
CLIENT_ID, //clid!
CLIENT_HARDWARE_ID, //hwid!
CLIENT_CHANNEL_GROUP_ID, //automatically up-to-date for any client "in view", only valid with PERMISSION feature, holds database client id
CLIENT_SERVERGROUPS, //automatically up-to-date for any client "in view", only valid with PERMISSION feature, holds all servergroups client belongs too
@ -370,6 +382,10 @@ namespace ts {
CLIENT_ENDMARKER
};
/*
* These properties are only for using with the bulk builder.
* They're not registered in any PropertyManager
*/
enum ConnectionProperties {
CONNECTION_UNDEFINED,
CONNECTION_BEGINMARKER,
@ -485,31 +501,55 @@ namespace ts {
}
template<typename>
constexpr inline PropertyType type_from_enum();
struct type_from_enum_t {
constexpr static auto supported{false};
constexpr static auto type{PropertyType::PROP_TYPE_UNKNOWN};
};
template<>
constexpr inline PropertyType type_from_enum<VirtualServerProperties>() { return PropertyType::PROP_TYPE_SERVER; }
struct type_from_enum_t<VirtualServerProperties> {
constexpr static auto supported{true};
constexpr static auto type{PropertyType::PROP_TYPE_SERVER};
};
template<>
constexpr inline PropertyType type_from_enum<ChannelProperties>() { return PropertyType::PROP_TYPE_CHANNEL; }
struct type_from_enum_t<ChannelProperties> {
constexpr static auto supported{true};
constexpr static auto type{PropertyType::PROP_TYPE_CHANNEL};
};
template<>
constexpr inline PropertyType type_from_enum<ClientProperties>() { return PropertyType::PROP_TYPE_CLIENT; }
struct type_from_enum_t<ClientProperties> {
constexpr static auto supported{true};
constexpr static auto type{PropertyType::PROP_TYPE_CLIENT};
};
template<>
constexpr inline PropertyType type_from_enum<ConnectionProperties>() { return PropertyType::PROP_TYPE_CONNECTION; }
struct type_from_enum_t<ConnectionProperties> {
constexpr static auto supported{true};
constexpr static auto type{PropertyType::PROP_TYPE_CONNECTION};
};
template<>
constexpr inline PropertyType type_from_enum<GroupProperties>() { return PropertyType::PROP_TYPE_GROUP; }
struct type_from_enum_t<GroupProperties> {
constexpr static auto supported{true};
constexpr static auto type{PropertyType::PROP_TYPE_GROUP};
};
template<>
constexpr inline PropertyType type_from_enum<InstanceProperties>() { return PropertyType::PROP_TYPE_INSTANCE; }
struct type_from_enum_t<InstanceProperties> {
constexpr static auto supported{true};
constexpr static auto type{PropertyType::PROP_TYPE_INSTANCE};
};
template<>
constexpr inline PropertyType type_from_enum<PlaylistProperties>() { return PropertyType::PROP_TYPE_PLAYLIST; }
struct type_from_enum_t<PlaylistProperties> {
constexpr static auto supported{true};
constexpr static auto type{PropertyType::PROP_TYPE_PLAYLIST};
};
template<>
constexpr inline PropertyType type_from_enum<UnknownProperties>() { return PropertyType::PROP_TYPE_UNKNOWN; }
template<typename T>
constexpr inline PropertyType type_from_enum() { return type_from_enum_t<T>::type; }
struct PropertyDescription {
std::string_view name{};
@ -535,7 +575,7 @@ namespace ts {
template <typename PropertyEnumType, typename std::enable_if<std::is_enum<PropertyEnumType>::value, int>::type = 0>
constexpr inline bool operator==(const PropertyEnumType& other) const {
return this->property_index == (int) other && this->type_property == type_from_enum<PropertyEnumType>();
return this->property_index == (int) other && this->type_property == type_from_enum_t<PropertyEnumType>::type;
}
[[nodiscard]] inline bool is_undefined() const { return property_index == 0; }
@ -635,20 +675,27 @@ namespace ts {
}
inline const auto& find(PropertyType type, const std::string_view& name) {
if(type >= property_list_info.end_index.size()) return undefined_property_description;
if(type >= property_list_info.end_index.size()) {
return undefined_property_description;
}
constexpr static auto buffer_size{128}; /* no property is longer than 128 bytes */
if(name.size() >= buffer_size) return undefined_property_description;
if(name.size() >= buffer_size) {
return undefined_property_description;
}
char buffer[buffer_size];
for(size_t index{0}; index < name.size(); index++)
for(size_t index{0}; index < name.size(); index++) {
buffer[index] = (char) tolower(name[index]);
}
const std::string_view lower_name{buffer, name.size()};
const auto begin = property_list_info.begin_index[type];
const auto end = property_list_info.end_index[type];
for(size_t index{begin}; index < end; index++)
if(property_list[index].name == lower_name)
for(size_t index{begin}; index < end; index++) {
if(property_list[index].name == lower_name) {
return property_list[index];
}
}
return property_list[begin]; /* begin index MUST be the undefined */
}
@ -664,7 +711,13 @@ namespace ts {
const auto begin = property_list_info.begin_index[type];
const auto end = property_list_info.end_index[type];
return {property_list.begin() + begin, property_list.begin() + end};
std::vector<const PropertyDescription*> result{};
result.reserve(end - begin);
for(size_t index{begin}; index < end; index++) {
result.push_back(&property_list[index]);
}
return result;
}
template <typename PropertyEnumType, typename std::enable_if<std::is_enum<PropertyEnumType>::value, int>::type = 0>
@ -681,15 +734,14 @@ namespace ts {
#undef const_modifier
}
class Properties;
class PropertyManager;
struct PropertyData {
spin_lock value_lock;
std::any casted_value;
std::string value;
const property::PropertyDescription* description;
bool flag_db_reference;
bool flag_database_reference;
bool flag_modified;
};
@ -698,104 +750,98 @@ namespace ts {
#pragma warning( disable : 4200 )
#endif
struct PropertyBundle {
std::mutex value_mutex{};
property::PropertyType type;
size_t length;
size_t property_count;
PropertyData properties[0];
};
#ifdef WIN32
#pragma warning( pop )
#endif
template <typename T>
struct PropertyAccess {
inline static T get(PropertyData* data_ptr) {
std::lock_guard lock(data_ptr->value_lock);
if(data_ptr->casted_value.type() == typeid(T))
return std::any_cast<T>(data_ptr->casted_value);
data_ptr->casted_value = ts::converter<T>::from_string_view(std::string_view{data_ptr->value});
return std::any_cast<T>(data_ptr->casted_value);
}
};
template <>
struct PropertyAccess<std::string> {
inline static std::string get(PropertyData* data_ptr) { return data_ptr->value; }
};
struct PropertyWrapper {
friend class Properties;
struct Property {
friend class PropertyManager;
public:
bool operator==(const PropertyWrapper& other) {
if(this->data_ptr == other.data_ptr)
return true;
explicit Property(std::shared_ptr<PropertyManager> /* handle */, PropertyData* /* ptr */, std::shared_ptr<PropertyBundle> /* bundle */);
return this->data_ptr->value == other.data_ptr->value;
}
/**
* Get the property manager for this property.
* Note: The handle might be null if the manager hasn't been created with
* `std::make_shared<..>()`.
*/
[[nodiscard]] inline const auto& get_handle() { return this->handle; }
[[nodiscard]] bool hasDbReference() const { return this->property_data->flag_database_reference; }
void setDbReference(bool flag){ this->property_data->flag_database_reference = flag; }
[[nodiscard]] bool isModified() const { return this->property_data->flag_modified; }
void setModified(bool flag){ this->property_data->flag_modified = flag; }
#ifndef WIN32
template <typename T>
[[nodiscard]] operator T(){ return this->as_unchecked<T>(); }
#endif
template <typename T>
bool operator==(const T& other){
return this->as<T>() == other;
}
template <typename T>
bool operator!=(const T& other){
return !operator==(other);
}
//Math operators
PropertyWrapper&operator++(){ return operator=(as<int64_t>() + 1); }
PropertyWrapper&operator++(int){ return operator=(as<int64_t>() + 1); }
PropertyWrapper&operator+=(uint16_t val){ return operator=(as<uint16_t>() + val); }
PropertyWrapper&operator+=(int64_t val){ return operator=(as<int64_t>() + val); }
PropertyWrapper&operator+=(uint64_t val){ return operator=(as<uint64_t>() + val); }
[[nodiscard]] bool hasDbReference() const { return this->data_ptr->flag_db_reference; }
void setDbReference(bool flag){ this->data_ptr->flag_db_reference = flag; }
[[nodiscard]] bool isModified() const { return this->data_ptr->flag_modified; }
void setModified(bool flag){ this->data_ptr->flag_modified = flag; }
template <typename T>
[[nodiscard]] T as() const {
[[nodiscard]] std::optional<T> as() const {
static_assert(ts::converter<T>::supported, "as<T> isn't supported for type");
static_assert(!ts::converter<T>::references, "as<T> only supports non reference types");
return PropertyAccess<T>::get(this->data_ptr);
}
template <typename T>
[[nodiscard]] operator T(){ return this->as<T>(); }
template <typename T>
[[nodiscard]] T as_save() const {
try {
std::lock_guard lock(this->data_ptr->value_lock);
if(this->data_ptr->casted_value.type() == typeid(T))
return std::any_cast<T>(this->data_ptr->casted_value);
std::lock_guard value_lock{this->bundle_lock->value_mutex};
if(this->property_data->casted_value.type() == typeid(T)) {
const auto& value = std::any_cast<T>(this->property_data->casted_value);
return std::make_optional(value);
}
this->data_ptr->casted_value = ts::converter<T>::from_string_view(this->data_ptr->value);
return std::any_cast<T>(this->data_ptr->casted_value);
auto value = ts::converter<T>::from_string_view(this->property_data->value);
this->property_data->casted_value = value;
return std::make_optional(std::move(value));
} catch(std::exception&) {
return T{};
return std::nullopt;
}
}
[[nodiscard]] const property::PropertyDescription& type() const { return *this->data_ptr->description; }
template <typename T>
[[nodiscard]] T as_or(T fallback_value) const {
try {
std::lock_guard value_lock{this->bundle_lock->value_mutex};
if(this->property_data->casted_value.type() == typeid(T)) {
return std::any_cast<T>(this->property_data->casted_value);
}
this->property_data->casted_value = ts::converter<T>::from_string_view(this->property_data->value);
return std::any_cast<T>(this->property_data->casted_value);
} catch(std::exception&) {
return fallback_value;
}
}
template <typename T>
[[nodiscard]] T as_or_get(const std::function<T()> defaultValue = [] { return T{}; }) const {
try {
std::lock_guard value_lock{this->bundle_lock->value_mutex};
if(this->property_data->casted_value.type() == typeid(T)) {
return std::any_cast<T>(this->property_data->casted_value);
}
this->property_data->casted_value = ts::converter<T>::from_string_view(this->property_data->value);
return std::any_cast<T>(this->property_data->casted_value);
} catch(std::exception&) {
return defaultValue();
}
}
/* TODO: Depricate */
template <typename T>
[[nodiscard]] T as_unchecked() const {
return std::move(*this->as<T>());
}
[[nodiscard]] const property::PropertyDescription& type() const { return *this->property_data->description; }
[[nodiscard]] std::string value() const {
std::lock_guard lock{this->data_ptr->value_lock};
return this->data_ptr->value;
}
void value(const std::string &value, bool trigger_update = true){
{
std::lock_guard lock(this->data_ptr->value_lock);
if(this->data_ptr->value == value)
return;
this->data_ptr->casted_value.reset();
this->data_ptr->value = value;
}
if(trigger_update)
this->trigger_update();
std::lock_guard value_lock{this->bundle_lock->value_mutex};
return this->property_data->value;
}
[[nodiscard]] const std::string_view& default_value() const {
@ -803,84 +849,115 @@ namespace ts {
}
template <typename T>
PropertyWrapper& operator=(const T& value) {
bool update_value(T value) {
static_assert(ts::converter<T>::supported, "type isn't supported for type");
{
std::any any_value{value};
std::any any_value{std::move(value)};
auto value_string = ts::converter<T>::to_string(any_value);
std::lock_guard lock(this->data_ptr->value_lock);
if(value_string == this->data_ptr->value)
return *this;
this->data_ptr->casted_value = any_value;
this->data_ptr->value = value_string;
std::lock_guard value_lock{this->bundle_lock->value_mutex};
if(value_string == this->property_data->value) {
return false;
}
this->property_data->casted_value = any_value;
this->property_data->value = std::move(value_string);
}
this->trigger_update();
return true;
}
/**
* Increment the value, if numeric, by the given value.
* If the value isn't cast able to T `false` will be returned.
* Note: This action is not atomic.
* @tparam T
* @param value
* @return
*/
template<typename T, std::enable_if_t<std::is_integral<T>::value, int> = 0>
bool increment_by(T value) {
auto current_value = this->as<T>();
if(current_value.has_value()) {
this->update_value(*current_value + value);
return true;
} else {
return false;
}
}
template <typename T>
Property& operator=(const T& value) {
this->update_value(value);
return *this;
}
PropertyWrapper& operator=(const std::string& value) {
this->value(value);
return *this;
}
PropertyWrapper& operator=(const char* value) {
this->value(value);
Property& operator=(const char* value) {
this->update_value(std::string_view{value});
return *this;
}
template <int N>
PropertyWrapper& operator=(char(value)[N]) {
this->value(value);
Property& operator=(char(value)[N]) {
this->update_value(std::string_view{value, N});
return *this;
}
void trigger_update();
template <typename T>
bool operator==(const T& other) {
auto value = this->as<T>();
return value.has_value() && *value == other;
}
PropertyWrapper(Properties* /* handle */, PropertyData* /* ptr */, std::shared_ptr<PropertyBundle> /* bundle */);
[[nodiscard]] inline Properties* get_handle() { return this->handle; }
template <typename T>
bool operator!=(const T& other){
return !(*this == other);
}
private:
Properties* handle = nullptr;
PropertyData* data_ptr = nullptr;
/* Will be initialized by the constructor */
PropertyData* property_data;
std::shared_ptr<PropertyManager> handle;
std::shared_ptr<PropertyBundle> bundle_lock;
void trigger_update();
};
typedef PropertyWrapper Property;
typedef std::function<void(PropertyWrapper&)> PropertyNotifyFn;
class Properties {
friend struct PropertyWrapper;
typedef std::function<void(Property&)> PropertyNotifyFn;
class PropertyManager : public std::enable_shared_from_this<PropertyManager> {
friend struct Property;
public:
Properties();
~Properties();
Properties(const Properties&) = delete;
Properties(Properties&&) = default;
PropertyManager();
~PropertyManager();
std::vector<PropertyWrapper> list_properties(property::flag_type flagMask = (property::flag_type) ~0UL, property::flag_type negatedFlagMask = 0);
std::vector<PropertyWrapper> all_properties();
PropertyManager(const PropertyManager&) = delete;
PropertyManager(PropertyManager&&) = default;
std::vector<Property> list_properties(property::flag_type flagMask = (property::flag_type) ~0UL, property::flag_type negatedFlagMask = 0);
std::vector<Property> all_properties();
template <typename Type>
bool register_property_type() {
void register_property_type() {
constexpr auto type = property::type_from_enum<Type>();
return this->register_property_type(type, property::property_count<Type>());
this->do_register_property_type(type, property::property_count<Type>());
}
template <typename T>
bool hasProperty(T type) { return this->has(property::type_from_enum<T>(), type); }
template <typename T, typename std::enable_if<std::is_enum<T>::value, int>::type = 0>
PropertyWrapper operator[](T type) {
return this->find(property::type_from_enum<T>(), type);
template <typename T, typename std::enable_if<property::type_from_enum_t<T>::supported, int>::type = 0>
[[nodiscard]] Property operator[](T type) {
return this->get(property::type_from_enum_t<T>::type, type);
}
PropertyWrapper operator[](const property::PropertyDescription& type) {
return this->find(type.type_property, type.property_index);
[[nodiscard]] Property operator[](const property::PropertyDescription& type) {
return this->get(type.type_property, type.property_index);
}
PropertyWrapper operator[](const property::PropertyDescription* type) {
return this->find(type->type_property, type->property_index);
[[nodiscard]] Property operator[](const property::PropertyDescription* type) {
return this->get(type->type_property, type->property_index);
}
void registerNotifyHandler(const PropertyNotifyFn &fn){
@ -888,30 +965,63 @@ namespace ts {
}
void triggerAllModified(){
for(auto& prop : this->all_properties())
if(prop.isModified())
for(auto& elm : notifyFunctions)
for(auto& prop : this->all_properties()) {
if(prop.isModified()) {
for(auto& elm : this->notifyFunctions) {
elm(prop);
}
}
}
}
void toggleSave(bool flag) { this->save = flag; }
bool isSaveEnabled(){ return this->save; }
[[nodiscard]] bool isSaveEnabled(){ return this->save; }
PropertyWrapper find(property::PropertyType type, int index);
bool has(property::PropertyType type, int index);
[[nodiscard]] Property get(property::PropertyType type, int index);
[[nodiscard]] bool has(property::PropertyType type, int index);
template <typename T, typename std::enable_if<std::is_enum<T>::value, int>::type = 0>
bool has(T type) { return this->has(property::type_from_enum<T>(), (int) type); }
[[nodiscard]] bool has(T type) { return this->has(property::type_from_enum<T>(), (int) type); }
private:
bool register_property_type(property::PropertyType /* type */, size_t /* length */);
void do_register_property_type(property::PropertyType /* type */, size_t /* length */);
bool save{true};
std::vector<std::function<void(PropertyWrapper&)>> notifyFunctions{};
std::vector<std::function<void(Property&)>> notifyFunctions{};
size_t properties_count{0};
std::vector<std::shared_ptr<PropertyBundle>> properties;
};
struct PropertyWrapper {
public:
explicit PropertyWrapper(std::shared_ptr<PropertyManager> handle) : handle{std::move(handle)} {}
inline PropertyManager* operator->() {
return &*handle;
}
inline const PropertyManager* operator->() const {
return &*handle;
}
template<class F>
inline std::invoke_result<F(PropertyManager &)> operator->*(F &&f) {
return std::forward<F>(f)(*handle);
}
template<class F>
inline std::invoke_result<F(PropertyManager const &)> operator->*(F &&f) const {
return std::forward<F>(f)(*handle);
}
template <typename T>
[[nodiscard]] inline ts::Property operator[](const T& type) {
return (*handle)[type];
}
private:
std::shared_ptr<PropertyManager> handle;
};
};
//DEFINE_TRANSFORMS(ts::property::PropertyType, uint8_t);

View File

@ -87,7 +87,6 @@ property_list = std::array<PropertyDescription, impl::property_count()>{
PropertyDescription{VIRTUALSERVER_TOTAL_PACKETLOSS_CONTROL, "virtualserver_total_packetloss_control", "0", TYPE_FLOAT, FLAG_SERVER_VARIABLE}, //only available on request (=> requestServerVariables)
PropertyDescription{VIRTUALSERVER_TOTAL_PACKETLOSS_TOTAL, "virtualserver_total_packetloss_total", "0", TYPE_FLOAT, FLAG_SERVER_VARIABLE}, //only available on request (=> requestServerVariables)
PropertyDescription{VIRTUALSERVER_TOTAL_PING, "virtualserver_total_ping", "0", TYPE_FLOAT, FLAG_SERVER_VARIABLE}, //only available on request (=> requestServerVariables)
PropertyDescription{VIRTUALSERVER_WEBLIST_ENABLED, "virtualserver_weblist_enabled", "1", TYPE_BOOL, FLAG_SERVER_VVSS | FLAG_USER_EDITABLE}, //only available on request (=> requestServerVariables)
PropertyDescription{VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY, "virtualserver_autogenerated_privilegekey", "", TYPE_STRING, FLAG_SAVE}, //internal use
PropertyDescription{VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY, "virtualserver_ask_for_privilegekey", "1", TYPE_BOOL, FLAG_SERVER_VV | FLAG_SAVE | FLAG_USER_EDITABLE}, //available when connected
PropertyDescription{VIRTUALSERVER_CHANNEL_TEMP_DELETE_DELAY_DEFAULT, "virtualserver_channel_temp_delete_delay_default", "60", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VVSS | FLAG_USER_EDITABLE}, //available when connected, always up-to-date
@ -149,7 +148,8 @@ property_list = std::array<PropertyDescription, impl::property_count()>{
PropertyDescription{CHANNEL_CREATED_AT, "channel_created_at", "0", TYPE_UNSIGNED_NUMBER, FLAG_SS | FLAG_CHANNEL_VIEW | FLAG_CHANNEL_VARIABLE | FLAG_NEW}, //Available for all channels that are "in view", always up-to-date
PropertyDescription{CHANNEL_CREATED_BY, "channel_created_by", "0", TYPE_UNSIGNED_NUMBER, FLAG_SS | FLAG_CHANNEL_VIEW | FLAG_CHANNEL_VARIABLE | FLAG_NEW}, //Available for all channels that are "in view", always up-to-date
PropertyDescription{CHANNEL_CONVERSATION_HISTORY_LENGTH, "channel_conversation_history_length", "1500", TYPE_SIGNED_NUMBER, FLAG_SS | FLAG_CHANNEL_VIEW | FLAG_CHANNEL_VARIABLE | FLAG_NEW | FLAG_USER_EDITABLE},
PropertyDescription{CHANNEL_FLAG_CONVERSATION_PRIVATE, "channel_flag_conversation_private", "0", TYPE_BOOL, FLAG_SS | FLAG_CHANNEL_VIEW | FLAG_CHANNEL_VARIABLE | FLAG_NEW | FLAG_USER_EDITABLE},
PropertyDescription{CHANNEL_CONVERSATION_MODE, "channel_conversation_mode", "0", TYPE_UNSIGNED_NUMBER, FLAG_SS | FLAG_CHANNEL_VIEW | FLAG_CHANNEL_VARIABLE | FLAG_NEW | FLAG_USER_EDITABLE},
PropertyDescription{CHANNEL_SIDEBAR_MODE, "channel_sidebar_mode", "0", TYPE_UNSIGNED_NUMBER, FLAG_SS | FLAG_CHANNEL_VIEW | FLAG_CHANNEL_VARIABLE | FLAG_NEW | FLAG_USER_EDITABLE},
/* group properties, this may gets removed */
PropertyDescription{GROUP_UNDEFINED, "undefined", "", TYPE_UNKNOWN, 0},
@ -203,10 +203,10 @@ property_list = std::array<PropertyDescription, impl::property_count()>{
PropertyDescription{CLIENT_TALK_REQUEST_MSG, "client_talk_request_msg", "", TYPE_STRING, FLAG_CLIENT_VIEW | FLAG_USER_EDITABLE}, //automatically up-to-date for any manager "in view", only valid with PERMISSION feature, holds matter for the request
PropertyDescription{CLIENT_DESCRIPTION, "client_description", "", TYPE_STRING, FLAG_CLIENT_VIEW | FLAG_SS | FLAG_USER_EDITABLE}, //automatically up-to-date for any manager "in view"
PropertyDescription{CLIENT_IS_TALKER, "client_is_talker", "0", TYPE_BOOL, FLAG_CLIENT_VIEW | FLAG_USER_EDITABLE}, //automatically up-to-date for any manager "in view"
PropertyDescription{CLIENT_MONTH_BYTES_UPLOADED, "client_month_bytes_uploaded", "0", TYPE_UNSIGNED_NUMBER, FLAG_CLIENT_VARIABLE | FLAG_SAVE}, //this needs to be requested (=> requestClientVariables)
PropertyDescription{CLIENT_MONTH_BYTES_DOWNLOADED, "client_month_bytes_downloaded", "0", TYPE_UNSIGNED_NUMBER, FLAG_CLIENT_VARIABLE | FLAG_SAVE}, //this needs to be requested (=> requestClientVariables)
PropertyDescription{CLIENT_TOTAL_BYTES_UPLOADED, "client_total_bytes_uploaded", "0", TYPE_UNSIGNED_NUMBER, FLAG_CLIENT_VARIABLE | FLAG_SAVE}, //this needs to be requested (=> requestClientVariables)
PropertyDescription{CLIENT_TOTAL_BYTES_DOWNLOADED, "client_total_bytes_downloaded", "0", TYPE_UNSIGNED_NUMBER, FLAG_CLIENT_VARIABLE | FLAG_SAVE}, //this needs to be requested (=> requestClientVariables)
PropertyDescription{CLIENT_MONTH_BYTES_UPLOADED, "client_month_bytes_uploaded", "0", TYPE_UNSIGNED_NUMBER, FLAG_CLIENT_VARIABLE | FLAG_SAVE | FLAG_GLOBAL}, //this needs to be requested (=> requestClientVariables)
PropertyDescription{CLIENT_MONTH_BYTES_DOWNLOADED, "client_month_bytes_downloaded", "0", TYPE_UNSIGNED_NUMBER, FLAG_CLIENT_VARIABLE | FLAG_SAVE | FLAG_GLOBAL}, //this needs to be requested (=> requestClientVariables)
PropertyDescription{CLIENT_TOTAL_BYTES_UPLOADED, "client_total_bytes_uploaded", "0", TYPE_UNSIGNED_NUMBER, FLAG_CLIENT_VARIABLE | FLAG_SAVE | FLAG_GLOBAL}, //this needs to be requested (=> requestClientVariables)
PropertyDescription{CLIENT_TOTAL_BYTES_DOWNLOADED, "client_total_bytes_downloaded", "0", TYPE_UNSIGNED_NUMBER, FLAG_CLIENT_VARIABLE | FLAG_SAVE | FLAG_GLOBAL}, //this needs to be requested (=> requestClientVariables)
PropertyDescription{CLIENT_TOTAL_ONLINE_TIME, "client_total_online_time", "0", TYPE_UNSIGNED_NUMBER, FLAG_CLIENT_VARIABLE | FLAG_SAVE | FLAG_NEW}, //this needs to be requested (=> requestClientVariables)
PropertyDescription{CLIENT_MONTH_ONLINE_TIME, "client_month_online_time", "0", TYPE_UNSIGNED_NUMBER, FLAG_CLIENT_VARIABLE | FLAG_SAVE | FLAG_NEW}, //this needs to be requested (=> requestClientVariables)
PropertyDescription{CLIENT_IS_PRIORITY_SPEAKER, "client_is_priority_speaker", "0", TYPE_BOOL, FLAG_CLIENT_VIEW | FLAG_USER_EDITABLE}, //automatically up-to-date for any manager "in view"

View File

@ -3,6 +3,7 @@
//
#include "./Properties.h"
#include <cassert>
using namespace ts;

View File

@ -131,7 +131,7 @@ DEFINE_VARIABLE_TRANSFORM_TYPE(type, ntype)
DEFINE_VARIABLE_TRANSFORM(class, VARTYPE_INT, std::to_string((size_type) in), static_cast<class>(in.as<size_type>()));
DEFINE_VARIABLE_TRANSFORM(std::string, VARTYPE_TEXT, in, in.value());
DEFINE_VARIABLE_TRANSFORM(std::string_view, VARTYPE_TEXT, std::string{in}, std::string_view{in});
DEFINE_VARIABLE_TRANSFORM(std::string_view, VARTYPE_TEXT, std::string{in}, std::string_view{in.value()});
DEFINE_VARIABLE_TRANSFORM(char*, VARTYPE_TEXT, std::string((const char*) in), (char*) in.value().c_str());
DEFINE_VARIABLE_TRANSFORM(const char*, VARTYPE_TEXT, std::string((const char*) in), in.value().c_str());

View File

@ -8,8 +8,9 @@ using namespace bbcode;
bool bbcode::sloppy::has_tag(std::string message, std::deque<std::string> tag) {
std::transform(message.begin(), message.end(), message.begin(), ::tolower);
for(auto& entry : tag)
for(auto& entry : tag) {
std::transform(entry.begin(), entry.end(), entry.begin(), ::tolower);
}
std::deque<std::string> begins;
size_t index = 0, found, length = 0;
@ -18,17 +19,23 @@ bool bbcode::sloppy::has_tag(std::string message, std::deque<std::string> tag) {
for(auto it = tag.begin(); it != tag.end() && found == string::npos; it++) {
found = message.find(*it, index);
length = it->length();
};
}
if(found > 0 && found + length < message.length()) {
if(message[found + length] == ']' || (message[found + length] == '=' && message.find(']', found + length) != string::npos)) {
if(message[found - 1] == '/') {
auto found_tag = message.substr(found, length);
for(const auto& entry : begins)
if(entry == found_tag) return true;
} else if(message[found - 1] == '[' && (found < 2 || message[found - 2] != '\\'))
for(const auto& entry : begins) {
if(entry == found_tag) {
return true;
}
}
} else if(message[found - 1] == '[' && (found < 2 || message[found - 2] != '\\')) {
begins.push_back(message.substr(found, length));
if(message[found + length] != ']')
}
if(message[found + length] != ']') {
found = message.find(']', found + length);
}
}
}
index = found + 1;

View File

@ -4,8 +4,8 @@
#include <memory>
namespace bbcode::sloppy {
extern bool has_tag(std::string message, std::deque<std::string> tag);
extern bool has_tag(std::string message, std::deque<std::string> tag);
inline bool has_url(const std::string& message) { return has_tag(message, {"url"}); }
inline bool has_image(const std::string& message) { return has_tag(message, {"img"}); }
}
inline bool has_url(const std::string& message) { return has_tag(message, {"url"}); }
inline bool has_image(const std::string& message) { return has_tag(message, {"img"}); }
}

View File

@ -1,4 +1,5 @@
#include <algorithm>
#include <assert.h>
#include "TreeView.h"
using namespace ts;

View File

@ -1,13 +1,11 @@
#pragma once
#include <deque>
#include <memory>
#include <utility>
#include <functional>
#include "misc/advanced_mutex.h"
#include <Definitions.h>
#include <deque>
#include <ThreadPool/Mutex.h>
#include "misc/memtracker.h"
#include "../misc/memtracker.h"
#ifndef __attribute_deprecated__
#define __attribute_deprecated__ [[deprecated]]

View File

@ -1,6 +1,4 @@
#include "converter.h"
#include <sstream>
#include <algorithm>
using namespace std;
using namespace ts;
@ -19,14 +17,14 @@ CONVERTER_METHOD_ENCODE(type, impl::converter_ ##type ##_encode) { \
return std::to_string(std::any_cast<type>(value)); \
)
CONVERTER_PRIMITIVE_ST(int8_t, std::stol(std::string{str}) & 0xFF);
CONVERTER_PRIMITIVE_ST(uint8_t, std::stoul(std::string{str}) & 0xFF);
CONVERTER_PRIMITIVE_ST(int8_t, std::stol(std::string{str}) & 0xFFU);
CONVERTER_PRIMITIVE_ST(uint8_t, std::stoul(std::string{str}) & 0xFFU);
CONVERTER_PRIMITIVE_ST(int16_t, std::stol(std::string{str}) & 0xFFFF);
CONVERTER_PRIMITIVE_ST(uint16_t, std::stoul(std::string{str}) & 0xFFFF);
CONVERTER_PRIMITIVE_ST(int16_t, std::stol(std::string{str}) & 0xFFFFU);
CONVERTER_PRIMITIVE_ST(uint16_t, std::stoul(std::string{str}) & 0xFFFFU);
CONVERTER_PRIMITIVE_ST(int32_t, std::stol(std::string{str}) & 0xFFFFFFFF);
CONVERTER_PRIMITIVE_ST(uint32_t, std::stoul(std::string{str}) & 0xFFFFFFFF);
CONVERTER_PRIMITIVE_ST(int32_t, std::stol(std::string{str}) & 0xFFFFFFFFU);
CONVERTER_PRIMITIVE_ST(uint32_t, std::stoul(std::string{str}) & 0xFFFFFFFFU);
CONVERTER_PRIMITIVE_ST(int64_t, std::stoll(std::string{str}));
CONVERTER_PRIMITIVE_ST(uint64_t, std::stoull(std::string{str}))
@ -36,6 +34,10 @@ CONVERTER_PRIMITIVE_ST(float, std::stof(std::string{str}));
CONVERTER_PRIMITIVE_ST(double, std::stod(std::string{str}));
CONVERTER_PRIMITIVE_ST(long_double, std::stold(std::string{str}));
#if __x86_64__
CONVERTER_PRIMITIVE_ST(long_long_unsigned_int_t, std::stoull(std::string{str}));
#endif
CONVERTER_ST(std__string, return std::string{str};, return std::any_cast<std__string>(value););
CONVERTER_ST(std__string_view, return str;, return std::string{std::any_cast<std__string_view>(value)};);
CONVERTER_ST(const_char__ , return str.data();, return std::string{std::any_cast<const_char__>(value)};);

View File

@ -3,87 +3,103 @@
#include <any>
#include <string>
#include <cstddef>
namespace ts {
typedef long double long_double;
/* Converter stuff */
template <typename T>
struct converter {
static constexpr bool supported = false;
static constexpr std::string(*to_string)(const std::any&) = nullptr;
static constexpr T(*from_string_view)(const std::string_view&) = nullptr;
};
#define DECLARE_CONVERTER(type, decode, encode) \
template <> \
struct converter<type> { \
static constexpr bool supported = true; \
\
static constexpr std::string(*to_string)(const std::any&) = encode; \
static constexpr type(*from_string_view)(const std::string_view&) = decode; \
};
#define CONVERTER_METHOD_DECODE(type, name) type name(const std::string_view& str)
#define CONVERTER_METHOD_ENCODE(type, name) std::string name(const std::any& value)
/* helper for primitive types */
#define CONVERTER_PRIMITIVE(type) \
namespace impl { \
CONVERTER_METHOD_DECODE(type, converter_ ##type ##_decode); \
CONVERTER_METHOD_ENCODE(type, converter_ ##type ##_encode); \
} \
DECLARE_CONVERTER(type, ::ts::impl::converter_ ##type ##_decode, ::ts::impl::converter_ ##type ##_encode);
CONVERTER_PRIMITIVE(bool);
CONVERTER_PRIMITIVE(float);
CONVERTER_PRIMITIVE(double);
CONVERTER_PRIMITIVE(long_double);
CONVERTER_PRIMITIVE(int8_t);
CONVERTER_PRIMITIVE(uint8_t);
CONVERTER_PRIMITIVE(int16_t);
CONVERTER_PRIMITIVE(uint16_t);
CONVERTER_PRIMITIVE(int32_t);
CONVERTER_PRIMITIVE(uint32_t);
CONVERTER_PRIMITIVE(int64_t);
CONVERTER_PRIMITIVE(uint64_t);
typedef std::string std__string;
typedef std::string_view std__string_view;
typedef const char* const_char__;
CONVERTER_PRIMITIVE(std__string);
CONVERTER_PRIMITIVE(std__string_view);
CONVERTER_PRIMITIVE(const_char__);
/* const expr char literal */
template <int length>
struct converter<char[length]> {
using type = char[length];
static constexpr bool supported = true;
static constexpr std::string(*to_string)(const std::any&) = [](const std::any& value) { return std::string(std::any_cast<const char*>(value), length - 1); };
};
#undef CONVERTER_PRIMITIVE
}
#include <chrono>
#define DEFINE_CONVERTER_ENUM(class, size_type) \
namespace ts { \
template <> \
struct converter<class> { \
static constexpr bool supported = true; \
static constexpr bool supported{true}; \
static constexpr bool references{false}; \
\
static constexpr std::string(*to_string)(const std::any&) = [](const std::any& val) { \
return std::to_string(std::any_cast<class>(val)); \
}; \
static constexpr class(*from_string_view)(const std::string_view&) = [](const std::string_view& val) { \
return ((class(*)(const std::string_view&)) ts::converter<size_type>::from_string_view)(val); \
static constexpr std::string(*to_string)(const std::any&) = [](const std::any& val) { \
return std::to_string(std::any_cast<class>(val)); \
}; \
static constexpr class(*from_string_view)(const std::string_view&) = [](const std::string_view& val) { \
return ((class(*)(const std::string_view&)) ts::converter<size_type>::from_string_view)(val); \
}; \
}; \
}
namespace ts {
typedef long double long_double;
typedef long long unsigned int long_long_unsigned_int_t;
/* Converter stuff */
template <typename T>
struct converter {
static constexpr bool supported{false};
static constexpr bool references{false};
static constexpr std::string(*to_string)(const std::any&) = nullptr;
static constexpr T(*from_string_view)(const std::string_view&) = nullptr;
};
#define DECLARE_CONVERTER(type, decode, encode, references_) \
template <> \
struct converter<type> { \
static constexpr bool supported{true}; \
static constexpr bool references{references_}; \
\
static constexpr std::string(*to_string)(const std::any&) = encode; \
static constexpr type(*from_string_view)(const std::string_view&) = decode; \
};
#define CONVERTER_METHOD_DECODE(type, name) type name(const std::string_view& str)
#define CONVERTER_METHOD_ENCODE(type, name) std::string name(const std::any& value)
/* helper for primitive types */
#define CONVERTER_PRIMITIVE(type, references) \
namespace impl { \
CONVERTER_METHOD_DECODE(type, converter_ ##type ##_decode); \
CONVERTER_METHOD_ENCODE(type, converter_ ##type ##_encode); \
} \
DECLARE_CONVERTER(type, ::ts::impl::converter_ ##type ##_decode, ::ts::impl::converter_ ##type ##_encode, references);
CONVERTER_PRIMITIVE(bool, false);
CONVERTER_PRIMITIVE(float, false);
CONVERTER_PRIMITIVE(double, false);
CONVERTER_PRIMITIVE(long_double, false);
CONVERTER_PRIMITIVE(int8_t, false);
CONVERTER_PRIMITIVE(uint8_t, false);
CONVERTER_PRIMITIVE(int16_t, false);
CONVERTER_PRIMITIVE(uint16_t, false);
CONVERTER_PRIMITIVE(int32_t, false);
CONVERTER_PRIMITIVE(uint32_t, false);
CONVERTER_PRIMITIVE(int64_t, false);
CONVERTER_PRIMITIVE(uint64_t, false);
#if __x86_64__
CONVERTER_PRIMITIVE(long_long_unsigned_int_t, false);
#endif
typedef std::string std__string;
typedef std::string_view std__string_view;
typedef const char* const_char__;
CONVERTER_PRIMITIVE(std__string, false);
CONVERTER_PRIMITIVE(std__string_view, true);
CONVERTER_PRIMITIVE(const_char__, true);
/* const expr char literal */
template <int length>
struct converter<char[length]> {
using type = char[length];
static constexpr bool supported{true};
static constexpr std::string(*to_string)(const std::any&) = [](const std::any& value) { return std::string(std::any_cast<const char*>(value), length - 1); };
};
/* We're not enabling this since we don't transport the unit
template <typename Rep, typename Period>
struct converter<std::chrono::duration<Rep, Period>> {
using type = std::chrono::duration<Rep, Period>;
static constexpr bool supported{true};
static constexpr std::string(*to_string)(const std::any&) = [](const std::any& value) { return std::to_string(std::any_cast<type>(value)); };
}
*/
#undef CONVERTER_PRIMITIVE
}
/* DO NOT REMOVE ME (NL warning) */

View File

@ -2,8 +2,6 @@
#include "LogSinks.h"
#include <ctime>
#include <array>
#include <spdlog/details/os.h>
#include <spdlog/formatter.h>
using namespace std;
using namespace spdlog;
@ -15,23 +13,28 @@ namespace logger {
std::string_view message{formatted.data(), formatted.size()};
#ifdef HAVE_CXX_TERMINAL
#ifdef HAVE_CXX_TERMINAL
if (terminal::active()) {
//Split the string at new lines
size_t index{0}, found{0};
size_t index{0}, found;
do {
found = message.find('\n', index);
const auto length = (found == -1 ? message.length() : found) - index;
const auto line = message.substr(index, length);
index = found;
if(length == 0) continue;
if(length == 0) {
continue;
}
terminal::instance()->writeMessage(std::string{line});
} while(++index);
} else
#endif
cout << message;
} else {
cout << message << std::flush;
}
#else
cout << message << std::flush;
#endif
}
void TerminalSink::flush_() {
@ -98,8 +101,9 @@ namespace logger {
const auto& mapping = level_mapping;
#endif
size_t level = msg.level.value;
if(level >= mapping.size())
if(level >= mapping.size()) {
level = mapping.size() - 1;
}
append(mapping[level]);
}
@ -122,10 +126,11 @@ namespace logger {
index = found;
append(spdlog::details::os::default_eol);
if(++index)
if(++index) {
dest.append(prefix_begin, prefix_end);
else
} else {
break;
}
}
}

View File

@ -1,6 +1,5 @@
#include "LogUtils.h"
#include "LogSinks.h"
#include <iomanip>
#include <fstream>
#include <map>
#ifdef WIN32
@ -17,10 +16,6 @@
#include <spdlog/async.h>
#include <spdlog/sinks/rotating_file_sink.h>
#ifdef HAVE_CXX_TERMINAL
#include <CXXTerminal/Terminal.h>
#endif
using namespace std;
using namespace std::chrono;
using namespace spdlog;
@ -97,10 +92,12 @@ namespace logger {
if(!::logger::currentConfig())
return default_logger();
size_t group = 0;
if(::logger::currentConfig()->vs_group_size > 0 && serverId > 0)
size_t group{0};
if(::logger::currentConfig()->vs_group_size > 0 && serverId > 0) {
group = serverId / ::logger::currentConfig()->vs_group_size;
else group = -1;
} else {
group = -1;
}
if(loggers.count(group) == 0) {
lock_guard lock(loggerLock);
@ -110,30 +107,25 @@ namespace logger {
logger(0)->debug("Creating new grouped logger for group {}", group);
vector<spdlog::sink_ptr> sinks;
string path;
if(logConfig->logfileLevel != spdlog::level::off) {
path = generate_log_file(group);
auto path = generate_log_file(group);
auto logFile = fs::u8path(path);
if(!logFile.parent_path().empty())
fs::create_directories(logFile.parent_path());
auto logFile = fs::u8path(path);
if(!logFile.parent_path().empty())
fs::create_directories(logFile.parent_path());
try {
auto sink = make_shared<spdlog::sinks::rotating_file_sink_mt>(logFile.string(), 1024 * 1024 * 50, 12);
sink->set_formatter(std::make_unique<LogFormatter>(::logger::currentConfig()->file_colored));
sinks.push_back(sink);
} catch(std::exception& ex) {
if(group != 0 && group != -1)
logger(0)->critical("Failed to create file for new log group: {}", ex.what());
else
try {
auto sink = make_shared<spdlog::sinks::rotating_file_sink_mt>(logFile.string(), 1024 * 1024 * 50, 12);
sink->set_formatter(std::make_unique<LogFormatter>(::logger::currentConfig()->file_colored));
sinks.push_back(sink);
} catch(std::exception& ex) {
if(group != 0 && group != -1)
logger(0)->critical("Failed to create file for new log group: {}", ex.what());
else
#ifdef HAVE_CXX_TERMINAL
terminal::instance()->writeMessage("§4[CRITICAL] §eFailed to create main log file: " + string{ex.what()}, false);
terminal::instance()->writeMessage("§4[CRITICAL] §eFailed to create main log file: " + string{ex.what()}, false);
#else
std::cout << "[CRITICAL] Failed to create main log file: " << ex.what() << "\n";
std::cout << "[CRITICAL] Failed to create main log file: " << ex.what() << "\n";
#endif
}
} else {
path = "/dev/null (" + to_string(group) + ")";
}
sinks.push_back(terminalSink);
@ -143,6 +135,7 @@ namespace logger {
std::shared_ptr<spdlog::logger> logger;
if(!logConfig->sync) {
logger = std::make_shared<spdlog::async_logger>("Logger (" + path + ")", sinks.begin(), sinks.end(), logging_threads, async_overflow_policy::block);
logger->flush_on(level::debug);
} else {
logger = std::make_shared<spdlog::logger>("Logger (" + path + ")", sinks.begin(), sinks.end());
logger->flush_on(level::trace);
@ -247,66 +240,4 @@ namespace logger {
logConfig = nullptr;
terminalSink = nullptr;
}
}
void hexDump(void *addr, int len, int pad,int columnLength, void (*print)(string));
void hexDump(void *addr, int len, int pad,int columnLength) {
hexDump(addr, len, pad, columnLength, [](string str){ logMessage(0, "\n{}", str); });
}
void hexDump(void *addr, int len, int pad,int columnLength, void (*print)(string)) {
int i;
uint8_t* buff = new uint8_t[pad+1];
unsigned char* pc = (unsigned char*)addr;
if (len <= 0) {
return;
}
stringstream line;
line << uppercase << hex << setfill('0');
// Process every byte in the data.
for (i = 0; i < len; i++) {
// Multiple of 16 means new line (with line offset).
if ((i % pad) == 0) {
// Just don't print ASCII for the zeroth line.
if (i != 0) {
line << buff;
print(line.str());
line = stringstream{};
line << hex;
};
// Output the offset.
line << setw(4) << i << " ";
}
if(i % columnLength == 0 && i % pad != 0){
line << "| ";
}
// Now the hex code for the specific character.
line << setw(2) << (int) pc[i] << " ";
// And store a printable ASCII character for later.
if ((pc[i] < 0x20) || (pc[i] > 0x7e))
buff[i % pad] = '.';
else
buff[i % pad] = pc[i];
buff[(i % pad) + 1] = '\0';
}
// Pad out last line if not exactly 16 characters.
while ((i % pad) != 0) {
line << " ";
i++;
}
line << buff;
delete[] buff;
print(line.str());
line = stringstream{};
line << "Length: " << dec << len << " Addr: " << addr;
print(line.str());
}
}

View File

@ -101,6 +101,4 @@ LOG_METHOD(logCritical);
LOG_METHOD(logTrace);
LOG_METHOD(debugMessage);
#undef LOG_METHOD
void hexDump(void* addr, int length, int numLength = 16, int columnLength = 8);
void hexDump(void* addr, int length, int numLength, int columnLength, void (*)(std::string));
#undef LOG_METHOD

View File

@ -1,5 +0,0 @@
//
// Created by wolverindev on 12.06.18.
//
#include "translation.h"

View File

@ -1,13 +0,0 @@
#pragma once
namespace tr {
enum Messages {
kick_invalid_badges,
kick_invalid_packet,
kick_invalid_command,
crash_instance,
shutdown_instance,
shutdown_server,
};
}

23
src/lookup/ip.cpp Normal file
View File

@ -0,0 +1,23 @@
//
// Created by WolverinDEV on 01/08/2020.
//
#include "ipv4.h"
#include "ipv6.h"
lookup::ip_v4<void> storage_4{};
lookup::ip_v6<void> loop6{};
int test() {
sockaddr_in addr_4{};
storage_4.insert(addr_4, nullptr);
(void) storage_4.lookup(addr_4);
storage_4.remove(addr_4);
sockaddr_in6 addr_6{};
loop6.insert(addr_6, nullptr);
(void) loop6.lookup(addr_6);
loop6.remove(addr_6);
return 0;
}

143
src/lookup/ip.h Normal file
View File

@ -0,0 +1,143 @@
#pragma once
#include <mutex>
#include "../misc/spin_mutex.h"
namespace lookup {
template <typename T, typename addr_t, typename addr_storage_t, typename addr_converter, typename addr_cmp, typename hash_fn>
struct ip_vx {
constexpr static auto kBukkitListSize{128}; /* must be a power of two */
constexpr static auto kBukkitSize{32};
struct bucket_entry {
addr_storage_t address{};
std::shared_ptr<T> entry{};
};
struct bucket_t {
std::array<bucket_entry, kBukkitSize> entries{};
uint8_t entry_count{0};
bucket_t* next{nullptr};
};
public:
inline void insert(const addr_t& address, const std::shared_ptr<T>& value) {
auto hash = (uint8_t) hash_fn{}(address);
hash &= (uint8_t) (kBukkitListSize - 1);
bucket_t* bucket = &this->buckets[hash];
std::lock_guard lock{this->bucket_locks[hash]};
while(bucket->entry_count == kBukkitSize && bucket->next) {
bucket = bucket->next;
}
if(bucket->entry_count == kBukkitSize) {
bucket = (bucket->next = new bucket_t{});
}
auto& entry = bucket->entries[bucket->entry_count++];
addr_converter{}(entry.address, address);
entry.entry = value;
}
inline std::shared_ptr<T> remove(const addr_t& address) {
auto hash = (uint8_t) hash_fn{}(address);
hash &= (uint8_t) (kBukkitListSize - 1);
bucket_t *bucket = &this->buckets[hash], *next_bucket;
addr_storage_t addr{};
addr_converter{}(addr, address);
addr_cmp cmp{};
std::lock_guard lock{this->bucket_locks[hash]};
size_t entry_index;
do {
for(entry_index = 0; entry_index < bucket->entry_count; entry_index++) {
if(auto& entry{bucket->entries[entry_index]}; cmp(entry.address, addr)) {
goto entry_found;
}
}
} while((bucket = bucket->next));
/* entry hasn't been found */
return nullptr;
entry_found:
next_bucket = bucket;
while(next_bucket->next && next_bucket->next->entry_count > 0) {
next_bucket = next_bucket->next;
}
/* swap the entry with the last entry and just remove the value */
next_bucket->entry_count--;
std::exchange(bucket->entries[entry_index], next_bucket->entries[next_bucket->entry_count]);
return std::exchange(next_bucket->entries[next_bucket->entry_count].entry, nullptr);
}
inline void cleanup() {
for(size_t bucket_index{0}; bucket_index < kBukkitListSize; bucket_index++) {
cleanup_bucket(bucket_index);
}
}
inline void cleanup_bucket(size_t index) {
bucket_t* delete_head;
{
std::lock_guard lock{this->bucket_locks[index]};
auto& bucket = this->buckets[index];
bucket_t* prev{nullptr}, curr{&bucket};
while(curr.entry_count > 0 && curr.next) {
prev = std::exchange(curr, curr.next);
}
if(curr.entry_count == 0) {
prev->next = nullptr;
delete_head = curr;
} else {
return;
}
}
while(delete_head) {
auto next = delete_head->next;
delete delete_head;
delete_head = next;
}
}
[[nodiscard]] inline std::shared_ptr<T> lookup(const addr_t& address) const {
auto hash = (uint8_t) hash_fn{}(address);
hash &= (uint8_t) (kBukkitListSize - 1);
const bucket_t* bucket = &this->buckets[hash], next_bucket;
addr_storage_t addr{};
addr_converter{}(addr, address);
addr_cmp cmp{};
{
std::lock_guard lock{this->bucket_locks[hash]};
do {
for(size_t index{0}; index < bucket->entry_count; index++) {
if(auto& entry{bucket->entries[index]}; cmp(entry.address, addr)) {
return entry.entry;
}
}
} while((bucket = bucket->next));
}
return nullptr;
}
private:
mutable std::array<spin_mutex, kBukkitListSize> bucket_locks{};
std::array<bucket_t, kBukkitListSize> buckets{};
};
}

46
src/lookup/ipv4.h Normal file
View File

@ -0,0 +1,46 @@
#pragma once
#include <netinet/in.h>
#include "./ip.h"
namespace lookup {
namespace ipv4_impl {
union uaddress_t {
struct {
uint32_t address{0};
uint16_t port{0};
};
uint64_t value;
};
struct converter {
constexpr inline void operator()(uaddress_t& result, const sockaddr_in& addr) {
result.address = addr.sin_addr.s_addr;
result.port = addr.sin_port;
}
};
struct comparator {
constexpr inline bool operator()(const uaddress_t& a, const uaddress_t& b) {
return a.value == b.value;
}
};
struct hash {
constexpr inline uint8_t operator()(const sockaddr_in& address) {
return (address.sin_addr.s_addr & 0xFFU) ^ (address.sin_port);
}
};
}
template <typename T>
using ip_v4 = ip_vx<
T,
sockaddr_in,
ipv4_impl::uaddress_t,
ipv4_impl::converter,
ipv4_impl::comparator,
ipv4_impl::hash
>;
}

51
src/lookup/ipv6.h Normal file
View File

@ -0,0 +1,51 @@
#pragma once
#include <netinet/in.h>
#include "./ip.h"
namespace lookup {
namespace ipv6_impl {
struct address_t {
union {
uint64_t address_u64[ 2];
};
uint16_t port;
};
struct converter {
constexpr inline void operator()(address_t& result, const sockaddr_in6& addr) {
auto addr_ptr = (uint64_t*) &addr.sin6_addr;
result.address_u64[0] = addr_ptr[0];
result.address_u64[1] = addr_ptr[1];
result.port = addr.sin6_port;
}
};
struct comparator {
constexpr inline bool operator()(const address_t& a, const address_t& b) {
return a.address_u64[0] == b.address_u64[0] && a.address_u64[1] == b.address_u64[1] && a.port == b.port;
}
};
struct hash {
constexpr inline uint8_t operator()(const sockaddr_in6& address) {
auto addr_ptr = (uint8_t*) &address.sin6_addr;
return (uint8_t) (addr_ptr[8] ^ addr_ptr[9]) ^ (uint8_t) (addr_ptr[15] ^ address.sin6_port);
}
};
}
template <typename T>
using ip_v6 = ip_vx<
T,
sockaddr_in6,
ipv6_impl::address_t,
ipv6_impl::converter,
ipv6_impl::comparator,
ipv6_impl::hash
>;
}

View File

@ -279,25 +279,33 @@ namespace std {
};
*/
/**
* Test if a `std::shared_mutex` is unique locked by trying to lock it in shared mode.
* @tparam T
* @param mutex
* @return
*/
template <typename T>
inline bool mutex_locked(T& mutex) {
/* std::shared_mutex can be recursively unique locked?? */
return true;
try {
unique_lock<T> lock_try(mutex, try_to_lock); /* should throw EDEADLK */
std::unique_lock<T> lock_try(mutex, try_to_lock); /* should throw EDEADLK */
return false;
} catch(const std::system_error& ex) {
return ex.code() == errc::resource_deadlock_would_occur;
}
}
/**
* Test if a `std::shared_mutex` is shared (or unique) locked by try locking it in unique mode
* @tparam T
* @param mutex
* @return
*/
template <typename T>
inline bool mutex_shared_locked(T& mutex) {
return true;
try {
shared_lock<T> lock_try(mutex, try_to_lock); /* should throw EDEADLK */
return false;
} catch(const std::system_error& ex) {
return ex.code() == errc::resource_deadlock_would_occur;
}
//return mutex_locked(mutex);
}
}

View File

@ -13,7 +13,7 @@ std::string base64::decode(const char* input, size_t size) {
return ret;
}
std::string base64::encode(const char* input, const unsigned long inputSize) {
std::string base64::encode(const char* input, size_t inputSize) {
auto outlen = static_cast<unsigned long>(inputSize + (inputSize / 3.0) + 16);
auto outbuf = new unsigned char[outlen]; //Reserve output memory
if(base64_encode((unsigned char*) input, inputSize, outbuf, &outlen) != CRYPT_OK){

View File

@ -10,14 +10,15 @@ namespace base64 {
* @param inputSize The size of the input to decode
* @return A Base64-encoded version of the encoded string
*/
extern std::string encode(const char* input, const unsigned long inputSize);
extern std::string encode(const char* input, size_t inputSize);
/**
* Encodes a given string in Base64
* @param input The input string to Base64-encode
* @return A Base64-encoded version of the encoded string
*/
inline std::string encode(const std::string& input) { return encode(input.c_str(), (unsigned long) input.size()); }
inline std::string encode(const std::string& input) { return encode(input.data(), input.size()); }
inline std::string encode(const std::string_view& input) { return encode(input.data(), input.size()); }
/**
@ -32,7 +33,8 @@ namespace base64 {
* @param input The input string to decode
* @return A string (binary) that represents the Base64-decoded data of the input
*/
inline std::string decode(const std::string& input) { return decode(input.c_str(), input.size()); }
inline std::string decode(const std::string& input) { return decode(input.data(), input.size()); }
inline std::string decode(const std::string_view& input) { return decode(input.data(), input.size()); }
//AZ, az, 09, + und /
inline bool validate(const std::string& input) {

View File

@ -39,18 +39,22 @@
#include <openssl/sha.h>
#define DECLARE_DIGEST(name, method, digestLength) \
inline std::string name(const std::string& input) { \
inline std::string name(const std::string_view& input) { \
u_char buffer[digestLength]; \
method((u_char*) input.data(), input.length(), buffer); \
return std::string((const char*) buffer, (size_t) digestLength); \
return std::string{(const char*) buffer, (size_t) digestLength}; \
} \
\
inline std::string name(const std::string& input) { \
return name(std::string_view{input}); \
} \
\
inline std::string name(const char* input, ssize_t length = -1) { \
if(length == -1) length = strlen(input); \
return name(std::string(input, (size_t) length)); \
return name(std::string_view{input, (size_t) length}); \
} \
\
inline void name(const char* input, size_t length, uint8_t* result) { \
\
inline void name(const char* input, size_t length, uint8_t* result) { \
method((u_char*) input, length, result); \
}
#endif

View File

@ -9,7 +9,11 @@
#define TRACK_OBJECT_ALLOCATION
#include "memtracker.h"
#define NO_IMPL //For fast disable (e.g. when you dont want to recompile the whole source)
#ifdef NDEBUG
#define NO_IMPL //For fast disable (e.g. when you dont want to recompile the whole source)
#else
#define NO_IMPL
#endif
#ifndef __GLIBC__
#define _GLIBCXX_NOEXCEPT
@ -65,6 +69,8 @@ namespace memtrack {
: strcmp (name, __arg.name) < 0; }
inline std::string as_mangled() {
if(!this->mangled.empty())
return this->mangled;
#ifndef WIN32
int status;
std::unique_ptr<char[], void (*)(void*)> result(abi::__cxa_demangle(name, nullptr, nullptr, &status), std::free);
@ -82,12 +88,12 @@ namespace memtrack {
class entry {
public:
/* std::string name; */
size_t type;
size_t type{};
void* address = nullptr;
entry() {}
entry() = default;
entry(size_t type, void* address) : type(type), address(address) {}
~entry() {}
~entry() = default;
};
template <int N>
@ -178,7 +184,7 @@ namespace memtrack {
auto _value = (size_t) type_index.value;
for (auto &brick : bricks)
if(brick->remove(_value, address)) return;
logError(lstream << "[MEMORY] Got deallocated notify, but never the allocated! (Address: " << address << " Name: " << name << ")");
logError(LOG_GENERAL, "[MEMORY] Got deallocated notify, but never the allocated! (Address: {} Name: {})", address, name);
#endif
}
@ -201,25 +207,25 @@ namespace memtrack {
mapping[type.second.value] = type.first.as_mangled();
}
logMessage("Allocated object types: " + to_string(objects.size()));
logMessage(LOG_GENERAL, "Allocated object types: " + to_string(objects.size()));
for(const auto& entry : objects) {
logMessage(" " + mapping[entry.first] + ": " + to_string(entry.second.size()));
logMessage(LOG_GENERAL, " " + mapping[entry.first] + ": " + to_string(entry.second.size()));
if (entry.second.size() < 50) {
stringstream ss;
for (int index = 0; index < entry.second.size(); index++) {
if (index % 16 == 0) {
if (index + 1 >= entry.second.size()) break;
if (index != 0)
logMessage(ss.str());
logMessage(LOG_GENERAL, ss.str());
ss = stringstream();
ss << " ";
}
ss << entry.second[index] << " ";
}
if (!ss.str().empty())
logMessage(ss.str());
logMessage(LOG_GENERAL, ss.str());
} else {
logMessage("<snipped>");
logMessage(LOG_GENERAL, "<snipped>");
}
}
#endif

View File

@ -5,7 +5,7 @@
namespace memtrack {
#define TRACK_OBJECT_ALLOCATION
#ifdef TRACK_OBJECT_ALLOCATION
#if defined(FEATURE_MEMTRACK) && defined(TRACK_OBJECT_ALLOCATION)
extern void allocated(const char* name, void* address);
extern void freed(const char* name, void* address);
template <typename T>

View File

@ -69,7 +69,7 @@ net::binding_result net::address_available(const sockaddr_storage& address, bind
fcntl(file_descriptor, F_SETFD, FD_CLOEXEC); /* just to ensure */
setsockopt(file_descriptor, SOL_SOCKET, SO_REUSEADDR, &disable, sizeof(int));
setsockopt(file_descriptor, SOL_SOCKET, SO_REUSEPORT, &disable, sizeof(int));
//setsockopt(file_descriptor, SOL_SOCKET, SO_REUSEPORT, &disable, sizeof(int));
if(type == binding_type::UDP && address.ss_family == AF_INET6)
setsockopt(file_descriptor, IPPROTO_IPV6, IPV6_V6ONLY, &enable, sizeof(int));

View File

@ -7,9 +7,13 @@
#define sassert(exp) assert(exp)
#else
#define S(s) #s
#define sassert(exp) \
do { \
if(!(exp)) \
logCritical(0, "Soft assertion @{}:{} '{}' failed! This could cause fatal fails!", __FILE__, __LINE__, #exp); \
#define sassert(exp) \
do { \
if(!(exp)) { \
logCritical(0, \
"Soft assertion @{}:{} '{}' failed! This could cause fatal fails!", \
__FILE__, __LINE__, #exp); \
} \
} while(0)
#undef S
#endif

View File

@ -9,7 +9,7 @@
#define always_inline inline __attribute__((__always_inline__))
#endif
class spin_lock {
class spin_mutex {
std::atomic_bool locked{false};
public:
always_inline void lock() {
@ -22,8 +22,9 @@ class spin_lock {
uint8_t round = 0;
while (locked.load(std::memory_order_relaxed)) {
//Yield when we're using this lock for a longer time, which we usually not doing
if(round++ % 8 == 0)
if(round++ % 8 == 0) {
std::this_thread::yield();
}
}
}

View File

@ -1,6 +1,7 @@
#pragma once
#include <cstdio>
#include <string>
#include <cstring>
#include <array>
#include <string_view>

542
src/misc/task_executor.cpp Normal file
View File

@ -0,0 +1,542 @@
//
// Created by WolverinDEV on 21/02/2021.
//
#include "./task_executor.h"
#include "./threads.h"
#include <thread>
#include <cassert>
#include <iostream>
#include <algorithm>
#include <optional>
#include <condition_variable>
using std::chrono::system_clock;
using namespace ts;
struct task_executor::task {
task_id id{0};
std::string name{};
std::function<void()> callback{};
/* will be set to true if the task has been canceled but is executing right now */
bool canceled{false};
std::optional<std::promise<void>> finish_callback{};
task* next{nullptr};
};
struct task_executor::task_recurring {
task_id id{0};
std::string name{};
bool shutdown{false};
std::optional<std::promise<void>> finish_callback{};
std::chrono::nanoseconds interval{};
std::chrono::system_clock::time_point last_invoked{};
std::chrono::system_clock::time_point scheduled_invoke{};
std::function<void(const std::chrono::system_clock::time_point& /* last executed */)> callback{};
task_recurring* next{nullptr};
};
struct task_executor::task_context {
std::mutex mutex{};
std::condition_variable notify{};
bool shutdown{false};
task_id id_index{1};
size_t task_count{};
task* task_head{nullptr};
task** task_tail{&this->task_head};
size_t task_recurring_count{};
task_recurring* task_recurring_head{nullptr};
task_exception_handler exception_handler{task_executor::abort_exception_handler};
};
struct task_executor::executor_context {
task_executor* handle{nullptr};
std::thread thread_handle{};
std::shared_ptr<struct task_context> task_context{};
/**
* Must be accessed while holding the task_context.mutex and shall never be changed except for the executor.
* Lifetime will be granted while holding the lock.
*/
task* executing_task{nullptr};
task_recurring* executing_recurring_task{nullptr};
};
task_executor::task_executor(size_t num_threads, const std::string &thread_prefix) {
this->task_context = std::make_shared<struct task_context>();
this->executors.reserve(num_threads);
for(size_t index{0}; index < num_threads; index++) {
auto handle = std::make_shared<executor_context>();
handle->handle = this;
handle->task_context = this->task_context;
handle->thread_handle = std::thread(task_executor::executor, handle);
if(!thread_prefix.empty()) {
threads::name(handle->thread_handle, thread_prefix + std::to_string(index + 1));
}
this->executors.push_back(std::move(handle));
}
}
task_executor::~task_executor() {
{
std::lock_guard task_lock{this->task_context->mutex};
for(auto& thread : this->executors) {
if(!thread->executing_recurring_task) {
continue;
}
/* TODO: Log error */
thread->executing_recurring_task->shutdown = true;
}
while(this->task_context->task_head) {
auto task = std::exchange(this->task_context->task_head, this->task_context->task_head->next);
delete task;
}
this->task_context->task_tail = &this->task_context->task_head;
this->task_context->task_count = 0;
while(this->task_context->task_recurring_head) {
auto task = std::exchange(this->task_context->task_recurring_head, this->task_context->task_recurring_head->next);
delete task;
}
this->task_context->task_recurring_count = 0;
}
for(auto& thread : this->executors) {
if(thread->thread_handle.joinable()) {
/* TODO: Print an error */
thread->thread_handle.detach();
}
}
}
void task_executor::set_exception_handler(task_exception_handler handler) {
std::lock_guard task_lock{this->task_context->mutex};
this->task_context->exception_handler = std::move(handler);
}
void task_executor::abort_exception_handler(const std::string &task, const std::exception_ptr &exception) {
std::string message{};
try {
std::rethrow_exception(exception);
} catch (const std::exception& ex) {
message = "std::exception::what() -> " + std::string{ex.what()};
} catch(...) {
message = "unknown exception";
}
std::cerr << "task_executor encountered an exception while executing task " << task << ": " << message << std::endl;
abort();
}
bool task_executor::shutdown(const std::chrono::system_clock::time_point &timeout) {
{
std::lock_guard task_lock{this->task_context->mutex};
this->task_context->shutdown = true;
this->task_context->notify.notify_all();
}
for(auto& thread : this->executors) {
if(timeout.time_since_epoch().count() > 0) {
auto now = system_clock::now();
if(now > timeout) {
/* failed to join all executors */
return false;
}
if(!threads::timed_join(thread->thread_handle, timeout - now)) {
/* thread failed to join */
return false;
}
} else {
threads::save_join(thread->thread_handle, false);
}
}
return true;
}
bool task_executor::schedule(task_id &task_id, std::string task_name, std::function<void()> callback) {
auto& task_context_ = this->task_context;
auto task = std::make_unique<task_executor::task>();
task->name = std::move(task_name);
task->callback = std::move(callback);
std::lock_guard task_lock{task_context_->mutex};
if(task_context_->shutdown) {
return false;
}
task->id = task_context_->id_index++;
if(!task->id) {
/* the task ids wrapped around I guess */
task->id = task_context_->id_index++;
}
task_id = task->id;
auto task_ptr = task.release();
task_context_->task_count++;
*task_context_->task_tail = task_ptr;
task_context_->task_tail = &task_ptr->next;
task_context_->notify.notify_one();
return true;
}
bool task_executor::schedule_repeating(task_id &task_id, std::string task_name, std::chrono::nanoseconds interval,
std::function<void(const std::chrono::system_clock::time_point &)> callback) {
auto& task_context_ = this->task_context;
auto task = std::make_unique<task_executor::task_recurring>();
task->name = std::move(task_name);
task->callback = std::move(callback);
task->interval = std::move(interval);
task->scheduled_invoke = std::chrono::system_clock::now();
std::lock_guard task_lock{task_context_->mutex};
if(task_context_->shutdown) {
return false;
}
task->id = task_context_->id_index++;
if(!task->id) {
/* the task ids wrapped around I guess */
task->id = task_context_->id_index++;
}
task_id = task->id;
task_context_->task_recurring_count++;
this->enqueue_recurring_task(task.release());
task_context_->notify.notify_one();
return true;
}
void task_executor::enqueue_recurring_task(task_recurring *task) {
auto& task_context_ = this->task_context;
if(!task_context_->task_recurring_head) {
/* No tasks pending. We could easily enqueue the task. */
task->next = nullptr;
task_context_->task_recurring_head = task;
} else {
/* Find the correct insert spot */
task_recurring* previous_task{nullptr};
task_recurring* next_task{task_context_->task_recurring_head};
while(true) {
if(next_task->scheduled_invoke > task->scheduled_invoke) {
break;
}
previous_task = next_task;
next_task = next_task->next;
if(!next_task) {
/* We reached the queue end. */
break;
}
}
task->next = next_task;
if(!previous_task) {
/* we're inserting the task as head */
assert(next_task == task_context_->task_recurring_head);
task_context_->task_recurring_head = task;
} else {
previous_task->next = task;
}
}
}
task_executor::task_cancel_result task_executor::internal_cancel_task(std::future<void> *future, task_id task_id) {
auto& task_context_ = this->task_context;
std::unique_lock task_lock{task_context->mutex};
/* 1. Search for a pending normal task */
{
task* previous_task{nullptr};
task* current_task{task_context_->task_head};
while(current_task) {
if(current_task->id == task_id) {
/* We found our task. Just remove and delete it. */
if(previous_task) {
previous_task->next = current_task->next;
} else {
assert(task_context_->task_head == current_task);
if(current_task->next) {
assert(task_context_->task_tail != &current_task->next);
task_context_->task_head = current_task->next;
} else {
assert(task_context_->task_tail == &current_task->next);
task_context_->task_head = nullptr;
task_context_->task_tail = &task_context_->task_head;
}
}
assert(task_context_->task_count > 0);
task_context_->task_count--;
task_lock.unlock();
delete current_task;
return task_cancel_result::pending_canceled;
}
previous_task = current_task;
current_task = current_task->next;
}
}
/* 2. Search for a pending recurring task */
{
task_recurring* previous_task{nullptr};
task_recurring* current_task{task_context_->task_recurring_head};
while(current_task) {
if(current_task->id == task_id) {
/* We found our task. Just remove and delete it. */
if(previous_task) {
previous_task->next = current_task->next;
} else {
assert(task_context_->task_recurring_head == current_task);
task_context_->task_recurring_head = current_task->next;
}
assert(task_context_->task_recurring_count > 0);
task_context_->task_recurring_count--;
task_lock.unlock();
delete current_task;
return task_cancel_result::pending_canceled;
}
previous_task = current_task;
current_task = current_task->next;
}
}
/* 3. Our task does not seem to pend anywhere. May it already gets executed. */
for(auto& executor : this->executors) {
if(executor->executing_task && executor->executing_task->id == task_id) {
auto task_handle = executor->executing_task;
if(task_handle->canceled) {
/* task has already been canceled */
return task_cancel_result::not_found;
}
task_handle->canceled = true;
if(future) {
assert(!task_handle->finish_callback.has_value());
task_handle->finish_callback = std::make_optional(std::promise<void>{});
*future = task_handle->finish_callback->get_future();
}
/*
* It gets executed right now.
* The task itself will be deleted by the executor.
* Note: No need to decrease the task count here since it has already been
* decreased when receiving the task by the executor.
*/
return task_cancel_result::running_canceled;
}
if(executor->executing_recurring_task && executor->executing_recurring_task->id == task_id) {
auto task_handle = executor->executing_recurring_task;
if(task_handle->shutdown) {
/* the task has already been canceled */
return task_cancel_result::not_found;
}
/*
* It gets executed right now.
* Setting shutdown flag to prevent rescheduling.
* The task will be deleted by the executor itself.
*/
task_handle->shutdown = true;
if(future) {
assert(!task_handle->finish_callback.has_value());
task_handle->finish_callback = std::make_optional(std::promise<void>{});
*future = task_handle->finish_callback->get_future();
}
assert(task_context_->task_recurring_count > 0);
task_context_->task_recurring_count--;
return task_cancel_result::running_canceled;
}
}
return task_cancel_result::not_found;
}
bool task_executor::cancel_task(task_id task_id) {
switch (this->internal_cancel_task(nullptr, task_id)) {
case task_cancel_result::pending_canceled:
case task_cancel_result::running_canceled:
return true;
case task_cancel_result::not_found:
return false;
default:
assert(false);
return false;
}
}
std::future<void> task_executor::cancel_task_joinable(task_id task_id) {
std::future<void> result{};
switch (this->internal_cancel_task(&result, task_id)) {
case task_cancel_result::pending_canceled:
/* May throw an exception? */
case task_cancel_result::not_found: {
std::promise<void> promise{};
promise.set_value();
return promise.get_future();
}
case task_cancel_result::running_canceled:
return result;
default:
assert(false);
return result;
}
}
void task_executor::executor(std::shared_ptr<executor_context> executor_context) {
auto& task_context = executor_context->task_context;
std::unique_lock task_lock{task_context->mutex};
while(true) {
assert(task_lock.owns_lock());
if(task_context->shutdown) {
break;
}
if(task_context->task_head) {
auto task = task_context->task_head;
if(task->next) {
assert(task_context->task_tail != &task->next);
task_context->task_head = task->next;
} else {
assert(task_context->task_tail == &task->next);
task_context->task_head = nullptr;
task_context->task_tail = &task_context->task_head;
}
assert(task_context->task_count > 0);
task_context->task_count--;
executor_context->executing_task = task;
task_lock.unlock();
try {
task->callback();
} catch (...) {
auto exception = std::current_exception();
task_lock.lock();
auto handler = task_context->exception_handler;
task_lock.unlock();
handler(task->name, exception);
}
task_lock.lock();
executor_context->executing_task = nullptr;
if(task->finish_callback.has_value()) {
task->finish_callback->set_value();
}
delete task;
continue;
}
auto execute_timestamp = std::chrono::system_clock::now();
std::chrono::system_clock::time_point next_timestamp{};
if(task_context->task_recurring_head) {
if(task_context->task_recurring_head->scheduled_invoke <= execute_timestamp) {
auto task = task_context->task_recurring_head;
task_context->task_recurring_head = task->next;
executor_context->executing_recurring_task = task;
task_lock.unlock();
try {
task->callback(task->last_invoked);
} catch (...) {
auto exception = std::current_exception();
task_lock.lock();
auto handler = task_context->exception_handler;
task_lock.unlock();
handler(task->name, exception);
}
task->last_invoked = execute_timestamp;
auto expected_next_invoke = execute_timestamp + std::chrono::duration_cast<std::chrono::system_clock::duration>(task->interval);
task->scheduled_invoke = std::max(std::chrono::system_clock::now(), expected_next_invoke);
task_lock.lock();
executor_context->executing_recurring_task = nullptr;
if(task->shutdown) {
if(task->finish_callback) {
task->finish_callback->set_value();
}
delete task;
} else {
executor_context->handle->enqueue_recurring_task(task);
}
continue;
} else {
next_timestamp = task_context->task_recurring_head->scheduled_invoke;
}
}
if(next_timestamp.time_since_epoch().count() > 0) {
task_context->notify.wait_until(task_lock, next_timestamp);
} else {
task_context->notify.wait(task_lock);
}
}
}
void task_executor::print_statistics(const std::function<void(const std::string &)>& println, bool print_task_list) {
println("Executor count: " + std::to_string(this->executors.size()));
std::lock_guard task_lock{this->task_context->mutex};
if(print_task_list) {
println("Tasks (" + std::to_string(this->task_context->task_count) + "):");
{
auto head = this->task_context->task_head;
while(head) {
println(" - " + head->name);
head = head->next;
}
}
println("Recurring task count (" + std::to_string(this->task_context->task_recurring_count) + "):");
{
auto head = this->task_context->task_recurring_head;
while(head) {
println(" - " + head->name);
head = head->next;
}
}
} else {
println("Task count: " + std::to_string(this->task_context->task_count));
println("Recurring task count: " + std::to_string(this->task_context->task_recurring_count));
}
}

245
src/misc/task_executor.h Normal file
View File

@ -0,0 +1,245 @@
#pragma once
#include <chrono>
#include <functional>
#include <mutex>
#include <atomic>
#include <future>
#include <cassert>
namespace ts {
typedef uint32_t task_id;
typedef std::function<void(const std::string& /* task name */, const std::exception_ptr& /* exception */)> task_exception_handler;
/**
* A basic task executor & scheduler for one time and repeating tasks
* Note: All methods are thread save or it's specified otherwise
*/
class task_executor {
public:
task_executor(size_t /* num threads */, const std::string& /* thread prefix */);
~task_executor();
void set_exception_handler(task_exception_handler);
/**
* Note: This method is not thread save.
* @returns `true` if all actions have been successfully shut down
*/
bool shutdown(const std::chrono::system_clock::time_point & /* timeout */);
/**
* Cancel a task. If the task is currently executing it will not block.
* @returns `true` if the task has been found and `false` if the task isn't known.
*/
bool cancel_task(task_id /* task id */);
/**
* Cancel a task with the possibility to wait until it has finished.
*/
std::future<void> cancel_task_joinable(task_id /* task id */);
/**
* @returns `true` if the task has successfully be enqueued for scheduling.
*/
bool schedule(task_id& /* task handle */, std::string /* name */, std::function<void()> /* callback */);
/**
* @returns `true` if the task has successfully be enqueued for repeating scheduling.
*/
bool schedule_repeating(task_id& /* task handle */,
std::string /* name */,
std::chrono::nanoseconds /* interval */,
std::function<void(const std::chrono::system_clock::time_point& /* last scheduled */)> /* callback */);
void print_statistics(const std::function<void(const std::string &)>& /* print function */, bool /* print task list */);
private:
struct task;
struct task_recurring;
struct task_context;
struct executor_context;
enum struct task_cancel_result {
not_found,
pending_canceled,
running_canceled,
};
std::vector<std::shared_ptr<executor_context>> executors{};
std::shared_ptr<task_context> task_context;
/**
* Enqueue the task into the task queue.
* Attention:
* 1. The task context mutex must be hold by the caller
* 2. The task should not be enqueued already
*/
void enqueue_recurring_task(task_recurring* /* task */);
[[nodiscard]] task_cancel_result internal_cancel_task(std::future<void>* /* future */, task_id /* task id */);
static void executor(std::shared_ptr<executor_context> /* context shared pointer */);
static void abort_exception_handler(const std::string& /* task name */, const std::exception_ptr& /* exception */);
};
/**
* Helper class for tasks which could be executed multiple times.
* It will avoid execution stacking while the task is executing.
* The task will never be executed twice, only sequential.
* Note: If the `multi_shot_task` handle gets deleted no enqueued tasks will be executed.
*/
struct multi_shot_task {
public:
explicit multi_shot_task() {}
multi_shot_task(std::shared_ptr<task_executor> executor, std::string task_name, std::function<void()> callback)
: inner{std::make_shared<execute_inner>(std::move(executor), std::move(task_name), std::move(callback))} {
std::weak_ptr weak_inner{this->inner};
this->inner->callback_wrapper = [weak_inner]{
auto callback_inner = weak_inner.lock();
if(!callback_inner) {
return;
}
auto result = callback_inner->schedule_kind.exchange(2);
assert(result == 1);
(void) result;
try {
(callback_inner->callback)();
execute_finished(&*callback_inner);
} catch (...) {
execute_finished(&*callback_inner);
std::rethrow_exception(std::current_exception());
}
};
}
multi_shot_task(const multi_shot_task&) = default;
multi_shot_task(multi_shot_task&& other) = default;
inline multi_shot_task& operator=(const multi_shot_task& other) {
this->inner = other.inner;
return *this;
}
inline multi_shot_task& operator=(multi_shot_task&& other) {
this->inner = std::move(other.inner);
return *this;
}
/**
* @returns `true` if the task has successfully be enqueued or is already enqueued
* and `false` if the `schedule` call failed or we have no task.
*/
inline bool enqueue() {
auto& inner_ = this->inner;
if(!inner_) {
return false;
}
{
//CAS loop: https://preshing.com/20150402/you-can-do-any-kind-of-atomic-read-modify-write-operation/
uint8_t current_state = inner_->schedule_kind.load();
uint8_t new_state;
do {
switch(current_state) {
case 0:
/* no execute has been scheduled */
new_state = 1;
break;
case 1:
case 3:
/* an execute is already scheduled */
return true;
case 2:
/* we're already executing now but we need a new execute */
new_state = 3;
return true;
default:
assert(false);
return false;
}
} while(!inner_->schedule_kind.compare_exchange_weak(current_state, new_state, std::memory_order_relaxed, std::memory_order_relaxed));
}
task_id task_id_;
auto result = inner_->executor->schedule(task_id_, inner_->task_name, inner->callback_wrapper);
if(!result) {
/*
* Task isn't scheduled any more. We failed to schedule it.
* Note: The task might got rescheduled again so may more than only one schedule attempt fail
* in total.
*/
inner_->schedule_kind = 0;
return false;
}
return true;
}
private:
struct execute_inner {
explicit execute_inner(std::shared_ptr<task_executor> executor, std::string name, std::function<void()> callback) noexcept
: task_name{std::move(name)}, executor{std::move(executor)}, callback{std::move(callback)} {}
std::string task_name;
std::shared_ptr<task_executor> executor;
std::function<void()> callback;
std::function<void()> callback_wrapper;
/**
* `0` not scheduled
* `1` scheduled
* `2` executing
* `3` executing with reschedule
*/
std::atomic<uint8_t> schedule_kind{0};
};
std::shared_ptr<execute_inner> inner{};
inline static void execute_finished(execute_inner* inner) {
auto current_state = inner->schedule_kind.load();
uint8_t new_state;
do {
switch(current_state) {
case 0:
case 1:
assert(false);
return;
case 2:
new_state = 0;
break;
case 3:
new_state = 1;
break;
default:
assert(false);
return;
};
} while(!inner->schedule_kind.compare_exchange_weak(current_state, new_state, std::memory_order_relaxed, std::memory_order_relaxed));
if(new_state == 1) {
/* a reschedule was requested */
task_id task_id_;
if(!inner->executor->schedule(task_id_, inner->task_name, inner->callback_wrapper)) {
/*
* Task isn't scheduled any more. We failed to schedule it.
* Note: The task might got rescheduled again so may more than only one schedule attempt fail
* in total.
*/
inner->schedule_kind = 0;
}
}
}
};
}

94
src/misc/threads.cpp Normal file
View File

@ -0,0 +1,94 @@
#include "./threads.h"
#include <system_error>
#include <cstring>
#ifdef WIN32
#include <Windows.h>
#else
#include <pthread.h>
#endif
std::string threads::name(std::thread &thread) {
#ifdef WIN32
static std::string _empty{};
return _empty;
#else
char buffer[255]; /* min 16 characters */
pthread_getname_np(thread.native_handle(), buffer, 255);
return std::string{buffer};
#endif
}
bool threads::name(std::thread &thread, const std::string_view &name) {
#ifdef WIN32
return false;
#else
char buffer[255]; /* min 16 characters */
memcpy(buffer, name.data(), name.length());
buffer[name.length()] = '\0';
buffer[16] = '\0'; /* cut of the name after 16 characters */
auto error = pthread_setname_np(thread.native_handle(), buffer);
return error == 0;
#endif
}
bool threads::save_join(std::thread &thread, bool rd) {
try {
if(thread.joinable())
thread.join();
} catch(const std::system_error& ex) {
if(ex.code() == std::errc::resource_deadlock_would_occur) {
if(rd)
return false;
throw;
} else if(ex.code() == std::errc::no_such_process) {
return false;
} else if(ex.code() == std::errc::invalid_argument) {
return false;
} else {
throw;
}
}
return true;
}
bool threads::timed_join(std::thread &thread, const std::chrono::nanoseconds &timeout) {
#ifdef WIN32
auto result = WaitForSingleObject(thread.native_handle(), (DWORD) std::chrono::floor<std::chrono::milliseconds>(timeout).count());
if(result != 0)
return false;
if(thread.joinable())
thread.join();
return result;
#else
struct timespec ts{};
if (clock_gettime(CLOCK_REALTIME, &ts) == -1)
return false; /* failed to get clock time */
auto seconds = std::chrono::floor<std::chrono::seconds>(timeout);
auto nanoseconds = std::chrono::ceil<std::chrono::nanoseconds>(timeout) - seconds;
ts.tv_sec += seconds.count();
ts.tv_nsec += nanoseconds.count();
if(ts.tv_nsec >= 1e9) {
ts.tv_sec += 1;
ts.tv_nsec -= 1e9;
}
auto result = pthread_timedjoin_np(thread.native_handle(), nullptr, &ts);
if(result > 0 && result != ESRCH) return false;
/* let the std lib set their flags */
std::thread _empty{}; /* could be destroyed even with an "active" thread handle because we overwrote the std::terminate() function with a macro */
_empty = std::move(thread);
/*
* Undefined behaviour:
* We destroy everything in a non trivial class,
* But when we take a closer look its just a wrapper around the native_handle type which could be an DWORD or a pthread_t which are both trivial destructible!
*/
memset(&_empty, 0, sizeof(_empty)); // NOLINT(bugprone-undefined-memory-manipulation)
return true;
#endif
}

26
src/misc/threads.h Normal file
View File

@ -0,0 +1,26 @@
#pragma once
#include <thread>
#include <string>
#include <string_view>
namespace threads {
extern bool name(std::thread& /* thread */, const std::string_view& /* name */);
extern std::string name(std::thread& /* thread */);
/*
* This function will not throw an error if the thread has already been joined.
* It returns true if join succeeded, false on any error (like thread has already be joined)
*/
extern bool save_join(std::thread& /* thread */, bool /* ignore resource deadlock */ = false);
extern bool timed_join(std::thread& /* thread */, const std::chrono::nanoseconds& /* timeout */);
template <typename Clock>
inline bool timed_join(std::thread& thread, const std::chrono::time_point<Clock>& timeout) {
auto now = Clock::now();
if(now > timeout)
timeout = now;
return timed_join(thread, timeout - now);
}
}

53
src/misc/utf8.h Normal file
View File

@ -0,0 +1,53 @@
#pragma once
#include <cstddef>
#include <string_view>
namespace utf8 {
[[nodiscard]] inline ssize_t count_characters(const std::string_view& message) {
ssize_t index{0};
ssize_t count{0};
while(index < message.length()){
count++;
auto current = (uint8_t) message[index];
if(current >= 128) { //UTF8 check
if(current >= 192 && (current <= 193 || current >= 245)) {
return -1;
} else if(current >= 194 && current <= 223) {
if(message.length() - index <= 1) {
return -1;
} else if((uint8_t) message[index + 1] >= 128 && (uint8_t) message[index + 1] <= 191) {
index += 1; //Valid
} else {
return -1;
}
} else if(current >= 224 && current <= 239) {
if(message.length() - index <= 2) {
return -1;
} else if((uint8_t) message[index + 1] >= 128 && (uint8_t) message[index + 1] <= 191 &&
(uint8_t) message[index + 2] >= 128 && (uint8_t) message[index + 2] <= 191) {
index += 2; //Valid
} else {
return -1;
}
} else if(current >= 240 && current <= 244) {
if(message.length() - index <= 3) {
return -1;
} else if((uint8_t) message[index + 1] >= 128 && (uint8_t) message[index + 1] <= 191 &&
(uint8_t) message[index + 2] >= 128 && (uint8_t) message[index + 2] <= 191 &&
(uint8_t) message[index + 3] >= 128 && (uint8_t) message[index + 3] <= 191) {
index += 3; //Valid
} else {
return -1;
}
} else {
return -1;
}
}
index++;
}
return count;
}
}

View File

@ -1,6 +1,5 @@
#include "AcknowledgeManager.h"
#include <cmath>
#include <misc/endianness.h>
#include <algorithm>
using namespace ts;
@ -21,10 +20,12 @@ void AcknowledgeManager::reset() {
auto pending_entries = std::move(this->entries);
lock.unlock();
/* save because entries are not accessable anymore */
for(const auto& entry : pending_entries)
if(entry->acknowledge_listener)
entry->acknowledge_listener->executionFailed("reset");
/* save because entries are not accessible anymore */
for(const auto& entry : pending_entries) {
if(entry->acknowledge_listener) {
(*entry->acknowledge_listener)(false);
}
}
}
}
@ -33,22 +34,22 @@ size_t AcknowledgeManager::awaiting_acknowledge() {
return this->entries.size();
}
void AcknowledgeManager::process_packet(ts::protocol::BasicPacket &packet) {
if(!packet.type().requireAcknowledge()) return;
void AcknowledgeManager::process_packet(uint8_t type, uint32_t id, void *ptr, std::unique_ptr<std::function<void(bool)>> ack) {
std::shared_ptr<Entry> entry{new Entry{}, [&](Entry* entry){
assert(this->destroy_packet);
this->destroy_packet(entry->packet_ptr);
delete entry;
}};
entry->acknowledge_listener = std::move(ack);
auto entry = make_shared<Entry>();
entry->acknowledge_listener = std::move(packet.getListener());
entry->buffer = packet.buffer();
entry->packet_type = type;
entry->packet_full_id = id;
entry->packet_ptr = ptr;
entry->resend_count = 0;
entry->first_send = system_clock::now();
entry->next_resend = entry->first_send + std::chrono::milliseconds{(int64_t) ceil(this->rto)};
entry->packet_type = packet.type().type();
entry->packet_id = packet.packetId();
entry->generation_id = packet.generationId();
entry->acknowledged = false;
entry->send_count = 1;
{
@ -61,11 +62,11 @@ bool AcknowledgeManager::process_acknowledge(uint8_t packet_type, uint16_t targe
PacketType target_type{packet_type == protocol::ACK_LOW ? PacketType::COMMAND_LOW : PacketType::COMMAND};
std::shared_ptr<Entry> entry;
std::unique_ptr<threads::Future<bool>> ack_listener;
std::unique_ptr<std::function<void(bool)>> ack_listener;
{
std::lock_guard lock{this->entry_lock};
for(auto it = this->entries.begin(); it != this->entries.end(); it++) {
if((*it)->packet_type == target_type && (*it)->packet_id == target_id) {
if((*it)->packet_type == target_type && (*it)->packet_full_id == target_id) {
entry = *it;
ack_listener = std::move(entry->acknowledge_listener); /* move it out so nobody else could call it as well */
@ -87,54 +88,48 @@ bool AcknowledgeManager::process_acknowledge(uint8_t packet_type, uint16_t targe
}
entry->acknowledged = true;
if(ack_listener) ack_listener->executionSucceed(true);
if(ack_listener) {
(*ack_listener)(true);
}
return true;
}
ssize_t AcknowledgeManager::execute_resend(const system_clock::time_point& now , std::chrono::system_clock::time_point &next_resend,std::deque<std::shared_ptr<Entry>>& buffers, string& error) {
size_t resend_count{0};
void AcknowledgeManager::execute_resend(const system_clock::time_point& now , std::chrono::system_clock::time_point &next_resend,std::deque<std::shared_ptr<Entry>>& buffers) {
std::deque<std::shared_ptr<Entry>> resend_failed;
vector<shared_ptr<Entry>> need_resend;
{
bool cleanup{false};
std::lock_guard lock{this->entry_lock};
need_resend.reserve(this->entries.size());
for (auto &entry : this->entries) {
this->entries.erase(std::remove_if(this->entries.begin(), this->entries.end(), [&](std::shared_ptr<Entry>& entry) {
if(entry->acknowledged) {
if(entry->next_resend + std::chrono::milliseconds{(int64_t) ceil(this->rto * 4)} <= now) { // Some resends are lost. So we just drop it after time
entry.reset();
cleanup = true;
if (entry->next_resend + std::chrono::milliseconds{(int64_t) ceil(this->rto * 4)} <= now) {
/* Some resends are lost. So we just drop it after time */
return true;
}
} else {
if(entry->next_resend <= now) {
entry->next_resend = now + std::chrono::milliseconds{(int64_t) std::min(ceil(this->rto), 1500.f)};
need_resend.push_back(entry);
entry->resend_count++;
entry->send_count++;
}
if(next_resend > entry->next_resend)
if (entry->next_resend <= now) {
if (entry->resend_count > 15 && entry->first_send + seconds(15) < now) {
/* packet resend seems to have failed */
resend_failed.push_back(std::move(entry));
return true;
} else {
entry->next_resend = now + std::chrono::milliseconds{(int64_t) std::min(ceil(this->rto), 1500.f)};
buffers.push_back(entry);
//entry->resend_count++; /* this MUST be incremented by the result handler (resend may fails) */
entry->send_count++;
}
}
if (next_resend > entry->next_resend) {
next_resend = entry->next_resend;
}
}
}
if(cleanup) {
this->entries.erase(std::remove_if(this->entries.begin(), this->entries.end(),
[](const auto& entry) { return !entry; }), this->entries.end());
}
return false;
}), this->entries.end());
}
for(const auto& packet : need_resend) {
if(packet->resend_count > 15 && packet->first_send + seconds(15) < now) { //FIXME configurable
error = "Failed to receive acknowledge for packet " + to_string(packet->packet_id) + " of type " + PacketTypeInfo::fromid(packet->packet_type).name();
return -1;
}
resend_count++;
buffers.push_back(packet);
for(const auto& failed : resend_failed) {
this->callback_resend_failed(this->callback_data, failed);
}
return resend_count;
}
/* we're not taking the clock granularity into account because its nearly 1ms and it would only add more branches */

View File

@ -1,48 +1,54 @@
#pragma once
#include <memory>
#include <protocol/Packet.h>
#include <chrono>
#include <functional>
#include <mutex>
#include "./Packet.h"
#define DEBUG_ACKNOWLEDGE
namespace ts::connection {
class VoiceClientConnection;
class AcknowledgeManager {
public:
struct Entry {
uint16_t packet_id{0};
uint16_t generation_id{0};
uint32_t packet_full_id{0};
uint8_t packet_type{0xFF};
uint8_t resend_count{0};
bool acknowledged : 1;
uint8_t send_count : 7;
pipes::buffer buffer;
std::chrono::system_clock::time_point first_send;
std::chrono::system_clock::time_point next_resend;
std::unique_ptr<threads::Future<bool>> acknowledge_listener;
std::unique_ptr<std::function<void(bool)>> acknowledge_listener;
void* packet_ptr;
};
typedef void(*callback_resend_failed_t)(void* /* user data */, const std::shared_ptr<Entry>& /* entry */);
AcknowledgeManager();
virtual ~AcknowledgeManager();
size_t awaiting_acknowledge();
[[nodiscard]] size_t awaiting_acknowledge();
void reset();
void process_packet(ts::protocol::BasicPacket& /* packet */);
bool process_acknowledge(uint8_t packet_type, uint16_t /* packet id */, std::string& /* error */);
void process_packet(uint8_t /* packet type */, uint32_t /* full packet id */, void* /* packet ptr */, std::unique_ptr<std::function<void(bool)>> /* ack listener */);
bool process_acknowledge(uint8_t /* packet type */, uint16_t /* packet id */, std::string& /* error */);
ssize_t execute_resend(
void execute_resend(
const std::chrono::system_clock::time_point& /* now */,
std::chrono::system_clock::time_point& /* next resend */,
std::deque<std::shared_ptr<Entry>>& /* buffers to resend */,
std::string& /* error */
std::deque<std::shared_ptr<Entry>>& /* buffers to resend */
);
[[nodiscard]] inline auto current_rto() const { return this->rto; }
[[nodiscard]] inline auto current_srtt() const { return this->srtt; }
[[nodiscard]] inline auto current_rttvar() const { return this->rttvar; }
void(*destroy_packet)(void* /* packet */){nullptr};
void* callback_data{nullptr};
callback_resend_failed_t callback_resend_failed{[](auto, auto){}}; /* must be valid all the time */
private:
std::mutex entry_lock;
std::deque<std::shared_ptr<Entry>> entries;

View File

@ -3,42 +3,11 @@
#define QLZ_COMPRESSION_LEVEL 1
#define QLZ_MEMORY_SAFE
#include "qlz/QuickLZ.h"
#include "buffers.h"
using namespace ts;
using namespace ts::connection;
using namespace std;
namespace ts::compression {
class thread_buffer {
public:
void* get_buffer(size_t size) {
if(size > 1024 * 1024 *5) /* we don't want to keep such big buffers in memory */
return malloc(size);
if(this->buffer_length < size) {
free(this->buffer_ptr);
size = std::max(size, (size_t) 1024);
this->buffer_ptr = malloc(size);
this->buffer_length = size;
}
return buffer_ptr;
}
void free_buffer(void* ptr) {
if(ptr == this->buffer_ptr) return;
free(ptr);
}
~thread_buffer() {
free(this->buffer_ptr);
}
private:
void* buffer_ptr{nullptr};
size_t buffer_length{0};
};
class qlz_states {
public:
qlz_states() noexcept {
@ -53,25 +22,28 @@ namespace ts::compression {
qlz_state_compress* state_compress{nullptr};
qlz_state_decompress* state_decompress{nullptr};
private:
};
thread_local thread_buffer qlz_buffer{};
thread_local qlz_states qlz_states{};
size_t qlz_decompressed_size(const void* payload, size_t payload_length) {
if(payload_length < 9) return 0; /* payload too small */
if(payload_length < 9) {
return 0; /* payload too small */
}
return qlz_size_decompressed((char*) payload) + 400;
}
bool qlz_decompress_payload(const void* payload, void* buffer, size_t* buffer_size) {
if(!qlz_states.state_decompress) return false;
if(!qlz_states.state_decompress) {
return false;
}
assert(payload != buffer);
size_t data_length = qlz_decompress((char*) payload, (char*) buffer, qlz_states.state_decompress);
if(data_length <= 0)
if(data_length <= 0) {
return false;
}
/* test for overflow */
if(data_length > *buffer_size) terminate();
@ -80,77 +52,29 @@ namespace ts::compression {
}
size_t qlz_compressed_size(const void* payload, size_t payload_length) {
(void) payload;
assert(payload_length >= 9);
//// "Always allocate size + 400 bytes for the destination buffer when compressing." <= http://www.quicklz.com/manual.html
return max(min(payload_length * 2, (size_t) (payload_length + 400ULL)), (size_t) 24ULL); /* at least 12 bytes (QLZ header) */
return payload_length + 400; // http://www.quicklz.com/manual.html
}
bool qlz_compress_payload(const void* payload, size_t payload_length, void* buffer, size_t* buffer_length) {
if(!qlz_states.state_compress) return false;
if(!qlz_states.state_compress) {
return false;
}
assert(payload != buffer);
assert(*buffer_length >= qlz_compressed_size(payload, payload_length));
size_t compressed_length = qlz_compress(payload, (char*) buffer, payload_length, qlz_states.state_compress);
if(compressed_length > *buffer_length) terminate();
if(compressed_length > *buffer_length) {
terminate();
}
if(compressed_length <= 0)
if(compressed_length <= 0) {
return false;
}
*buffer_length = compressed_length;
return true;
}
}
bool CompressionHandler::compress(protocol::BasicPacket* packet, std::string &error) {
auto packet_payload = packet->data();
auto header_length = packet->length() - packet_payload.length();
size_t max_compressed_payload_size = compression::qlz_compressed_size(packet_payload.data_ptr(), packet_payload.length());
auto target_buffer = buffer::allocate_buffer(max_compressed_payload_size + header_length);
size_t compressed_size{max_compressed_payload_size};
if(!compression::qlz_compress_payload(packet_payload.data_ptr(), packet_payload.length(), &target_buffer[header_length], &compressed_size)) return false;
memcpy(target_buffer.data_ptr(), packet->buffer().data_ptr(), header_length);
packet->buffer(target_buffer.range(0, compressed_size + header_length));
return true;
}
bool CompressionHandler::decompress(protocol::BasicPacket* packet, std::string &error) {
auto expected_length = compression::qlz_decompressed_size(packet->data().data_ptr(), packet->data().length());
if(expected_length > this->max_packet_size){ //Max 16MB. (97% Compression!)
error = "Invalid packet size. (Calculated target length of " + to_string(expected_length) + ". Max length: " + to_string(this->max_packet_size) + ")";
return false;
} else if(expected_length == 0) {
error = "Failed to calculate decompressed packet length";
return false;
}
auto header_length = packet->header().length() + packet->mac().length();
auto buffer = buffer::allocate_buffer(expected_length + header_length);
size_t compressed_size{expected_length};
if(!compression::qlz_decompress_payload(packet->data().data_ptr(), &buffer[header_length], &compressed_size)) return false;
memcpy(buffer.data_ptr(), packet->buffer().data_ptr(), header_length);
packet->buffer(buffer.range(0, compressed_size + header_length));
return true;
}
bool CompressionHandler::progressPacketIn(protocol::BasicPacket* packet, std::string &error) {
if(packet->isCompressed()) {
if(!this->decompress(packet, error)) return false;
packet->setCompressed(false);
}
return true;
}
bool CompressionHandler::progressPacketOut(protocol::BasicPacket* packet, std::string& error) {
if(packet->has_flag(protocol::PacketFlag::Compressed) && !packet->isCompressed()) {
if(!this->compress(packet, error)) return false;
packet->setCompressed(true);
}
return true;
}
CompressionHandler::CompressionHandler() { }
CompressionHandler::~CompressionHandler() { }
}

View File

@ -1,29 +1,12 @@
#pragma once
#include "Packet.h"
#include "./Packet.h"
namespace ts {
namespace compression {
/* Attention: These methods does not validate the data! */
size_t qlz_decompressed_size(const void* payload, size_t payload_length);
bool qlz_decompress_payload(const void* payload, void* buffer, size_t* buffer_size); //Attention: payload & buffer must be differen!
namespace ts::compression {
/* Attention: These methods does not validate the data! */
size_t qlz_decompressed_size(const void* payload, size_t payload_length);
bool qlz_decompress_payload(const void* payload, void* buffer, size_t* buffer_size); //Attention: payload & buffer must be different!
size_t qlz_compressed_size(const void* payload, size_t payload_length);
bool qlz_compress_payload(const void* payload, size_t payload_length, void* buffer, size_t* buffer_length);
}
namespace connection {
class CompressionHandler {
public:
CompressionHandler();
virtual ~CompressionHandler();
bool progressPacketOut(protocol::BasicPacket*, std::string&);
bool progressPacketIn(protocol::BasicPacket*, std::string&);
size_t max_packet_size = 16 * 1024;
private:
bool compress(protocol::BasicPacket*, std::string &error);
bool decompress(protocol::BasicPacket*, std::string &error);
};
}
size_t qlz_compressed_size(const void* payload, size_t payload_length);
bool qlz_compress_payload(const void* payload, size_t payload_length, void* buffer, size_t* buffer_length);
}

View File

@ -1,13 +1,14 @@
//#define NO_OPEN_SSL /* because we're lazy and dont want to build this lib extra for the TeaClient */
#define FIXEDINT_H_INCLUDED /* else it will be included by ge */
#include "misc/endianness.h"
#include <ed25519/ed25519.h>
#include <cstdint>
#include <ed25519/ge.h>
#include <log/LogUtils.h>
#include "misc/memtracker.h"
#include "misc/digest.h"
#include "CryptHandler.h"
#include <ed25519/ed25519.h>
#include <mutex>
#include "./CryptHandler.h"
#include "../misc/endianness.h"
#include "../misc/memtracker.h"
#include "../misc/digest.h"
#include "../misc/sassert.h"
using namespace std;
@ -18,6 +19,9 @@ using namespace ts::protocol;
CryptHandler::CryptHandler() {
memtrack::allocated<CryptHandler>(this);
this->cipher_code = find_cipher("rijndael");
assert(this->cipher_code >= 0);
this->reset();
}
CryptHandler::~CryptHandler() {
@ -25,23 +29,26 @@ CryptHandler::~CryptHandler() {
}
void CryptHandler::reset() {
this->useDefaultChipherKeyNonce = true;
this->encryption_initialized_ = true;
this->iv_struct_length = 0;
memset(this->iv_struct, 0, sizeof(this->iv_struct));
memcpy(this->current_mac, CryptHandler::default_mac, sizeof(CryptHandler::default_mac));
for(auto& cache : this->cache_key_client)
for(auto& cache : this->cache_key_client) {
cache.generation = 0xFFEF;
for(auto& cache : this->cache_key_server)
}
for(auto& cache : this->cache_key_server) {
cache.generation = 0xFFEF;
}
}
#define SHARED_KEY_BUFFER_LENGTH (256)
bool CryptHandler::setupSharedSecret(const std::string& alpha, const std::string& beta, ecc_key *publicKey, ecc_key *ownKey, std::string &error) {
bool CryptHandler::setupSharedSecret(const std::string& alpha, const std::string& beta, ecc_key *remote_public_key, ecc_key *own_private_key, std::string &error) {
size_t buffer_length = SHARED_KEY_BUFFER_LENGTH;
uint8_t buffer[SHARED_KEY_BUFFER_LENGTH];
int err;
if((err = ecc_shared_secret(ownKey, publicKey, buffer, (unsigned long*) &buffer_length)) != CRYPT_OK){
if((err = ecc_shared_secret(own_private_key, remote_public_key, buffer, (unsigned long*) &buffer_length)) != CRYPT_OK){
error = "Could not calculate shared secret. Message: " + string(error_to_string(err));
return false;
}
@ -63,7 +70,7 @@ bool CryptHandler::setupSharedSecret(const std::string& alpha, const std::string
}
{
lock_guard lock(this->cache_key_lock);
std::lock_guard lock(this->cache_key_lock);
memcpy(this->iv_struct, iv_buffer, SHA_DIGEST_LENGTH);
this->iv_struct_length = SHA_DIGEST_LENGTH;
@ -71,13 +78,13 @@ bool CryptHandler::setupSharedSecret(const std::string& alpha, const std::string
digest::sha1((const char*) iv_buffer, SHA_DIGEST_LENGTH, mac_buffer);
memcpy(this->current_mac, mac_buffer, 8);
this->useDefaultChipherKeyNonce = false;
this->encryption_initialized_ = false;
}
return true;
}
void _fe_neg(fe h, const fe f) {
void fe_neg_(fe h, const fe f) {
int32_t f0 = f[0];
int32_t f1 = f[1];
int32_t f2 = f[2];
@ -117,8 +124,8 @@ inline void keyMul(uint8_t(& target_buffer)[32], const uint8_t* publicKey /* com
ge_frombytes_negate_vartime(&keyA, publicKey);
if(negate) {
_fe_neg(*(fe*) &keyA.X, *(const fe*) &keyA.X); /* undo negate */
_fe_neg(*(fe*) &keyA.T, *(const fe*) &keyA.T); /* undo negate */
fe_neg_(*(fe*) &keyA.X, *(const fe*) &keyA.X); /* undo negate */
fe_neg_(*(fe*) &keyA.T, *(const fe*) &keyA.T); /* undo negate */
}
ge_scalarmult_vartime(&result, privateKey, &keyA);
@ -148,7 +155,7 @@ bool CryptHandler::setupSharedSecretNew(const std::string &alpha, const std::str
uint8_t mac_buffer[SHA_DIGEST_LENGTH];
digest::sha1((char*) this->iv_struct, 64, mac_buffer);
memcpy(this->current_mac, mac_buffer, 8);
this->useDefaultChipherKeyNonce = false;
this->encryption_initialized_ = false;
}
return true;
@ -165,7 +172,9 @@ bool CryptHandler::generate_key_nonce(
) {
auto& key_cache_array = to_server ? this->cache_key_client : this->cache_key_server;
if(type < 0 || type >= key_cache_array.max_size()) {
#if 0
logError(0, "Tried to generate a crypt key with invalid type ({})!", type);
#endif
return false;
}
@ -174,7 +183,7 @@ bool CryptHandler::generate_key_nonce(
auto& key_cache = key_cache_array[type];
if(key_cache.generation != generation) {
const size_t buffer_length = 6 + this->iv_struct_length;
sassert(buffer_length < GENERATE_BUFFER_LENGTH);
assert(buffer_length < GENERATE_BUFFER_LENGTH);
char buffer[GENERATE_BUFFER_LENGTH];
memset(buffer, 0, buffer_length);
@ -184,22 +193,23 @@ bool CryptHandler::generate_key_nonce(
} else {
buffer[0] = 0x30;
}
buffer[1] = (char) (type & 0xF);
buffer[1] = (char) (type & 0xFU);
le2be32(generation, buffer, 2);
memcpy(&buffer[6], this->iv_struct, this->iv_struct_length);
digest::sha256(buffer, buffer_length, key_cache.key_nonce);
digest::sha256(buffer, buffer_length, key_cache.key_nonce.value);
key_cache.generation = generation;
}
memcpy(key.data(), key_cache.key, 16);
memcpy(nonce.data(), key_cache.nonce, 16);
memcpy(key.data(), key_cache.key_nonce.key, 16);
memcpy(nonce.data(), key_cache.key_nonce.nonce, 16);
}
//Xor the key
key[0] ^= (uint8_t) ((packet_id >> 8) & 0xFFU);
key[1] ^=(packet_id & 0xFFU);
key[0] ^= (uint8_t) (packet_id >> 8U);
key[1] ^= (uint8_t) packet_id;
return true;
}
@ -210,8 +220,9 @@ bool CryptHandler::verify_encryption(const pipes::buffer_view &packet, uint16_t
key_t key{};
nonce_t nonce{};
if(!generate_key_nonce(true, (protocol::PacketType) (packet[12] & 0xF), packet_id, generation, key, nonce))
if(!generate_key_nonce(true, (protocol::PacketType) ((uint8_t) packet[12] & 0xFU), packet_id, generation, key, nonce)) {
return false;
}
auto mac = packet.view(0, 8);
auto header = packet.view(8, 5);
@ -219,14 +230,13 @@ bool CryptHandler::verify_encryption(const pipes::buffer_view &packet, uint16_t
auto length = data.length();
/* static shareable void buffer */
const static unsigned long void_target_length = 2048;
static uint8_t void_target_buffer[2048];
if(void_target_length < length)
const static unsigned long void_target_length{2048};
uint8_t void_target_buffer[2048];
if(void_target_length < length) {
return false;
}
//TODO: Cache find_cipher
err = eax_decrypt_verify_memory(find_cipher("rijndael"),
err = eax_decrypt_verify_memory(this->cipher_code,
(uint8_t *) key.data(), /* the key */
(size_t) key.size(), /* key is 16 bytes */
(uint8_t *) nonce.data(), /* the nonce */
@ -244,18 +254,16 @@ bool CryptHandler::verify_encryption(const pipes::buffer_view &packet, uint16_t
return err == CRYPT_OK && success;
}
#define tmp_buffer_size (2048)
bool CryptHandler::decrypt(const void *header, size_t header_length, void *payload, size_t payload_length, const void *mac, const key_t &key, const nonce_t &nonce, std::string &error) {
if(tmp_buffer_size < payload_length) {
error = "buffer too large";
bool CryptHandler::decrypt(const void *header, size_t header_length, void *payload, size_t payload_length, const void *mac, const key_t &key, const nonce_t &nonce, std::string &error) const {
const static unsigned long kTempBufferLength{2048};
uint8_t tmp_buffer[kTempBufferLength];
if(kTempBufferLength < payload_length) {
error = "packet too large";
return false;
}
uint8_t tmp_buffer[tmp_buffer_size];
int success;
//TODO: Cache cipher
auto err = eax_decrypt_verify_memory(find_cipher("rijndael"),
auto err = eax_decrypt_verify_memory(this->cipher_code,
(const uint8_t *) key.data(), /* the key */
(unsigned long) key.size(), /* key is 16 bytes */
(const uint8_t *) nonce.data(), /* the nonce */
@ -288,17 +296,11 @@ bool CryptHandler::encrypt(
void *payload, size_t payload_length,
void *mac,
const key_t &key, const nonce_t &nonce, std::string &error) {
if(tmp_buffer_size < payload_length) {
error = "buffer too large";
return false;
}
uint8_t tmp_buffer[tmp_buffer_size];
size_t tag_length{8};
uint8_t tag_buffer[16];
static_assert(sizeof(unsigned long) <= sizeof(tag_length));
auto err = eax_encrypt_authenticate_memory(find_cipher("rijndael"),
auto err = eax_encrypt_authenticate_memory(this->cipher_code,
(uint8_t *) key.data(), /* the key */
(unsigned long) key.size(), /* key is 16 bytes */
(uint8_t *) nonce.data(), /* the nonce */
@ -307,7 +309,7 @@ bool CryptHandler::encrypt(
(unsigned long) header_length, /* header length */
(uint8_t *) payload, /* The plain text */
(unsigned long) payload_length, /* Plain text length */
(uint8_t *) tmp_buffer, /* The result buffer */
(uint8_t *) payload, /* The result buffer */
(uint8_t *) tag_buffer,
(unsigned long *) &tag_length
);
@ -319,6 +321,5 @@ bool CryptHandler::encrypt(
}
memcpy(mac, tag_buffer, 8);
memcpy(payload, tmp_buffer, payload_length);
return true;
}

View File

@ -2,89 +2,78 @@
#include <array>
#include <string>
#include "Packet.h"
#include <tomcrypt.h>
#include "./Packet.h"
#undef byte /* the macro byte gets defined by tomcrypt_macros. We have to undefine it */
namespace ts {
namespace connection {
class CryptHandler {
enum Methode {
TEAMSPEAK_3_1,
TEAMSPEAK_3
};
namespace ts::connection {
class CryptHandler {
public:
typedef std::array<uint8_t, 16> key_t;
typedef std::array<uint8_t, 16> nonce_t;
CryptHandler();
~CryptHandler();
void reset();
bool setupSharedSecret(const std::string& /* alpha */, const std::string& /* beta */, ecc_key* /* remote_public_key */, ecc_key* /* own_private_key */, std::string &/* error */);
bool setupSharedSecret(const std::string& /* alpha */, const std::string& /* beta */, const std::string& /* shared_key */, std::string &/* error */);
bool setupSharedSecretNew(const std::string& alpha, const std::string& beta, const char privateKey[32], const char publicKey[32]);
bool encrypt(
const void* /* header */, size_t /* header length */,
void* /* payload */, size_t /* payload length */,
void* /* mac */, /* mac must be 8 bytes long! */
const key_t& /* key */, const nonce_t& /* nonce */,
std::string& /* error */);
bool decrypt(
const void* /* header */, size_t /* header length */,
void* /* payload */, size_t /* payload length */,
const void* /* mac */, /* mac must be 8 bytes long! */
const key_t& /* key */, const nonce_t& /* nonce */,
std::string& /* error */) const;
bool generate_key_nonce(bool /* to server */, uint8_t /* packet type */, uint16_t /* packet id */, uint16_t /* generation */, key_t& /* key */, nonce_t& /* nonce */);
bool verify_encryption(const pipes::buffer_view& /* data */, uint16_t /* packet id */, uint16_t /* generation */);
inline void write_default_mac(void* buffer) {
memcpy(buffer, this->current_mac, 8);
}
[[nodiscard]] inline bool encryption_initialized() const { return !this->encryption_initialized_; }
static constexpr key_t kDefaultKey{'c', ':', '\\', 'w', 'i', 'n', 'd', 'o', 'w', 's', '\\', 's', 'y', 's', 't', 'e'}; //c:\windows\syste
static constexpr nonce_t kDefaultNonce{'m', '\\', 'f', 'i', 'r', 'e', 'w', 'a', 'l', 'l', '3', '2', '.', 'c', 'p', 'l'}; //m\firewall32.cpl
private:
static constexpr char default_mac[8] = {'T', 'S', '3', 'I', 'N', 'I', 'T', '1'}; //TS3INIT1
struct KeyCache {
uint16_t generation = 0xFFEF;
union {
union _key_nonce {
struct {
uint8_t key[16];
uint8_t nonce[16];
};
uint8_t key_nonce[32];
};
uint8_t value[32];
} key_nonce;
};
public:
typedef std::array<uint8_t, 16> key_t;
typedef std::array<uint8_t, 16> nonce_t;
CryptHandler();
~CryptHandler();
void reset();
bool encryption_initialized_{false};
int cipher_code{-1};
//TeamSpeak old
bool setupSharedSecret(const std::string& alpha, const std::string& beta, ecc_key* publicKey, ecc_key* ownKey, std::string &error);
bool setupSharedSecret(const std::string& alpha, const std::string& beta, const std::string& sharedKey, std::string &error);
/* for the old protocol SHA1 length for the new 64 bytes */
uint8_t iv_struct[64];
uint8_t iv_struct_length{0};
//TeamSpeak new
bool setupSharedSecretNew(const std::string& alpha, const std::string& beta, const char privateKey[32], const char publicKey[32]);
uint8_t current_mac[8]{};
/* mac must be 8 bytes long! */
bool encrypt(
const void* /* header */, size_t /* header length */,
void* /* payload */, size_t /* payload length */,
void* /* mac */,
const key_t& /* key */, const nonce_t& /* nonce */,
std::string& /* error */);
std::mutex cache_key_lock{};
std::array<KeyCache, /* protocol::PACKET_MAX */ 0x08> cache_key_client{};
std::array<KeyCache, /* protocol::PACKET_MAX */ 0x08> cache_key_server{};
/* mac must be 8 bytes long! */
bool decrypt(
const void* /* header */, size_t /* header length */,
void* /* payload */, size_t /* payload length */,
const void* /* mac */,
const key_t& /* key */, const nonce_t& /* nonce */,
std::string& /* error */);
bool generate_key_nonce(bool /* to server */, uint8_t /* packet type */, uint16_t /* packet id */, uint16_t /* generation */, key_t& /* key */, nonce_t& /* nonce */);
bool verify_encryption(const pipes::buffer_view& data, uint16_t packet_id, uint16_t generation);
inline void write_default_mac(void* buffer) {
memcpy(buffer, this->current_mac, 8);
}
static constexpr key_t default_key{'c', ':', '\\', 'w', 'i', 'n', 'd', 'o', 'w', 's', '\\', 's', 'y', 's', 't', 'e'}; //c:\windows\syste
static constexpr nonce_t default_nonce{'m', '\\', 'f', 'i', 'r', 'e', 'w', 'a', 'l', 'l', '3', '2', '.', 'c', 'p', 'l'}; //m\firewall32.cpl
private:
static constexpr char default_mac[8] = {'T', 'S', '3', 'I', 'N', 'I', 'T', '1'}; //TS3INIT1
bool generate_key_nonce(protocol::BasicPacket* packet, bool use_default, uint8_t(&)[16] /* key */, uint8_t(&)[16] /* nonce */);
//The default key and nonce
bool useDefaultChipherKeyNonce = true;
/* for the old protocol SHA1 length for the new 64 bytes */
uint8_t iv_struct[64];
uint8_t iv_struct_length = 0;
uint8_t current_mac[8];
std::mutex cache_key_lock;
std::array<KeyCache, protocol::PACKET_MAX> cache_key_client;
std::array<KeyCache, protocol::PACKET_MAX> cache_key_server;
static_assert(sizeof(current_mac) == sizeof(default_mac), "invalid mac");
static_assert(sizeof(iv_struct) == 64, "invalid iv struct");
};
}
static_assert(sizeof(current_mac) == sizeof(default_mac), "invalid mac");
static_assert(sizeof(iv_struct) == 64, "invalid iv struct");
};
}

View File

@ -3,238 +3,15 @@
//
#include <cstring>
#include <iostream>
#include <bitset>
#include "Packet.h"
#include "buffers.h"
#include "misc/endianness.h"
#include <mutex>
#include "./Packet.h"
#include "../misc/endianness.h"
#include "../misc/spin_mutex.h"
using namespace std;
namespace ts {
namespace protocol {
PacketTypeInfo::PacketTypeInfo(const std::string& name, PacketType type, bool ack, int max_length) noexcept {
this->data = new PacketTypeProperties{name, type, max_length, ack};
this->owns_data = true;
if(type < 0x0F)
types.insert({type, *this});
}
PacketTypeInfo::~PacketTypeInfo() {
if(this->owns_data)
delete this->data;
}
PacketTypeInfo::PacketTypeInfo(const PacketTypeInfo &red) : data(red.data) { }
std::map<int, PacketTypeInfo> PacketTypeInfo::types;
PacketTypeInfo PacketTypeInfo::fromid(int id) {
for(const auto& elm : types)
if(elm.first == id) return elm.second;
return PacketTypeInfo::Undefined;
}
PacketTypeInfo PacketTypeInfo::Voice = {"Voice", PacketType::VOICE, false, 1024};
PacketTypeInfo PacketTypeInfo::VoiceWhisper = {"VoiceWhisper", PacketType::VOICE_WHISPER, false, 1024};
PacketTypeInfo PacketTypeInfo::Command = {"Command", PacketType::COMMAND, true, 487};
PacketTypeInfo PacketTypeInfo::CommandLow = {"CommandLow", PacketType::COMMAND_LOW, true, 487};
PacketTypeInfo PacketTypeInfo::Ping = {"Ping", PacketType::PING, false, 1024};
PacketTypeInfo PacketTypeInfo::Pong = {"Pong", PacketType::PONG, false, 1024};
PacketTypeInfo PacketTypeInfo::Ack = {"Ack", PacketType::ACK, false, 1024};
PacketTypeInfo PacketTypeInfo::AckLow = {"AckLow", PacketType::ACK_LOW, false, 1024};
PacketTypeInfo PacketTypeInfo::Init1 = {"Init1", PacketType::INIT1, false, 1024};
PacketTypeInfo PacketTypeInfo::Undefined = {"Undefined", PacketType::UNDEFINED, false, 1024};
namespace PacketFlag {
std::string to_string(PacketFlag flag){
switch(flag){
case Fragmented:
return "Fragmented";
case NewProtocol:
return "NewProtocol";
case Compressed:
return "Compressed";
case Unencrypted:
return "Unencrypted";
default:
return "None";
}
}
}
BasicPacket::BasicPacket(size_t header_length, size_t data_length) {
this->_header_length = (uint8_t) header_length;
this->_buffer = pipes::buffer(MAC_SIZE + this->_header_length + data_length);
memset(this->_buffer.data_ptr(), 0, this->_buffer.length());
}
BasicPacket::~BasicPacket() {}
void BasicPacket::append_data(const std::vector<pipes::buffer> &data) {
size_t length = 0;
for(const auto& buffer : data)
length += buffer.length();
/* we've to allocate a new buffer because out buffer is fixed in size */
size_t index = this->_buffer.length();
auto new_buffer = buffer::allocate_buffer(length + index);
new_buffer.write(this->_buffer, index);
for(const auto& buffer : data) {
new_buffer.write(buffer, buffer.length(), index);
index += buffer.length();
}
this->_buffer = new_buffer;
}
std::string BasicPacket::flags() const {
std::string result;
if(this->has_flag(PacketFlag::Unencrypted)) result += string(result.empty() ? "" : " | ") + "Unencrypted";
if(this->has_flag(PacketFlag::Compressed)) result += string(result.empty() ? "" : " | ") + "Compressed";
if(this->has_flag(PacketFlag::Fragmented)) result += string(result.empty() ? "" : " | ") + "Fragmented";
if(this->has_flag(PacketFlag::NewProtocol)) result += string(result.empty() ? "" : " | ") + "NewProtocol";
if(result.empty()) result = "none";
return result;
}
void BasicPacket::applyPacketId(PacketIdManager& manager) {
this->applyPacketId(manager.nextPacketId(this->type()), manager.generationId(this->type()));
}
void BasicPacket::applyPacketId(uint16_t packetId, uint16_t generationId) {
if(this->memory_state.id_branded)
throw std::logic_error("Packet already got a packet id!");
this->memory_state.id_branded = true;
this->setPacketId(packetId, generationId);
}
Command BasicPacket::asCommand() {
return Command::parse(this->data());
}
/**
* @param buffer -> [mac][Header [uint16 BE packetId | [uint8](4bit flags | 4bit type)]][Data]
* @return
*/
std::unique_ptr<ServerPacket> ServerPacket::from_buffer(const pipes::buffer_view &buffer) {
auto result = make_unique<ServerPacket>();
result->_buffer = buffer.own_buffer();
result->_header_length = SERVER_HEADER_SIZE;
return result;
}
ServerPacket::ServerPacket(uint8_t flagMask, const pipes::buffer_view& data) : BasicPacket(SERVER_HEADER_SIZE, data.length()) {
this->header()[2] = flagMask;
memcpy(this->data().data_ptr(), data.data_ptr(), data.length());
}
ServerPacket::ServerPacket(const PacketTypeInfo& type, const pipes::buffer_view& data) : BasicPacket(SERVER_HEADER_SIZE, data.length()) {
this->header()[2] |= (uint8_t) type.type();
memcpy(this->data().data_ptr(), data.data_ptr(), data.length());
}
ServerPacket::ServerPacket(ts::protocol::PacketTypeInfo type, size_t data_length) : BasicPacket(SERVER_HEADER_SIZE, data_length) {
this->header()[2] |= type.type();
}
ServerPacket::~ServerPacket() {}
uint16_t ServerPacket::packetId() const {
return be2le16(&this->header()[0]);
}
void ServerPacket::setPacketId(uint16_t pkId, uint16_t gen) {
le2be16(pkId, &this->header()[0]);
this->genId = gen;
}
uint16_t ServerPacket::generationId() const {
return this->genId;
}
PacketTypeInfo ServerPacket::type() const {
return PacketTypeInfo::fromid(this->header()[2] & 0xF);
}
std::unique_ptr<ClientPacket> ClientPacket::from_buffer(const pipes::buffer_view &buffer) {
auto result = make_unique<ClientPacket>();
result->_buffer = buffer.own_buffer();
result->_header_length = CLIENT_HEADER_SIZE;
return result;
}
ClientPacket::ClientPacket(const PacketTypeInfo &type, const pipes::buffer_view& data) : BasicPacket(CLIENT_HEADER_SIZE, data.length()) {
this->header()[4] = type.type() & 0xF;
memcpy(this->data().data_ptr(), data.data_ptr(), data.length());
}
ClientPacket::ClientPacket(const PacketTypeInfo &type, uint8_t flag_mask, const pipes::buffer_view& data) : ClientPacket(type, data) {
this->header()[4] |= flag_mask;
}
ClientPacket::~ClientPacket() {}
uint16_t ClientPacket::packetId() const {
return be2le16(&this->header()[0]);
}
uint16_t ClientPacket::generationId() const {
return this->genId;
}
PacketTypeInfo ClientPacket::type() const {
return PacketTypeInfo::fromid(this->header()[4] & 0xF);
}
void ClientPacket::type(const ts::protocol::PacketTypeInfo &type) {
auto& field = this->header().data_ptr<uint8_t>()[4];
field &= (uint8_t) ~0xF;
field |= type.type();
}
void ClientPacket::setPacketId(uint16_t pkId, uint16_t gen) {
this->header()[0] = (uint8_t) ((pkId >> 8) & 0xFF);
this->header()[1] = (uint8_t) ((pkId >> 0) & 0xFF);
this->genId = gen;
}
uint16_t ClientPacket::clientId() const {
return be2le16(&this->header()[2]);
}
void ClientPacket::clientId(uint16_t clId) {
this->header()[2] = clId >> 8;
this->header()[3] = clId & 0xFF;
}
/* New packet parser API */
bool PacketParser::is_encrypted() const {
if(this->decrypted) return false;
return (this->flags() & PacketFlag::Unencrypted) == 0;
}
bool PacketParser::is_compressed() const {
if(this->uncompressed) return false;
return (this->flags() & PacketFlag::Compressed) > 0;
}
bool PacketParser::is_fragmented() const {
if(this->defragmented) return false;
return (this->flags() & PacketFlag::Fragmented) > 0;
}
uint16_t ClientPacketParser::packet_id() const { return be2le16(this->_buffer.data_ptr<uint8_t>(), ClientPacketParser::kHeaderOffset + 0); }
uint16_t ClientPacketParser::client_id() const { return be2le16(this->_buffer.data_ptr<uint8_t>(), ClientPacketParser::kHeaderOffset + 2); }
uint8_t ClientPacketParser::type() const { return (uint8_t) this->_buffer[ClientPacketParser::kHeaderOffset + 4] & 0xFU; }
@ -244,4 +21,172 @@ namespace ts {
uint8_t ServerPacketParser::type() const { return (uint8_t) this->_buffer[ClientPacketParser::kHeaderOffset + 2] & 0xFU; }
uint8_t ServerPacketParser::flags() const { return (uint8_t) this->_buffer[ClientPacketParser::kHeaderOffset + 2] & 0xF0U; }
}
void construct_ocp(protocol::OutgoingClientPacket* packet) {
new (packet) protocol::OutgoingClientPacket{};
}
void deconstruct_ocp(protocol::OutgoingClientPacket* packet) {
packet->~OutgoingClientPacket();
}
void reset_ocp(protocol::OutgoingClientPacket* packet, size_t payload_size) {
packet->next = nullptr;
packet->payload_size = payload_size;
packet->type_and_flags_ = 0;
packet->generation = 0;
}
void construct_osp(protocol::OutgoingServerPacket* packet) {
new (packet) protocol::OutgoingServerPacket{};
}
void deconstruct_osp(protocol::OutgoingServerPacket* packet) {
packet->~OutgoingServerPacket();
}
void reset_osp(protocol::OutgoingServerPacket* packet, size_t payload_size) {
packet->next = nullptr;
packet->payload_size = payload_size;
packet->type_and_flags_ = 0;
packet->generation = 0;
}
#if 1
#define BUKKIT_ENTRY_SIZE (1650)
#define BUKKIT_MAX_ENTRIES (3000)
struct OSPBukkitEntry {
bool extra_allocated;
OSPBukkitEntry* next;
};
spin_mutex osp_mutex{};
size_t sdp_count{0};
OSPBukkitEntry* osp_head{nullptr};
OSPBukkitEntry** osp_tail{&osp_head};
protocol::OutgoingServerPacket* osp_from_bosp(OSPBukkitEntry* bops) {
return reinterpret_cast<protocol::OutgoingServerPacket*>((char*) bops + sizeof(OSPBukkitEntry));
}
OSPBukkitEntry* bosp_from_osp(protocol::OutgoingServerPacket* ops) {
return reinterpret_cast<OSPBukkitEntry*>((char*) ops - sizeof(OSPBukkitEntry));
}
void destroy_bosp(OSPBukkitEntry* entry) {
deconstruct_osp(osp_from_bosp(entry));
::free(entry);
}
OSPBukkitEntry* construct_bosp(size_t payload_size) {
auto base_size = sizeof(OSPBukkitEntry) + sizeof(protocol::OutgoingServerPacket) - 1;
auto full_size = base_size + payload_size;
auto bentry = (OSPBukkitEntry*) malloc(full_size);
bentry->next = nullptr;
bentry->extra_allocated = false;
construct_osp(osp_from_bosp(bentry));
return bentry;
}
void protocol::OutgoingServerPacket::free_object() {
auto bentry = (OSPBukkitEntry*) bosp_from_osp(this);
if(bentry->extra_allocated) {
destroy_bosp(bentry);
return;
}
std::unique_lock block{osp_mutex};
if(sdp_count >= BUKKIT_MAX_ENTRIES) {
block.unlock();
destroy_bosp(bentry);
return;
}
assert(!bentry->next);
*osp_tail = bentry;
osp_tail = &bentry->next;
sdp_count++;
}
protocol::OutgoingServerPacket* protocol::allocate_outgoing_server_packet(size_t payload_size) {
if(BUKKIT_ENTRY_SIZE >= payload_size) {
std::lock_guard block{osp_mutex};
if(osp_head) {
assert(sdp_count > 0);
sdp_count--;
auto entry = osp_head;
if(osp_head->next) {
assert(osp_tail != &osp_head->next);
osp_head = osp_head->next;
} else {
assert(osp_tail == &osp_head->next);
osp_head = nullptr;
osp_tail = &osp_head;
}
entry->next = nullptr;
auto result = osp_from_bosp(entry);
reset_osp(result, payload_size);
result->ref_count++;
return result;
} else if(sdp_count < BUKKIT_MAX_ENTRIES) {
auto entry = construct_bosp(BUKKIT_ENTRY_SIZE);
entry->extra_allocated = false;
auto result = osp_from_bosp(entry);
reset_osp(result, payload_size);
result->ref_count++;
return result;
}
}
auto entry = construct_bosp(payload_size);
entry->extra_allocated = true;
auto result = osp_from_bosp(entry);
reset_osp(result, payload_size);
result->ref_count++;
return result;
}
#else
void protocol::OutgoingServerPacket::free_object() {
deconstruct_osp(this);
::free(this);
}
protocol::OutgoingServerPacket* protocol::allocate_outgoing_server_packet(size_t payload_size) {
auto base_size = sizeof(protocol::OutgoingServerPacket) - 1;
/* Allocate at least one payload byte since we're our payload array of length 1 */
auto full_size = base_size + std::max(payload_size, (size_t) 1);
auto result = (protocol::OutgoingServerPacket*) malloc(full_size);
construct_osp(result);
reset_osp(result, payload_size);
result->ref_count++;
return result;
}
#endif
void protocol::OutgoingClientPacket::free_object() {
deconstruct_ocp(this);
::free(this);
}
protocol::OutgoingClientPacket* protocol::allocate_outgoing_client_packet(size_t payload_size) {
auto base_size = sizeof(protocol::OutgoingClientPacket) - 1;
/* Allocate at least one payload byte since we're our payload array of length 1 */
auto full_size = base_size + std::max(payload_size, (size_t) 1);
auto result = (protocol::OutgoingClientPacket*) malloc(full_size);
construct_ocp(result);
reset_ocp(result, payload_size);
result->ref_count++;
return result;
}
}

View File

@ -4,405 +4,353 @@
#include <string>
#include <map>
#include <utility>
#include <ThreadPool/Future.h>
#include <array>
#include <pipes/buffer.h>
#include "../query/Command.h"
namespace ts {
namespace protocol {
enum PacketType : uint8_t {
VOICE = 0x00,
VOICE_WHISPER = 0x01,
COMMAND = 0x02,
COMMAND_LOW = 0x03,
PING = 0x04,
PONG = 0x05,
ACK = 0x06,
ACK_LOW = 0x07,
INIT1 = 0x08,
namespace ts::protocol {
enum PacketType : uint8_t {
VOICE = 0x00,
VOICE_WHISPER = 0x01,
COMMAND = 0x02,
COMMAND_LOW = 0x03,
PING = 0x04,
PONG = 0x05,
ACK = 0x06,
ACK_LOW = 0x07,
INIT1 = 0x08,
};
PACKET_MAX = INIT1,
UNDEFINED = 0xFF
};
class PacketIdManager {
public:
PacketIdManager() = default;
~PacketIdManager() = default;
PacketIdManager(const PacketIdManager& ref) = delete;
PacketIdManager(PacketIdManager&& ref) = delete;
struct PacketTypeProperties {
std::string name;
PacketType type;
int max_length;
bool requireAcknowledge;
};
class PacketTypeInfo {
public:
static PacketTypeInfo Voice;
static PacketTypeInfo VoiceWhisper;
static PacketTypeInfo Command;
static PacketTypeInfo CommandLow;
static PacketTypeInfo Ping;
static PacketTypeInfo Pong;
static PacketTypeInfo Ack;
static PacketTypeInfo AckLow;
static PacketTypeInfo Init1;
static PacketTypeInfo Undefined;
static PacketTypeInfo fromid(int id);
std::string name() const { return data->name; }
PacketType type() const { return data->type; }
bool requireAcknowledge(){ return data->requireAcknowledge; }
bool operator==(const PacketTypeInfo& other) const {
return other.data->type == this->data->type;
}
bool operator!=(const PacketTypeInfo& other){
return other.data->type != this->data->type;
}
int max_length() const { return data->max_length; }
inline bool fragmentable() { return *this == PacketTypeInfo::Command || *this == PacketTypeInfo::CommandLow; }
inline bool compressable() { return *this == PacketTypeInfo::Command || *this == PacketTypeInfo::CommandLow; }
PacketTypeInfo(const PacketTypeInfo&);
PacketTypeInfo(PacketTypeInfo&& remote) : data(remote.data) {}
~PacketTypeInfo();
private:
static std::map<int, PacketTypeInfo> types;
PacketTypeInfo(const std::string&, PacketType, bool, int) noexcept;
PacketTypeProperties* data;
bool owns_data = false;
};
struct PacketIdManagerData {
PacketIdManagerData(){
memset(this->packetCounter, 0, sizeof(uint32_t) * 16);
[[nodiscard]] uint16_t nextPacketId(const PacketType &type) {
return (uint16_t) (this->packet_counter[(uint8_t) type & 0xFU]++);
}
uint32_t packetCounter[16]{};
};
class PacketIdManager {
public:
PacketIdManager() : data(new PacketIdManagerData){}
~PacketIdManager() = default;
PacketIdManager(const PacketIdManager& ref) : data(ref.data) {}
PacketIdManager(PacketIdManager&& ref) : data(std::move(ref.data)) {}
[[nodiscard]] uint16_t currentPacketId(const PacketType &type) {
return (uint16_t) (this->packet_counter[(uint8_t) type & 0xFU]);
}
uint16_t nextPacketId(const PacketTypeInfo &type){
return static_cast<uint16_t>(data->packetCounter[type.type()]++ & 0xFFFF);
}
[[nodiscard]] uint16_t generationId(const PacketType &type) {
return (uint16_t) (this->packet_counter[(uint8_t) type & 0xFU] >> 16U);
}
uint16_t currentPacketId(const PacketTypeInfo &type){
return static_cast<uint16_t>(data->packetCounter[type.type()] & 0xFFFF);
}
[[nodiscard]] uint32_t generate_full_id(const PacketType& type) {
return this->packet_counter[type]++;
}
uint16_t generationId(const PacketTypeInfo &type){
return static_cast<uint16_t>((data->packetCounter[type.type()] >> 16) & 0xFFFF);
}
void reset() {
memset(&this->packet_counter[0], 0, sizeof(uint32_t) * 16);
}
void reset() {
memset(&data->packetCounter[0], 0, sizeof(uint32_t) * 16);
}
private:
std::shared_ptr<PacketIdManagerData> data;
};
private:
std::array<uint32_t, 16> packet_counter{};
};
namespace PacketFlag {
enum PacketFlag : uint8_t {
None = 0x00,
Fragmented = 0x10, //If packet type voice then its toggle the CELT Mono
NewProtocol = 0x20,
Compressed = 0x40, //If packet type voice than its the header
Unencrypted = 0x80
};
typedef uint8_t PacketFlags;
enum struct PacketFlag {
None = 0x00,
Fragmented = 0x10, //If packet type voice then its toggle the CELT Mono
NewProtocol = 0x20,
Compressed = 0x40, //If packet type voice than its the header
Unencrypted = 0x80
};
typedef uint8_t PacketFlags;
std::string to_string(PacketFlag flag);
constexpr const char* packet_flag_to_string(const PacketFlag& flag) {
switch(flag){
case PacketFlag::Fragmented:
return "Fragmented";
case PacketFlag::NewProtocol:
return "NewProtocol";
case PacketFlag::Compressed:
return "Compressed";
case PacketFlag::Unencrypted:
return "Unencrypted";
case PacketFlag::None:
default:
return "None";
}
}
#define MAC_SIZE 8
#define SERVER_HEADER_SIZE 3
#define CLIENT_HEADER_SIZE 5
#define MAC_SIZE 8
#define SERVER_HEADER_SIZE 3
#define CLIENT_HEADER_SIZE 5
class BasicPacket {
public:
explicit BasicPacket(size_t header_length, size_t data_length);
virtual ~BasicPacket();
class PacketParser {
public:
PacketParser(const PacketParser&) = delete;
explicit PacketParser(pipes::buffer_view buffer) : _buffer{std::move(buffer)} {}
BasicPacket(const BasicPacket&) = delete;
BasicPacket(BasicPacket&&) = delete;
[[nodiscard]] inline const void* data_ptr() const { return this->_buffer.data_ptr(); }
[[nodiscard]] inline void* mutable_data_ptr() { return (void*) this->_buffer.data_ptr(); }
virtual uint16_t packetId() const = 0;
virtual uint16_t generationId() const = 0;
virtual PacketTypeInfo type() const = 0;
[[nodiscard]] inline pipes::buffer_view buffer() const { return this->_buffer; }
[[nodiscard]] inline pipes::buffer_view mac() const { return this->_buffer.view(0, 8); }
[[nodiscard]] virtual pipes::buffer_view header() const = 0;
[[nodiscard]] virtual pipes::buffer_view payload() const = 0;
[[nodiscard]] virtual void* payload_ptr_mut() = 0;
[[nodiscard]] virtual size_t payload_length() const = 0;
/* packet flag info */
inline bool has_flag(PacketFlag::PacketFlag flag) const { return this->_flags_type_byte() & flag; }
inline uint8_t flag_mask() const { return this->_flags_type_byte(); };
[[nodiscard]] std::string flags() const;
[[nodiscard]] inline uint32_t full_packet_id() const { return this->packet_id() | (uint32_t) ((uint32_t) this->estimated_generation() << 16U); }
[[nodiscard]] virtual uint16_t packet_id() const = 0;
[[nodiscard]] virtual uint8_t type() const = 0;
[[nodiscard]] virtual uint8_t flags() const = 0;
/* manipulate flags */
inline void set_flags(PacketFlag::PacketFlags flags) {
uint8_t& byte = this->_flags_type_byte();
byte &= 0xF; /* clear all flags */
byte |= (flags & 0xF0);
}
inline void enable_flag(PacketFlag::PacketFlag flag){ this->toggle_flag(flag, true); }
inline void toggle_flag(PacketFlag::PacketFlag flag, bool state) {
if(state)
this->_flags_type_byte() |= flag;
else
this->_flags_type_byte() &= (uint8_t) ~flag;
[[nodiscard]] inline bool has_flag(const PacketFlag& flag) const { return this->flags() & (uint8_t) flag; }
[[nodiscard]] inline bool is_encrypted() const {
return !this->decrypted && !this->has_flag(PacketFlag::Unencrypted);
}
[[nodiscard]] inline bool is_compressed() const {
return !this->uncompressed && this->has_flag(PacketFlag::Compressed);
}
[[nodiscard]] inline bool is_fragmented() const {
return !this->defragmented && this->has_flag(PacketFlag::Fragmented);
}
[[nodiscard]] uint16_t estimated_generation() const { return this->generation; }
void set_estimated_generation(uint16_t gen) { this->generation = gen; }
inline void set_decrypted() { this->decrypted = true; }
inline void set_uncompressed() { this->uncompressed = true; }
inline void set_defragmented() { this->defragmented = true; }
protected:
uint16_t generation{};
bool decrypted{false}, uncompressed{false}, defragmented{false};
pipes::buffer_view _buffer{};
};
class ClientPacketParser : public PacketParser {
public:
constexpr static auto kHeaderOffset = 8;
constexpr static auto kHeaderLength = CLIENT_HEADER_SIZE;
constexpr static auto kPayloadOffset = kHeaderOffset + CLIENT_HEADER_SIZE;
explicit ClientPacketParser(pipes::buffer_view buffer) : PacketParser{std::move(buffer)} {}
ClientPacketParser(const ClientPacketParser&) = delete;
[[nodiscard]] inline bool valid() const {
if(this->_buffer.length() < kPayloadOffset) return false;
return this->type() <= 8;
}
[[nodiscard]] inline pipes::buffer_view header() const override { return this->_buffer.view(kHeaderOffset, kHeaderLength); }
[[nodiscard]] inline pipes::buffer_view payload() const override { return this->_buffer.view(kPayloadOffset); }
[[nodiscard]] inline void* payload_ptr_mut() override { return (char*) this->mutable_data_ptr() + kPayloadOffset; };
[[nodiscard]] inline size_t payload_length() const override { return this->_buffer.length() - kPayloadOffset; }
[[nodiscard]] uint16_t client_id() const;
[[nodiscard]] uint16_t packet_id() const override;
[[nodiscard]] uint8_t type() const override;
[[nodiscard]] uint8_t flags() const override;
};
class ServerPacketParser : public PacketParser {
public:
constexpr static auto kHeaderOffset = 8;
constexpr static auto kHeaderLength = SERVER_HEADER_SIZE;
constexpr static auto kPayloadOffset = kHeaderOffset + SERVER_HEADER_SIZE;
explicit ServerPacketParser(pipes::buffer_view buffer) : PacketParser{std::move(buffer)} {}
ServerPacketParser(const ServerPacketParser&) = delete;
[[nodiscard]] inline bool valid() const {
if(this->_buffer.length() < kPayloadOffset) return false;
return this->type() <= 8;
}
[[nodiscard]] inline pipes::buffer_view header() const override { return this->_buffer.view(kHeaderOffset, kHeaderLength); }
[[nodiscard]] inline pipes::buffer_view payload() const override { return this->_buffer.view(kPayloadOffset); }
[[nodiscard]] inline void* payload_ptr_mut() override { return (char*) this->mutable_data_ptr() + kPayloadOffset; };
[[nodiscard]] inline size_t payload_length() const override { return this->_buffer.length() - kPayloadOffset; }
[[nodiscard]] uint16_t packet_id() const override;
[[nodiscard]] uint8_t type() const override;
[[nodiscard]] uint8_t flags() const override;
};
struct OutgoingPacket {
public:
OutgoingPacket() = default;
virtual ~OutgoingPacket() = default;
/* general info */
std::atomic<uint16_t> ref_count{0};
size_t payload_size;
uint16_t generation;
inline auto ref() {
auto count = ++ref_count;
assert(count > 1);
return count;
}
inline void unref() {
if(--this->ref_count == 0) {
this->free_object();
}
}
virtual void applyPacketId(PacketIdManager &);
virtual void applyPacketId(uint16_t, uint16_t);
/* some helper methods */
inline void set_packet_id(uint16_t id) {
auto packet_id_bytes = this->packet_id_bytes();
packet_id_bytes[0] = id >> 8U;
packet_id_bytes[1] = id & 0xFFU;
}
void setListener(std::unique_ptr<threads::Future<bool>> listener){
if(!this->type().requireAcknowledge())
throw std::logic_error("Packet type does not support a acknowledge listener!");
this->listener = std::move(listener);
}
inline std::unique_ptr<threads::Future<bool>>& getListener() { return this->listener; }
[[nodiscard]] inline uint16_t packet_id() const {
auto packet_id_bytes = this->packet_id_bytes();
return (uint16_t) (packet_id_bytes[0] << 8U) | packet_id_bytes[1];
}
inline size_t length() const { return this->_buffer.length(); }
inline const pipes::buffer_view mac() const { return this->_buffer.view(0, MAC_SIZE); }
inline pipes::buffer mac() { return this->_buffer.range(0, MAC_SIZE); }
inline size_t mac_length() const { return MAC_SIZE; }
[[nodiscard]] inline auto packet_type() const {
auto type_and_flags = this->type_and_flags();
return (PacketType) (type_and_flags & 0xFU);
}
inline const pipes::buffer_view header() const { return this->_buffer.view(MAC_SIZE, this->_header_length); }
inline pipes::buffer header() { return this->_buffer.range(MAC_SIZE, this->_header_length); }
inline size_t header_length() const { return this->_header_length; }
/**
* @returns a pointer to the beginning of the packet including the packet header
*/
[[nodiscard]] virtual const void* packet_data() const = 0;
inline size_t data_length() const { return this->_buffer.length() - (MAC_SIZE + this->_header_length); }
inline const pipes::buffer_view data() const { return this->_buffer.view(MAC_SIZE + this->_header_length); }
inline pipes::buffer data() { return this->_buffer.range(MAC_SIZE + this->_header_length); }
/**
* @returns the full packet length including the packet header
*/
[[nodiscard]] virtual size_t packet_length() const = 0;
[[nodiscard]] virtual uint8_t type_and_flags() const = 0;
void append_data(const std::vector<pipes::buffer> &data);
[[nodiscard]] virtual OutgoingPacket* next_in_queue() const = 0;
virtual void set_next_in_queue(OutgoingPacket*) = 0;
protected:
[[nodiscard]] virtual const uint8_t* packet_id_bytes() const = 0;
[[nodiscard]] virtual uint8_t* packet_id_bytes() = 0;
virtual void free_object() = 0;
};
inline void data(const pipes::buffer_view &data){
this->_buffer.resize(MAC_SIZE + this->_header_length + data.length());
memcpy((char*) this->_buffer.data_ptr() + MAC_SIZE + this->_header_length, data.data_ptr(), data.length());
}
struct OutgoingClientPacket : public OutgoingPacket {
public:
OutgoingClientPacket() = default;
~OutgoingClientPacket() override = default;
inline void mac(const pipes::buffer_view &_new){
assert(_new.length() >= MAC_SIZE);
memcpy(this->_buffer.data_ptr(), _new.data_ptr(), MAC_SIZE);
}
OutgoingClientPacket* next; /* used within the write/process queue */
[[nodiscard]] inline bool isEncrypted() const { return this->memory_state.encrypted; }
inline void setEncrypted(bool flag){ this->memory_state.encrypted = flag; }
/* actual packet data */
uint8_t mac[8];
uint8_t packet_id_bytes_[2];
uint8_t client_id_bytes[2];
uint8_t type_and_flags_;
uint8_t payload[1]; /* variable size */
[[nodiscard]] inline bool isCompressed() const { return this->memory_state.compressed; }
inline void setCompressed(bool flag){ this->memory_state.compressed = flag; }
[[nodiscard]] inline const void* packet_data() const override {
return this->mac;
}
[[nodiscard]] inline bool isFragmentEntry() const { return this->memory_state.fragment_entry; }
inline void setFragmentedEntry(bool flag){ this->memory_state.fragment_entry = flag; }
[[nodiscard]] inline size_t packet_length() const override {
return this->payload_size + (8 + 2 + 2 + 1);
}
Command asCommand();
[[nodiscard]] inline uint8_t type_and_flags() const override {
return this->type_and_flags_;
}
//Has the size of a byte
union {
#ifdef WIN32
__pragma(pack(push, 1))
#endif
struct {
bool encrypted: 1;
bool compressed: 1;
bool fragment_entry: 1;
[[nodiscard]] inline OutgoingPacket* next_in_queue() const override {
return this->next;
}
bool id_branded: 1;
}
#ifdef WIN32
__pragma(pack(pop));
#else
__attribute__((packed));
#endif
inline void set_next_in_queue(OutgoingPacket* packet) override {
this->next = dynamic_cast<OutgoingClientPacket*>(packet);
assert(!packet || this->next);
}
protected:
[[nodiscard]] inline const uint8_t* packet_id_bytes() const override {
return this->packet_id_bytes_;
}
uint8_t flags = 0;
} memory_state;
[[nodiscard]] inline uint8_t* packet_id_bytes() override {
return this->packet_id_bytes_;
}
pipes::buffer buffer() { return this->_buffer; }
void buffer(pipes::buffer buffer) {
assert(buffer.length() >= this->_header_length + MAC_SIZE);
this->_buffer = std::move(buffer);
}
protected:
BasicPacket() = default;
void free_object() override;
};
virtual const uint8_t& _flags_type_byte() const = 0;
virtual uint8_t& _flags_type_byte() = 0;
struct OutgoingServerPacket : public OutgoingPacket {
public:
virtual ~OutgoingServerPacket() = default;
virtual void setPacketId(uint16_t, uint16_t) = 0;
uint8_t _header_length;
pipes::buffer _buffer;
OutgoingServerPacket* next; /* used within the write/process queue */
uint16_t genId = 0;
std::unique_ptr<threads::Future<bool>> listener;
};
/* actual packet data */
uint8_t mac[8];
uint8_t packet_id_bytes_[2];
uint8_t type_and_flags_;
uint8_t payload[1]; /* variable size */
[[nodiscard]] inline uint8_t type_and_flags() const override {
return this->type_and_flags_;
}
/**
* Packet from the client
*/
class ClientPacket : public BasicPacket {
friend std::unique_ptr<ClientPacket> std::make_unique<ClientPacket>();
public:
static constexpr size_t META_MAC_SIZE = 8;
static constexpr size_t META_HEADER_SIZE = CLIENT_HEADER_SIZE;
static constexpr size_t META_SIZE = META_MAC_SIZE + META_HEADER_SIZE;
[[nodiscard]] inline const void* packet_data() const override {
return this->mac;
}
[[nodiscard]] static std::unique_ptr<ClientPacket> from_buffer(const pipes::buffer_view& buffer);
[[nodiscard]] inline size_t packet_length() const override {
return this->payload_size + (8 + 2 + 1);
}
ClientPacket(const PacketTypeInfo& type, const pipes::buffer_view& data);
ClientPacket(const PacketTypeInfo& type, uint8_t flag_mask, const pipes::buffer_view& data);
~ClientPacket() override;
ClientPacket(const ClientPacket&) = delete;
ClientPacket(ClientPacket&&) = delete;
[[nodiscard]] inline OutgoingPacket* next_in_queue() const override {
return this->next;
}
uint16_t clientId() const;
void clientId(uint16_t);
inline void set_next_in_queue(OutgoingPacket* packet) override {
this->next = dynamic_cast<OutgoingServerPacket*>(packet);
assert(!packet || this->next);
}
protected:
[[nodiscard]] inline const uint8_t* packet_id_bytes() const override {
return this->packet_id_bytes_;
}
uint16_t packetId() const override;
[[nodiscard]] inline uint8_t* packet_id_bytes() override {
return this->packet_id_bytes_;
}
uint16_t generationId() const override;
void generationId(uint16_t generation) { this->genId = generation; }
void free_object() override;
};
PacketTypeInfo type() const override;
void type(const PacketTypeInfo&);
/* This will allocate a new outgoing packet. To delete just unref the packet! */
OutgoingServerPacket* allocate_outgoing_server_packet(size_t /* payload size */);
OutgoingClientPacket* allocate_outgoing_client_packet(size_t /* payload size */);
private:
ClientPacket() = default;
inline PacketFlags& operator|=(PacketFlags& flags, const PacketFlag& flag) {
flags |= (uint8_t) flag;
return flags;
}
const uint8_t &_flags_type_byte() const override {
return this->header().data_ptr<uint8_t>()[4];
}
inline PacketFlags operator|(PacketFlags flags, const PacketFlag& flag) {
return flags |= flag;
}
uint8_t &_flags_type_byte() override {
return this->header().data_ptr<uint8_t>()[4];
}
inline PacketFlags& operator&=(PacketFlags& flags, const PacketFlag& flag) {
flags &= (uint8_t) flag;
return flags;
}
void setPacketId(uint16_t, uint16_t) override;
};
inline PacketFlags operator&(PacketFlags flags, const PacketFlag& flag) {
return flags &= flag;
}
class PacketParser {
public:
PacketParser(const PacketParser&) = delete;
explicit PacketParser(pipes::buffer_view buffer) : _buffer{std::move(buffer)} {}
[[nodiscard]] inline const void* data_ptr() const { return this->_buffer.data_ptr(); }
[[nodiscard]] inline void* mutable_data_ptr() { return (void*) this->_buffer.data_ptr(); }
[[nodiscard]] inline pipes::buffer_view buffer() const { return this->_buffer; }
[[nodiscard]] inline pipes::buffer_view mac() const { return this->_buffer.view(0, 8); }
[[nodiscard]] virtual pipes::buffer_view payload() const = 0;
[[nodiscard]] virtual size_t payload_length() const = 0;
[[nodiscard]] inline uint32_t full_packet_id() const { return this->packet_id() | (this->estimated_generation() << 16U); }
[[nodiscard]] virtual uint16_t packet_id() const = 0;
[[nodiscard]] virtual uint8_t type() const = 0;
[[nodiscard]] virtual uint8_t flags() const = 0;
[[nodiscard]] bool is_encrypted() const;
[[nodiscard]] bool is_compressed() const;
[[nodiscard]] bool is_fragmented() const;
[[nodiscard]] uint16_t estimated_generation() const { return this->generation; }
void set_estimated_generation(uint16_t gen) { this->generation = gen; }
inline void set_decrypted() { this->decrypted = true; }
inline void set_uncompressed() { this->uncompressed = true; }
inline void set_defragmented() { this->defragmented = true; }
protected:
uint16_t generation{};
bool decrypted{false}, uncompressed{false}, defragmented{false};
pipes::buffer_view _buffer{};
};
class ClientPacketParser : public PacketParser {
public:
constexpr static auto kHeaderOffset = 8;
constexpr static auto kHeaderLength = CLIENT_HEADER_SIZE;
constexpr static auto kPayloadOffset = kHeaderOffset + CLIENT_HEADER_SIZE;
explicit ClientPacketParser(pipes::buffer_view buffer) : PacketParser{std::move(buffer)} {}
ClientPacketParser(const ClientPacketParser&) = delete;
[[nodiscard]] inline bool valid() const {
if(this->_buffer.length() < kPayloadOffset) return false;
return this->type() <= 8;
}
[[nodiscard]] inline pipes::buffer_view payload() const override { return this->_buffer.view(kPayloadOffset); }
[[nodiscard]] inline size_t payload_length() const override { return this->_buffer.length() - kPayloadOffset; }
[[nodiscard]] uint16_t client_id() const;
[[nodiscard]] uint16_t packet_id() const override;
[[nodiscard]] uint8_t type() const override;
[[nodiscard]] uint8_t flags() const override;
};
/**
* Packet from the server
*/
class ServerPacket : public BasicPacket {
friend std::unique_ptr<ServerPacket> std::make_unique<ServerPacket>();
public:
static constexpr size_t META_MAC_SIZE = 8;
static constexpr size_t META_HEADER_SIZE = SERVER_HEADER_SIZE;
static constexpr size_t META_SIZE = META_MAC_SIZE + META_HEADER_SIZE;
[[nodiscard]] static std::unique_ptr<ServerPacket> from_buffer(const pipes::buffer_view& buffer);
ServerPacket(uint8_t flagMask, const pipes::buffer_view& data);
ServerPacket(const PacketTypeInfo& type, const pipes::buffer_view& data);
ServerPacket(PacketTypeInfo type, size_t /* data length */);
~ServerPacket() override;
ServerPacket(const ServerPacket&) = delete;
ServerPacket(ServerPacket&&) = delete;
[[nodiscard]] uint16_t packetId() const override;
[[nodiscard]] uint16_t generationId() const override;
void generationId(uint16_t generation) { this->genId = generation; }
[[nodiscard]] PacketTypeInfo type() const override;
private:
ServerPacket() = default;
[[nodiscard]] const uint8_t &_flags_type_byte() const override {
return this->header().data_ptr<uint8_t>()[2];
}
uint8_t &_flags_type_byte() override {
return this->header().data_ptr<uint8_t>()[2];
}
void setPacketId(uint16_t, uint16_t) override;
};
class ServerPacketParser : public PacketParser {
public:
constexpr static auto kHeaderOffset = 8;
constexpr static auto kHeaderLength = SERVER_HEADER_SIZE;
constexpr static auto kPayloadOffset = kHeaderOffset + SERVER_HEADER_SIZE;
explicit ServerPacketParser(pipes::buffer_view buffer) : PacketParser{std::move(buffer)} {}
ServerPacketParser(const ServerPacketParser&) = delete;
[[nodiscard]] inline bool valid() const {
if(this->_buffer.length() < kPayloadOffset) return false;
return this->type() <= 8;
}
[[nodiscard]] inline pipes::buffer_view payload() const override { return this->_buffer.view(kPayloadOffset); }
[[nodiscard]] inline size_t payload_length() const override { return this->_buffer.length() - kPayloadOffset; }
[[nodiscard]] uint16_t packet_id() const override;
[[nodiscard]] uint8_t type() const override;
[[nodiscard]] uint8_t flags() const override;
};
inline PacketFlags operator|(const PacketFlag& flag_a, const PacketFlag& flag_b) {
return (uint8_t) flag_a | (uint8_t) flag_b;
}
}

View File

@ -0,0 +1,320 @@
//
// Created by WolverinDEV on 10/03/2020.
//
#include "PacketDecoder.h"
#include "../misc/memtracker.h"
#include "./AcknowledgeManager.h"
#include "./CompressionHandler.h"
#include "./CryptHandler.h"
#ifdef FEATURE_LOGGING
#include <log/LogUtils.h>
#endif
using namespace ts;
using namespace ts::protocol;
using namespace ts::connection;
PacketDecoder::PacketDecoder(ts::connection::CryptHandler *crypt_handler, bool is_server)
: is_server{is_server}, crypt_handler_{crypt_handler} {
memtrack::allocated<PacketDecoder>(this);
}
PacketDecoder::~PacketDecoder() {
memtrack::freed<PacketDecoder>(this);
this->reset();
}
void PacketDecoder::reset() {
{
std::lock_guard buffer_lock(this->packet_buffer_lock);
for(auto& buffer : this->_command_fragment_buffers) {
buffer.reset();
}
}
{
std::lock_guard estimator_lock{this->incoming_generation_estimator_lock};
for(auto& estimator : this->incoming_generation_estimators) {
estimator.reset();
}
}
}
PacketProcessResult PacketDecoder::process_incoming_data(PacketParser &packet_parser, std::string& error) {
#ifdef FUZZING_TESTING_INCOMMING
if(rand() % 100 < 20) {
return PacketProcessResult::FUZZ_DROPPED;
}
#ifdef FIZZING_TESTING_DISABLE_HANDSHAKE
if (this->client->state == ConnectionState::CONNECTED) {
#endif
if ((rand() % FUZZING_TESTING_DROP_MAX) < FUZZING_TESTING_DROP) {
debugMessage(this->client->getServerId(), "{}[FUZZING] Dropping incoming packet of length {}", CLIENT_STR_LOG_PREFIX_(this->client), buffer.length());
return;
}
#ifdef FIZZING_TESTING_DISABLE_HANDSHAKE
}
#endif
#endif
assert(packet_parser.type() >= 0 && packet_parser.type() < this->incoming_generation_estimators.size());
auto& generation_estimator = this->incoming_generation_estimators[packet_parser.type()];
{
std::lock_guard glock{this->incoming_generation_estimator_lock};
packet_parser.set_estimated_generation(generation_estimator.visit_packet(packet_parser.packet_id()));
}
auto result = this->decrypt_incoming_packet(error, packet_parser);
if(result != PacketProcessResult::SUCCESS) {
return result;
}
#ifdef LOG_INCOMPING_PACKET_FRAGMENTS
debugMessage(lstream << CLIENT_LOG_PREFIX << "Recived packet. PacketId: " << packet->packetId() << " PacketType: " << packet->type().name() << " Flags: " << packet->flags() << " - " << packet->data() << endl);
#endif
auto is_command = packet_parser.type() == protocol::COMMAND || packet_parser.type() == protocol::COMMAND_LOW;
if(is_command) {
auto& fragment_buffer = this->_command_fragment_buffers[command_fragment_buffer_index(packet_parser.type())];
CommandFragment fragment_entry{
packet_parser.packet_id(),
packet_parser.estimated_generation(),
packet_parser.flags(),
(uint32_t) packet_parser.payload_length(),
packet_parser.payload().own_buffer()
};
std::unique_lock queue_lock(fragment_buffer.buffer_lock);
auto insert_result = fragment_buffer.insert_index2(packet_parser.full_packet_id(), std::move(fragment_entry));
if(insert_result != 0) {
queue_lock.unlock();
error = "pid: " + std::to_string(packet_parser.packet_id()) + ", ";
error += "bidx: " + std::to_string(fragment_buffer.current_index()) + ", ";
error += "bcap: " + std::to_string(fragment_buffer.capacity());
if(insert_result == -2) {
return PacketProcessResult::DUPLICATED_PACKET;
} else if(insert_result == -1) {
this->callback_send_acknowledge(this->callback_argument, packet_parser.packet_id(), packet_parser.type() == protocol::COMMAND_LOW);
return PacketProcessResult::BUFFER_UNDERFLOW;
} else if(insert_result == 1) {
return PacketProcessResult::BUFFER_OVERFLOW;
}
assert(false);
return PacketProcessResult::UNKNOWN_ERROR;
}
this->callback_send_acknowledge(this->callback_argument, packet_parser.packet_id(), packet_parser.type() == protocol::COMMAND_LOW);
ReassembledCommand* command{nullptr};
CommandReassembleResult assemble_result;
do {
if(!queue_lock.owns_lock()) {
queue_lock.lock();
}
assemble_result = this->try_reassemble_ordered_packet(fragment_buffer, queue_lock, command);
if(assemble_result == CommandReassembleResult::SUCCESS || assemble_result == CommandReassembleResult::MORE_COMMANDS_PENDING) {
this->callback_decoded_command(this->callback_argument, command);
}
if(command) {
/* ownership hasn't transferred */
ReassembledCommand::free(command);
command = nullptr;
}
switch (assemble_result) {
case CommandReassembleResult::NO_COMMANDS_PENDING:
case CommandReassembleResult::SUCCESS:
case CommandReassembleResult::MORE_COMMANDS_PENDING:
break;
case CommandReassembleResult::SEQUENCE_LENGTH_TOO_LONG:
return PacketProcessResult::COMMAND_BUFFER_OVERFLOW;
case CommandReassembleResult::COMMAND_TOO_LARGE:
return PacketProcessResult::COMMAND_TOO_LARGE;
case CommandReassembleResult::COMMAND_DECOMPRESS_FAILED:
return PacketProcessResult::COMMAND_DECOMPRESS_FAILED;
default:
assert(false);
break;
}
} while(assemble_result == CommandReassembleResult::MORE_COMMANDS_PENDING);
} else {
this->callback_decoded_packet(this->callback_argument, packet_parser);
}
return PacketProcessResult::SUCCESS;
}
PacketProcessResult PacketDecoder::decrypt_incoming_packet(std::string& error, PacketParser &packet_parser) {
/* decrypt the packet if needed */
if(packet_parser.is_encrypted()) {
CryptHandler::key_t crypt_key{};
CryptHandler::nonce_t crypt_nonce{};
bool use_default_key{!this->crypt_handler_->encryption_initialized()}, decrypt_result;
decrypt_packet:
if(use_default_key) {
crypt_key = CryptHandler::kDefaultKey;
crypt_nonce = CryptHandler::kDefaultNonce;
} else {
if(!this->crypt_handler_->generate_key_nonce(this->is_server, packet_parser.type(), packet_parser.packet_id(), packet_parser.estimated_generation(), crypt_key, crypt_nonce)) {
return PacketProcessResult::DECRYPT_KEY_GEN_FAILED;
}
}
auto mac = packet_parser.mac();
auto header = packet_parser.header();
auto payload = packet_parser.payload_ptr_mut();
decrypt_result = this->crypt_handler_->decrypt(
header.data_ptr(), header.length(),
payload, packet_parser.payload_length(),
mac.data_ptr(),
crypt_key, crypt_nonce,
error
);
if(!decrypt_result) {
if(packet_parser.packet_id() < 10 && packet_parser.estimated_generation() == 0) {
if(use_default_key) {
return PacketProcessResult::DECRYPT_FAILED;
} else {
use_default_key = true;
goto decrypt_packet;
}
} else {
return PacketProcessResult::DECRYPT_FAILED;
}
}
packet_parser.set_decrypted();
}
return PacketProcessResult::SUCCESS;
}
bool PacketDecoder::verify_encryption_client_packet(const protocol::ClientPacketParser& packet_parser) {
if(!packet_parser.is_encrypted()) {
return false;
}
assert(packet_parser.type() >= 0 && packet_parser.type() < this->incoming_generation_estimators.size());
return this->crypt_handler_->verify_encryption(packet_parser.buffer(), packet_parser.packet_id(), this->incoming_generation_estimators[packet_parser.type()].generation());
}
void PacketDecoder::register_initiv_packet() {
auto& fragment_buffer = this->_command_fragment_buffers[command_fragment_buffer_index(protocol::COMMAND)];
std::unique_lock buffer_lock(fragment_buffer.buffer_lock);
fragment_buffer.set_full_index_to(1); /* the first packet (0) is already the clientinitiv packet */
}
CommandReassembleResult PacketDecoder::try_reassemble_ordered_packet(
command_fragment_buffer_t &buffer,
std::unique_lock<std::mutex> &buffer_lock,
ReassembledCommand *&assembled_command) {
assert(buffer_lock.owns_lock());
if(!buffer.front_set()) {
return CommandReassembleResult::NO_COMMANDS_PENDING;
}
uint8_t packet_flags;
std::unique_ptr<ReassembledCommand, void(*)(ReassembledCommand*)> rcommand{nullptr, ReassembledCommand::free};
/* lets find out if we've to reassemble the packet */
auto& first_buffer = buffer.slot_value(0);
if(first_buffer.packet_flags & PacketFlag::Fragmented) {
uint16_t sequence_length{1};
size_t total_payload_length{first_buffer.payload_length};
do {
if(sequence_length >= buffer.capacity()) {
return CommandReassembleResult::SEQUENCE_LENGTH_TOO_LONG;
}
if(!buffer.slot_set(sequence_length)) {
return CommandReassembleResult::NO_COMMANDS_PENDING; /* we need more packets */
}
auto& packet = buffer.slot_value(sequence_length++);
total_payload_length += packet.payload_length;
if(packet.packet_flags & PacketFlag::Fragmented) {
/* yep we find the end */
break;
}
} while(true);
/* ok we have all fragments lets reassemble */
/*
* Packet sequence could never be so long. If it is so then the data_length() returned an invalid value.
* We're checking it here because we dont want to make a huge allocation
*/
assert(total_payload_length < 512 * 1024 * 1024);
rcommand.reset(ReassembledCommand::allocate(total_payload_length));
char* packet_buffer_ptr = rcommand->command();
size_t packet_count{0};
packet_flags = buffer.slot_value(0).packet_flags;
while(packet_count < sequence_length) {
auto fragment = buffer.pop_front();
memcpy(packet_buffer_ptr, fragment.payload.data_ptr(), fragment.payload_length);
packet_buffer_ptr += fragment.payload_length;
packet_count++;
}
/* We don't have log functions for our TeaClient */
#if !defined(_NDEBUG) && defined(FEATURE_LOGGING)
if((packet_buffer_ptr - 1) != &rcommand->command()[rcommand->length() - 1]) {
logCritical(0,
"Buffer over/underflow: packet_buffer_ptr != &packet_buffer[packet_buffer.length() - 1]; packet_buffer_ptr := {}; packet_buffer.end() := {}",
(void*) packet_buffer_ptr,
(void*) &rcommand->command()[rcommand->length() - 1]
);
}
#endif
} else {
auto packet = buffer.pop_front();
packet_flags = packet.packet_flags;
rcommand.reset(ReassembledCommand::allocate(packet.payload_length));
memcpy(rcommand->command(), packet.payload.data_ptr(), packet.payload_length);
}
auto more_commands_pending = buffer.front_set(); /* set the more flag if we have more to process */
buffer_lock.unlock();
if(packet_flags & PacketFlag::Compressed) {
std::string error{};
auto compressed_command = std::move(rcommand);
auto decompressed_size = compression::qlz_decompressed_size(compressed_command->command(), compressed_command->length());
if(decompressed_size > 64 * 1024 * 1024) {
return CommandReassembleResult::COMMAND_TOO_LARGE;
}
rcommand.reset(ReassembledCommand::allocate(decompressed_size));
if(!compression::qlz_decompress_payload(compressed_command->command(), rcommand->command(), &decompressed_size)) {
return CommandReassembleResult::COMMAND_DECOMPRESS_FAILED;
}
rcommand->set_length(decompressed_size);
}
assembled_command = rcommand.release();
return more_commands_pending ? CommandReassembleResult::MORE_COMMANDS_PENDING : CommandReassembleResult::SUCCESS;
}

View File

@ -0,0 +1,92 @@
#pragma once
#include <misc/spin_mutex.h>
#include <mutex>
#include <deque>
#include <protocol/Packet.h>
#include <protocol/generation.h>
#include <protocol/ringbuffer.h>
#include "./RawCommand.h"
namespace ts::connection {
class CryptHandler;
}
namespace ts::protocol {
enum struct PacketProcessResult {
SUCCESS,
UNKNOWN_ERROR,
FUZZ_DROPPED,
DUPLICATED_PACKET, /* error message contains debug properties */
BUFFER_OVERFLOW, /* error message contains debug properties */
BUFFER_UNDERFLOW, /* error message contains debug properties */
COMMAND_BUFFER_OVERFLOW, /* can cause a total connection drop */
COMMAND_SEQUENCE_LENGTH_TOO_LONG, /* unrecoverable error */
COMMAND_TOO_LARGE,
COMMAND_DECOMPRESS_FAILED,
DECRYPT_KEY_GEN_FAILED,
DECRYPT_FAILED, /* has custom message */
};
enum struct CommandReassembleResult {
SUCCESS,
MORE_COMMANDS_PENDING, /* equal with success */
NO_COMMANDS_PENDING,
COMMAND_TOO_LARGE, /* this is a fatal error to the connection */
COMMAND_DECOMPRESS_FAILED,
SEQUENCE_LENGTH_TOO_LONG /* unrecoverable error */
};
/* TODO: Implement for the client the command overflow recovery option! */
class PacketDecoder {
using CommandFragment = command::CommandFragment;
using ReassembledCommand = command::ReassembledCommand;
typedef protocol::FullPacketRingBuffer<CommandFragment, 32, CommandFragment> command_fragment_buffer_t;
typedef std::array<command_fragment_buffer_t, 2> command_packet_reassembler;
public:
/* direct function calls are better optimized out */
typedef void(*callback_decoded_packet_t)(void* /* cb argument */, const protocol::PacketParser&);
typedef void(*callback_decoded_command_t)(void* /* cb argument */, ReassembledCommand*& /* command */); /* must move the command, else it gets freed */
typedef void(*callback_send_acknowledge_t)(void* /* cb argument */, uint16_t /* packet id */, bool /* is command low */);
explicit PacketDecoder(connection::CryptHandler* /* crypt handler */, bool /* is server */);
~PacketDecoder();
void reset();
bool verify_encryption_client_packet(const protocol::ClientPacketParser& /* packet */);
/* true if commands might be pending */
PacketProcessResult process_incoming_data(protocol::PacketParser &/* packet */, std::string& /* error detail */);
void register_initiv_packet();
void* callback_argument{nullptr};
callback_decoded_packet_t callback_decoded_packet{[](auto, auto&){}}; /* needs to be valid all the time! */
callback_decoded_command_t callback_decoded_command{[](auto, auto&){}}; /* needs to be valid all the time! */
callback_send_acknowledge_t callback_send_acknowledge{[](auto, auto, auto){}}; /* needs to be valid all the time! */
private:
bool is_server;
connection::CryptHandler* crypt_handler_{nullptr};
spin_mutex incoming_generation_estimator_lock{};
std::array<protocol::GenerationEstimator, 9> incoming_generation_estimators{}; /* implementation is thread save */
std::recursive_mutex packet_buffer_lock;
command_packet_reassembler _command_fragment_buffers;
static inline uint8_t command_fragment_buffer_index(uint8_t packet_index) {
return packet_index & 0x1U; /* use 0 for command and 1 for command low */
}
PacketProcessResult decrypt_incoming_packet(std::string &error /* error */, protocol::PacketParser &packet_parser/* packet */);
CommandReassembleResult try_reassemble_ordered_packet(command_fragment_buffer_t& /* buffer */, std::unique_lock<std::mutex>& /* buffer lock */, ReassembledCommand*& /* command */);
};
}

View File

@ -80,7 +80,7 @@ void UnorderedPacketLossCalculator::reset_offsets() {
void CommandPacketLossCalculator::packet_send(uint32_t packet_id) {
if(packet_id > this->packet_history_offset) {
assert(packet_id - 1 == this->packet_history_offset); /* the method will only be called with an incrementing packet id */
assert(packet_id - 1 == this->packet_history_offset || this->packet_history_offset == 0); /* the method will only be called with an incrementing packet id */
/* newly send packet */
auto lost = std::exchange(this->packet_ack_counts[packet_id % CommandPacketLossCalculator::packet_ack_counts_length], 1);
this->lost_packets_ += lost;

View File

@ -0,0 +1,124 @@
//
// Created by WolverinDEV on 06/04/2020.
//
#include <mutex>
#include "./PacketStatistics.h"
using namespace ts::protocol;
void PacketStatistics::received_packet(ts::protocol::PacketType type, uint32_t pid) {
std::lock_guard lock{this->data_mutex};
switch (type) {
case protocol::PacketType::VOICE:
this->calculator_voice.packet_received(pid);
return;
case protocol::PacketType::VOICE_WHISPER:
this->calculator_voice_whisper.packet_received(pid);
return;
case protocol::PacketType::COMMAND:
case protocol::PacketType::COMMAND_LOW:
return;
case protocol::PacketType::ACK:
this->calculator_ack.packet_received(pid);
return;
case protocol::PacketType::ACK_LOW:
this->calculator_ack_low.packet_received(pid);
return;
case protocol::PacketType::PING:
this->calculator_ping.packet_received(pid);
return;
default:
/* some invalid packet lul */
return;
}
}
void PacketStatistics::send_command(ts::protocol::PacketType type, uint32_t pid) {
std::lock_guard lock{this->data_mutex};
if(type == protocol::PacketType::COMMAND)
this->calculator_command.packet_send(pid);
else if(type == protocol::PacketType::COMMAND_LOW)
this->calculator_command_low.packet_send(pid);
}
void PacketStatistics::received_acknowledge(ts::protocol::PacketType type, uint32_t pid) {
std::lock_guard lock{this->data_mutex};
if(type == protocol::PacketType::ACK)
this->calculator_command.ack_received(pid);
else if(type == protocol::PacketType::ACK_LOW)
this->calculator_command_low.ack_received(pid);
}
PacketStatistics::PacketLossReport PacketStatistics::loss_report() const {
PacketStatistics::PacketLossReport result{};
result.received_voice = this->calculator_voice.received_packets() + this->calculator_voice_whisper.received_packets();
result.lost_voice = this->calculator_voice.lost_packets() + this->calculator_voice_whisper.lost_packets();
result.received_keep_alive = this->calculator_ping.received_packets();
result.lost_keep_alive = this->calculator_ping.lost_packets();
result.received_control = this->calculator_command.received_packets() + this->calculator_command_low.received_packets();
result.lost_control = this->calculator_command.lost_packets() + this->calculator_command_low.lost_packets();
//result.lost_control -= this->calculator_ack.lost_packets() + this->calculator_ack_low.lost_packets(); /* subtract the lost acks (command received but ack got lost) */
result.received_control += this->calculator_ack.received_packets() + this->calculator_ack_low.received_packets();
//result.lost_control += this->calculator_ack.lost_packets() + this->calculator_ack_low.lost_packets(); /* this cancels out the line above */
return result;
}
void PacketStatistics::tick() {
auto now = std::chrono::system_clock::now();
if(now + std::chrono::seconds{15} > this->last_short) {
this->last_short = now;
std::lock_guard lock{this->data_mutex};
this->calculator_command.short_stats();
this->calculator_command_low.short_stats();
this->calculator_ack.short_stats();
this->calculator_ack_low.short_stats();
this->calculator_voice.short_stats();
this->calculator_voice_whisper.short_stats();
this->calculator_ping.short_stats();
}
}
void PacketStatistics::reset() {
std::lock_guard lock{this->data_mutex};
this->calculator_command.reset();
this->calculator_command_low.reset();
this->calculator_ack.reset();
this->calculator_ack_low.reset();
this->calculator_voice.reset();
this->calculator_voice_whisper.reset();
this->calculator_ping.reset();
}
void PacketStatistics::reset_offsets() {
std::lock_guard lock{this->data_mutex};
this->calculator_command.reset_offsets();
this->calculator_command_low.reset_offsets();
this->calculator_ack.reset_offsets();
this->calculator_ack_low.reset_offsets();
this->calculator_voice.reset_offsets();
this->calculator_voice_whisper.reset_offsets();
this->calculator_ping.reset_offsets();
}
float PacketStatistics::current_packet_loss() const {
auto report = this->loss_report();
return report.total_loss();
}

View File

@ -0,0 +1,68 @@
#pragma once
#include "../misc/spin_mutex.h"
#include "./PacketLossCalculator.h"
#include "./Packet.h"
namespace ts::protocol {
class PacketStatistics {
public:
struct PacketLossReport {
uint32_t lost_voice{0};
uint32_t lost_control{0};
uint32_t lost_keep_alive{0};
uint32_t received_voice{0};
uint32_t received_control{0};
uint32_t received_keep_alive{0};
[[nodiscard]] inline float voice_loss() const {
const auto total_packets = this->received_voice + this->lost_voice;
if(total_packets == 0) return 0;
return this->lost_voice / (float) total_packets;
}
[[nodiscard]] inline float control_loss() const {
const auto total_packets = this->received_control + this->lost_control;
//if(total_packets == 0) return 0; /* not possible so remove this to speed it up */
return this->lost_control / (float) total_packets;
}
[[nodiscard]] inline float keep_alive_loss() const {
const auto total_packets = this->received_keep_alive + this->lost_keep_alive;
if(total_packets == 0) return 0;
return this->lost_keep_alive / (float) total_packets;
}
[[nodiscard]] inline float total_loss() const {
const auto total_lost = this->lost_voice + this->lost_control + this->lost_keep_alive;
const auto total_received = this->received_control + this->received_voice + this->received_keep_alive;
//if(total_received + total_lost == 0) return 0; /* not possible to speed this up */
return total_lost / (float) (total_lost + total_received);
}
};
[[nodiscard]] PacketLossReport loss_report() const;
[[nodiscard]] float current_packet_loss() const;
void send_command(protocol::PacketType /* type */, uint32_t /* packet id */);
void received_acknowledge(protocol::PacketType /* type */, uint32_t /* packet id */);
void received_packet(protocol::PacketType /* type */, uint32_t /* packet id */);
void tick();
void reset();
void reset_offsets();
private:
std::chrono::system_clock::time_point last_short{};
spin_mutex data_mutex{};
protocol::UnorderedPacketLossCalculator calculator_voice_whisper{};
protocol::UnorderedPacketLossCalculator calculator_voice{};
protocol::UnorderedPacketLossCalculator calculator_ack_low{};
protocol::UnorderedPacketLossCalculator calculator_ack{};
protocol::UnorderedPacketLossCalculator calculator_ping{};
protocol::CommandPacketLossCalculator calculator_command{};
protocol::CommandPacketLossCalculator calculator_command_low{};
};
}

View File

@ -0,0 +1,76 @@
//
// Created by WolverinDEV on 11/03/2020.
//
#include "./PingHandler.h"
using namespace ts::server::server::udp;
void PingHandler::reset() {
this->last_ping_id = 0;
this->current_ping_ = std::chrono::milliseconds{0};
this->last_recovery_command_send = std::chrono::system_clock::time_point{};
this->last_command_acknowledge_ = std::chrono::system_clock::time_point{};
this->last_response_ = std::chrono::system_clock::time_point{};
this->last_request_ = std::chrono::system_clock::time_point{};
}
void PingHandler::received_pong(uint16_t ping_id) {
if(this->last_ping_id != ping_id) return;
auto now = std::chrono::system_clock::now();
this->current_ping_ = std::chrono::floor<std::chrono::milliseconds>(now - this->last_request_);
this->last_response_ = now;
this->last_command_acknowledge_ = now; /* That's here for purpose!*/
}
void PingHandler::received_command_acknowledged() {
this->last_command_acknowledge_ = std::chrono::system_clock::now();
}
void PingHandler::tick(const std::chrono::system_clock::time_point& now) {
if(this->last_request_ + PingHandler::kPingRequestInterval < now) {
this->send_ping_request(); /* may update last_response_ */
}
if(this->last_response_ + PingHandler::kPingTimeout < now) {
if(this->last_recovery_command_send + PingHandler::kRecoveryRequestInterval < now) {
this->send_recovery_request();
}
if(this->last_command_acknowledge_ + PingHandler::kRecoveryTimeout < now) {
if(auto callback{this->callback_time_outed}; callback) {
callback(this->callback_argument);
}
}
}
}
void PingHandler::send_ping_request() {
auto now = std::chrono::system_clock::now();
if(this->last_response_.time_since_epoch().count() == 0) {
this->last_response_ = now;
}
this->last_request_ = now;
if(auto callback{this->callback_send_ping}; callback) {
callback(this->callback_argument, this->last_ping_id);
}
}
void PingHandler::send_recovery_request() {
auto now = std::chrono::system_clock::now();
if(this->last_command_acknowledge_.time_since_epoch().count() == 0) {
this->last_command_acknowledge_ = now;
}
this->last_recovery_command_send = now;
if(auto callback{this->callback_send_recovery_command}; callback) {
callback(this->callback_argument);
}
}

View File

@ -0,0 +1,46 @@
#pragma once
#include <cstdint>
#include <chrono>
namespace ts::server::server::udp {
class PingHandler {
public:
typedef void(*callback_time_outed_t)(void* /* cb data */);
typedef void(*callback_send_ping_t)(void* /* cb data */, uint16_t& /* ping id */);
typedef void(*callback_send_recovery_command_t)(void* /* cb data */);
void reset();
void tick(const std::chrono::system_clock::time_point&);
void received_pong(uint16_t /* ping id */);
void received_command_acknowledged();
[[nodiscard]] inline std::chrono::milliseconds current_ping() const { return this->current_ping_; }
[[nodiscard]] inline std::chrono::system_clock::time_point last_ping_response() const { return this->last_response_; }
[[nodiscard]] inline std::chrono::system_clock::time_point last_command_acknowledged() const { return this->last_command_acknowledge_; }
void* callback_argument{nullptr};
callback_send_ping_t callback_send_ping{nullptr};
callback_send_recovery_command_t callback_send_recovery_command{nullptr};
callback_time_outed_t callback_time_outed{nullptr};
private:
constexpr static std::chrono::milliseconds kPingRequestInterval{1000};
constexpr static std::chrono::milliseconds kPingTimeout{15 * 1000};
constexpr static std::chrono::milliseconds kRecoveryRequestInterval{1000};
constexpr static std::chrono::milliseconds kRecoveryTimeout{15 * 1000};
std::chrono::milliseconds current_ping_{0};
uint16_t last_ping_id{0};
std::chrono::system_clock::time_point last_response_{};
std::chrono::system_clock::time_point last_request_{};
std::chrono::system_clock::time_point last_command_acknowledge_{};
std::chrono::system_clock::time_point last_recovery_command_send{};
void send_ping_request();
void send_recovery_request();
};
}

View File

@ -0,0 +1,19 @@
//
// Created by WolverinDEV on 28/01/2021.
//
#include "./RawCommand.h"
using namespace ts::command;
ReassembledCommand *ReassembledCommand::allocate(size_t size) {
auto instance = (ReassembledCommand*) malloc(sizeof(ReassembledCommand) + size);
instance->length_ = size;
instance->capacity_ = size;
instance->next_command = nullptr;
return instance;
}
void ReassembledCommand::free(ReassembledCommand *command) {
::free(command);
}

52
src/protocol/RawCommand.h Normal file
View File

@ -0,0 +1,52 @@
#pragma once
#include <cstdint>
#include <string_view>
#include <pipes/buffer.h>
namespace ts::command {
struct CommandFragment {
uint16_t packet_id{0};
uint16_t packet_generation{0};
uint8_t packet_flags{0};
uint32_t payload_length : 24;
pipes::buffer payload{};
CommandFragment() : payload_length{0} { }
CommandFragment(uint16_t packetId, uint16_t packetGeneration, uint8_t packetFlags, uint32_t payloadLength, pipes::buffer payload)
: packet_id{packetId}, packet_generation{packetGeneration}, packet_flags{packetFlags}, payload_length{payloadLength}, payload{std::move(payload)} {}
CommandFragment& operator=(const CommandFragment&) = default;
CommandFragment(const CommandFragment& other) = default;
CommandFragment(CommandFragment&&) = default;
};
/* Windows aligns stuff somewhat different */
#ifndef WIN32
static_assert(sizeof(CommandFragment) == 8 + sizeof(pipes::buffer));
#endif
struct ReassembledCommand {
public:
static ReassembledCommand* allocate(size_t /* command length */);
static void free(ReassembledCommand* /* command */);
[[nodiscard]] inline size_t length() const { return this->length_; }
inline void set_length(size_t length) { assert(this->capacity_ >= length); this->length_ = length; }
[[nodiscard]] inline size_t capacity() const { return this->capacity_; }
[[nodiscard]] inline const char* command() const { return (const char*) this + sizeof(ReassembledCommand); }
[[nodiscard]] inline char* command() { return (char*) this + sizeof(ReassembledCommand); }
[[nodiscard]] inline std::string_view command_view() const { return std::string_view{this->command(), this->length()}; }
mutable ReassembledCommand* next_command; /* nullptr by default */
private:
explicit ReassembledCommand() = default;
size_t capacity_;
size_t length_;
};
}

View File

@ -1,19 +1,15 @@
#pragma once
#include <list>
#include <chrono>
#include <memory>
#include <list>
#include <cstring>
#include <ThreadPool/Mutex.h>
#include <sstream>
#include "Packet.h"
#include "../misc/queue.h"
#include <cassert>
#include <utility>
#ifndef NO_LOG
#include <log/LogUtils.h>
#endif
#include "./Packet.h"
#include "../misc/queue.h"
namespace ts::buffer {
struct RawBuffer {
@ -72,6 +68,8 @@ namespace ts::buffer {
return 1024;
case Bytes_1536:
return 1536;
case unset:
case max:
default:
return 0;
}

View File

@ -2,22 +2,22 @@
using namespace ts::protocol;
generation_estimator::generation_estimator() {
GenerationEstimator::GenerationEstimator() {
this->reset();
}
void generation_estimator::reset() {
void GenerationEstimator::reset() {
this->last_generation = 0;
this->last_packet_id = 0;
}
uint16_t generation_estimator::visit_packet(uint16_t packet_id) {
if(this->last_packet_id >= generation_estimator::overflow_area_begin) {
uint16_t GenerationEstimator::visit_packet(uint16_t packet_id) {
if(this->last_packet_id >= GenerationEstimator::overflow_area_begin) {
if(packet_id > this->last_packet_id) {
/* normal behaviour */
this->last_packet_id = packet_id;
return this->last_generation;
} else if(packet_id < generation_estimator::overflow_area_end) {
} else if(packet_id < GenerationEstimator::overflow_area_end) {
/* we're within a new generation */
this->last_packet_id = packet_id;
return ++this->last_generation;
@ -25,16 +25,21 @@ uint16_t generation_estimator::visit_packet(uint16_t packet_id) {
/* old packet */
return this->last_generation;
}
} else if(this->last_packet_id <= generation_estimator::overflow_area_end) {
if(packet_id >= generation_estimator::overflow_area_begin) /* old packet */
} else if(this->last_packet_id <= GenerationEstimator::overflow_area_end) {
if(packet_id >= GenerationEstimator::overflow_area_begin) {/* old packet */
return this->last_generation - 1;
if(packet_id > this->last_packet_id)
}
if(packet_id > this->last_packet_id) {
this->last_packet_id = packet_id;
}
return this->last_generation;
} else {
/* only update on newer packet id */
if(packet_id > this->last_packet_id)
if(packet_id > this->last_packet_id) {
this->last_packet_id = packet_id;
}
return this->last_generation;
}
}

View File

@ -3,9 +3,9 @@
#include <cstdint>
namespace ts::protocol {
class generation_estimator {
class GenerationEstimator {
public:
generation_estimator();
GenerationEstimator();
void reset();
[[nodiscard]] uint16_t visit_packet(uint16_t /* packet id */);

View File

@ -129,17 +129,25 @@ namespace ts {
}
inline bool insert_index(size_type index, E&& entry) {
return insert_index2(index, std::forward<E>(entry)) == 0;
}
/**
* @param index
* @return -2 duplicated entry | -1 underflow | 0 success | 1 overflow
*/
inline int insert_index2(size_type index, E&& entry) {
size_t relative_index = 0;
if(!this->calculate_index(index, relative_index))
return false;
return index < this->current_index() ? -1 : 1;
auto& slot = this->index(relative_index);
if(slot.flag_set)
return false;
return -2;
slot.entry = std::forward<E>(entry);
slot.flag_set = true;
return true;
return 0;
}
inline size_t capacity() { return this->_capacity; }
@ -209,5 +217,11 @@ namespace ts {
std::recursive_timed_mutex buffer_lock;
std::recursive_timed_mutex execute_lock;
};
template <typename E, uint32_t SIZE = 32, typename PTR_TYPE = std::shared_ptr<E>>
class FullPacketRingBuffer : public RingBuffer<PTR_TYPE, SIZE, uint32_t> {
public:
std::mutex buffer_lock;
};
}
}

View File

@ -9,7 +9,7 @@
// 1.5.0 final
#include "QuickLZ.h"
#include "./QuickLZ.h"
#if QLZ_VERSION_MAJOR != 1 || QLZ_VERSION_MINOR != 5 || QLZ_VERSION_REVISION != 0
#error quicklz.c and quicklz.h have different versions
@ -19,8 +19,829 @@
#define X86X64
#endif
#define MINOFFSET 2
#define UNCONDITIONAL_MATCHLEN 6
#define UNCOMPRESSED_END 4
#define CWORD_LEN 4
#if QLZ_COMPRESSION_LEVEL == 1 && defined QLZ_PTR_64 && QLZ_STREAMING_BUFFER == 0
#define OFFSET_BASE source
#define CAST (ui32)(size_t)
#else
#define OFFSET_BASE 0
#define CAST
#endif
int qlz_get_setting(int setting)
{
switch (setting)
{
case 0: return QLZ_COMPRESSION_LEVEL;
case 1: return sizeof(qlz_state_compress);
case 2: return sizeof(qlz_state_decompress);
case 3: return QLZ_STREAMING_BUFFER;
#ifdef QLZ_MEMORY_SAFE
case 6: return 1;
#else
case 6: return 0;
#endif
case 7: return QLZ_VERSION_MAJOR;
case 8: return QLZ_VERSION_MINOR;
case 9: return QLZ_VERSION_REVISION;
}
return -1;
}
#if QLZ_COMPRESSION_LEVEL == 1
static int same(const unsigned char *src, size_t n)
{
while(n > 0 && *(src + n) == *src)
n--;
return n == 0 ? 1 : 0;
}
#endif
static void reset_table_compress(qlz_state_compress *state)
{
int i;
for(i = 0; i < QLZ_HASH_VALUES; i++)
{
#if QLZ_COMPRESSION_LEVEL == 1
state->hash[i].offset = 0;
#else
state->hash_counter[i] = 0;
#endif
}
}
static void reset_table_decompress(qlz_state_decompress *state)
{
int i;
(void)state;
(void)i;
#if QLZ_COMPRESSION_LEVEL == 2
for(i = 0; i < QLZ_HASH_VALUES; i++)
{
state->hash_counter[i] = 0;
}
#endif
}
static __inline ui32 hash_func(ui32 i)
{
#if QLZ_COMPRESSION_LEVEL == 2
return ((i >> 9) ^ (i >> 13) ^ i) & (QLZ_HASH_VALUES - 1);
#else
return ((i >> 12) ^ i) & (QLZ_HASH_VALUES - 1);
#endif
}
static __inline ui32 fast_read(void const *src, ui32 bytes)
{
#ifndef X86X64
unsigned char *p = (unsigned char*)src;
switch (bytes)
{
case 4:
return(*p | *(p + 1) << 8 | *(p + 2) << 16 | *(p + 3) << 24);
case 3:
return(*p | *(p + 1) << 8 | *(p + 2) << 16);
case 2:
return(*p | *(p + 1) << 8);
case 1:
return(*p);
}
return 0;
#else
if (bytes >= 1 && bytes <= 4)
return *((ui32*)src);
else
return 0;
#endif
}
static __inline ui32 hashat(const unsigned char *src)
{
ui32 fetch, hash;
fetch = fast_read(src, 3);
hash = hash_func(fetch);
return hash;
}
static __inline void fast_write(ui32 f, void *dst, size_t bytes)
{
#ifndef X86X64
unsigned char *p = (unsigned char*)dst;
switch (bytes)
{
case 4:
*p = (unsigned char)f;
*(p + 1) = (unsigned char)(f >> 8);
*(p + 2) = (unsigned char)(f >> 16);
*(p + 3) = (unsigned char)(f >> 24);
return;
case 3:
*p = (unsigned char)f;
*(p + 1) = (unsigned char)(f >> 8);
*(p + 2) = (unsigned char)(f >> 16);
return;
case 2:
*p = (unsigned char)f;
*(p + 1) = (unsigned char)(f >> 8);
return;
case 1:
*p = (unsigned char)f;
return;
}
#else
switch (bytes)
{
case 4:
*((ui32*)dst) = f;
return;
case 3:
*((ui32*)dst) = f;
return;
case 2:
*((ui16 *)dst) = (ui16)f;
return;
case 1:
*((unsigned char*)dst) = (unsigned char)f;
return;
}
#endif
}
size_t qlz_size_decompressed(const char *source)
{
ui32 n, r;
n = (((*source) & 2) == 2) ? 4 : 1;
r = fast_read(source + 1 + n, n);
r = r & (0xffffffff >> ((4 - n)*8));
return r;
}
size_t qlz_size_compressed(const char *source)
{
ui32 n, r;
n = (((*source) & 2) == 2) ? 4 : 1;
r = fast_read(source + 1, n);
r = r & (0xffffffff >> ((4 - n)*8));
return r;
}
size_t qlz_size_header(const char *source)
{
size_t n = 2*((((*source) & 2) == 2) ? 4 : 1) + 1;
return n;
}
}
static __inline void memcpy_up(unsigned char *dst, const unsigned char *src, ui32 n)
{
// Caution if modifying memcpy_up! Overlap of dst and src must be special handled.
#ifndef X86X64
unsigned char *end = dst + n;
while(dst < end)
{
*dst = *src;
dst++;
src++;
}
#else
ui32 f = 0;
do
{
*(ui32 *)(dst + f) = *(ui32 *)(src + f);
f += MINOFFSET + 1;
}
while (f < n);
#endif
}
static __inline void update_hash(qlz_state_decompress *state, const unsigned char *s)
{
#if QLZ_COMPRESSION_LEVEL == 1
ui32 hash;
hash = hashat(s);
state->hash[hash].offset = s;
state->hash_counter[hash] = 1;
#elif QLZ_COMPRESSION_LEVEL == 2
ui32 hash;
unsigned char c;
hash = hashat(s);
c = state->hash_counter[hash];
state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = s;
c++;
state->hash_counter[hash] = c;
#endif
(void)state;
(void)s;
}
#if QLZ_COMPRESSION_LEVEL <= 2
static void update_hash_upto(qlz_state_decompress *state, unsigned char **lh, const unsigned char *max)
{
while(*lh < max)
{
(*lh)++;
update_hash(state, *lh);
}
}
#endif
static size_t qlz_compress_core(const unsigned char *source, unsigned char *destination, size_t size, qlz_state_compress *state)
{
const unsigned char *last_byte = source + size - 1;
const unsigned char *src = source;
unsigned char *cword_ptr = destination;
unsigned char *dst = destination + CWORD_LEN;
ui32 cword_val = 1U << 31;
const unsigned char *last_matchstart = last_byte - UNCONDITIONAL_MATCHLEN - UNCOMPRESSED_END;
ui32 fetch = 0;
unsigned int lits = 0;
(void) lits;
if(src <= last_matchstart)
fetch = fast_read(src, 3);
while(src <= last_matchstart)
{
if ((cword_val & 1) == 1)
{
// store uncompressed if compression ratio is too low
if (src > source + (size >> 1) && dst - destination > src - source - ((src - source) >> 5))
return 0;
fast_write((cword_val >> 1) | (1U << 31), cword_ptr, CWORD_LEN);
cword_ptr = dst;
dst += CWORD_LEN;
cword_val = 1U << 31;
fetch = fast_read(src, 3);
}
#if QLZ_COMPRESSION_LEVEL == 1
{
const unsigned char *o;
ui32 hash, cached;
hash = hash_func(fetch);
cached = fetch ^ state->hash[hash].cache;
state->hash[hash].cache = fetch;
o = state->hash[hash].offset + OFFSET_BASE;
state->hash[hash].offset = CAST(src - OFFSET_BASE);
#ifdef X86X64
if ((cached & 0xffffff) == 0 && o != OFFSET_BASE && (src - o > MINOFFSET || (src == o + 1 && lits >= 3 && src > source + 3 && same(src - 3, 6))))
{
if(cached != 0)
{
#else
if (cached == 0 && o != OFFSET_BASE && (src - o > MINOFFSET || (src == o + 1 && lits >= 3 && src > source + 3 && same(src - 3, 6))))
{
if (*(o + 3) != *(src + 3))
{
#endif
hash <<= 4;
cword_val = (cword_val >> 1) | (1U << 31);
fast_write((3 - 2) | hash, dst, 2);
src += 3;
dst += 2;
}
else
{
const unsigned char *old_src = src;
size_t matchlen;
hash <<= 4;
cword_val = (cword_val >> 1) | (1U << 31);
src += 4;
if(*(o + (src - old_src)) == *src)
{
src++;
if(*(o + (src - old_src)) == *src)
{
size_t q = last_byte - UNCOMPRESSED_END - (src - 5) + 1;
size_t remaining = q > 255 ? 255 : q;
src++;
while(*(o + (src - old_src)) == *src && (size_t)(src - old_src) < remaining)
src++;
}
}
matchlen = src - old_src;
if (matchlen < 18)
{
fast_write((ui32)(matchlen - 2) | hash, dst, 2);
dst += 2;
}
else
{
fast_write((ui32)(matchlen << 16) | hash, dst, 3);
dst += 3;
}
}
fetch = fast_read(src, 3);
lits = 0;
}
else
{
lits++;
*dst = *src;
src++;
dst++;
cword_val = (cword_val >> 1);
#ifdef X86X64
fetch = fast_read(src, 3);
#else
fetch = (fetch >> 8 & 0xffff) | (*(src + 2) << 16);
#endif
}
}
#elif QLZ_COMPRESSION_LEVEL >= 2
{
const unsigned char *o, *offset2;
ui32 hash, matchlen, k, m, best_k = 0;
unsigned char c;
size_t remaining = (last_byte - UNCOMPRESSED_END - src + 1) > 255 ? 255 : (last_byte - UNCOMPRESSED_END - src + 1);
(void)best_k;
//hash = hashat(src);
fetch = fast_read(src, 3);
hash = hash_func(fetch);
c = state->hash_counter[hash];
offset2 = state->hash[hash].offset[0];
if(offset2 < src - MINOFFSET && c > 0 && ((fast_read(offset2, 3) ^ fetch) & 0xffffff) == 0)
{
matchlen = 3;
if(*(offset2 + matchlen) == *(src + matchlen))
{
matchlen = 4;
while(*(offset2 + matchlen) == *(src + matchlen) && matchlen < remaining)
matchlen++;
}
}
else
matchlen = 0;
for(k = 1; k < QLZ_POINTERS && c > k; k++)
{
o = state->hash[hash].offset[k];
#if QLZ_COMPRESSION_LEVEL == 3
if(((fast_read(o, 3) ^ fetch) & 0xffffff) == 0 && o < src - MINOFFSET)
#elif QLZ_COMPRESSION_LEVEL == 2
if(*(src + matchlen) == *(o + matchlen) && ((fast_read(o, 3) ^ fetch) & 0xffffff) == 0 && o < src - MINOFFSET)
#endif
{
m = 3;
while(*(o + m) == *(src + m) && m < remaining)
m++;
#if QLZ_COMPRESSION_LEVEL == 3
if ((m > matchlen) || (m == matchlen && o > offset2))
#elif QLZ_COMPRESSION_LEVEL == 2
if (m > matchlen)
#endif
{
offset2 = o;
matchlen = m;
best_k = k;
}
}
}
o = offset2;
state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = src;
c++;
state->hash_counter[hash] = c;
#if QLZ_COMPRESSION_LEVEL == 3
if(matchlen > 2 && src - o < 131071)
{
ui32 u;
size_t offset = src - o;
for(u = 1; u < matchlen; u++)
{
hash = hashat(src + u);
c = state->hash_counter[hash]++;
state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = src + u;
}
cword_val = (cword_val >> 1) | (1U << 31);
src += matchlen;
if(matchlen == 3 && offset <= 63)
{
*dst = (unsigned char)(offset << 2);
dst++;
}
else if (matchlen == 3 && offset <= 16383)
{
ui32 f = (ui32)((offset << 2) | 1);
fast_write(f, dst, 2);
dst += 2;
}
else if (matchlen <= 18 && offset <= 1023)
{
ui32 f = ((matchlen - 3) << 2) | ((ui32)offset << 6) | 2;
fast_write(f, dst, 2);
dst += 2;
}
else if(matchlen <= 33)
{
ui32 f = ((matchlen - 2) << 2) | ((ui32)offset << 7) | 3;
fast_write(f, dst, 3);
dst += 3;
}
else
{
ui32 f = ((matchlen - 3) << 7) | ((ui32)offset << 15) | 3;
fast_write(f, dst, 4);
dst += 4;
}
}
else
{
*dst = *src;
src++;
dst++;
cword_val = (cword_val >> 1);
}
#elif QLZ_COMPRESSION_LEVEL == 2
if(matchlen > 2)
{
cword_val = (cword_val >> 1) | (1U << 31);
src += matchlen;
if (matchlen < 10)
{
ui32 f = best_k | ((matchlen - 2) << 2) | (hash << 5);
fast_write(f, dst, 2);
dst += 2;
}
else
{
ui32 f = best_k | (matchlen << 16) | (hash << 5);
fast_write(f, dst, 3);
dst += 3;
}
}
else
{
*dst = *src;
src++;
dst++;
cword_val = (cword_val >> 1);
}
#endif
}
#endif
}
while (src <= last_byte)
{
if ((cword_val & 1) == 1)
{
fast_write((cword_val >> 1) | (1U << 31), cword_ptr, CWORD_LEN);
cword_ptr = dst;
dst += CWORD_LEN;
cword_val = 1U << 31;
}
#if QLZ_COMPRESSION_LEVEL < 3
if (src <= last_byte - 3)
{
#if QLZ_COMPRESSION_LEVEL == 1
ui32 hash, fetch;
fetch = fast_read(src, 3);
hash = hash_func(fetch);
state->hash[hash].offset = CAST(src - OFFSET_BASE);
state->hash[hash].cache = fetch;
#elif QLZ_COMPRESSION_LEVEL == 2
ui32 hash;
unsigned char c;
hash = hashat(src);
c = state->hash_counter[hash];
state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = src;
c++;
state->hash_counter[hash] = c;
#endif
}
#endif
*dst = *src;
src++;
dst++;
cword_val = (cword_val >> 1);
}
while((cword_val & 1) != 1)
cword_val = (cword_val >> 1);
fast_write((cword_val >> 1) | (1U << 31), cword_ptr, CWORD_LEN);
// min. size must be 9 bytes so that the qlz_size functions can take 9 bytes as argument
return dst - destination < 9 ? 9 : dst - destination;
}
static size_t qlz_decompress_core(const unsigned char *source, unsigned char *destination, size_t size, qlz_state_decompress *state, const unsigned char *history)
{
const unsigned char *src = source + qlz_size_header((const char *)source);
unsigned char *dst = destination;
const unsigned char *last_destination_byte = destination + size - 1;
ui32 cword_val = 1;
const unsigned char *last_matchstart = last_destination_byte - UNCONDITIONAL_MATCHLEN - UNCOMPRESSED_END;
unsigned char *last_hashed = destination - 1;
const unsigned char *last_source_byte = source + qlz_size_compressed((const char *)source) - 1;
static const ui32 bitlut[16] = {4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0};
(void) last_source_byte;
(void) last_hashed;
(void) state;
(void) history;
for(;;)
{
ui32 fetch;
if (cword_val == 1)
{
#ifdef QLZ_MEMORY_SAFE
if(src + CWORD_LEN - 1 > last_source_byte)
return 0;
#endif
cword_val = fast_read(src, CWORD_LEN);
src += CWORD_LEN;
}
#ifdef QLZ_MEMORY_SAFE
if(src + 4 - 1 > last_source_byte)
return 0;
#endif
fetch = fast_read(src, 4);
if ((cword_val & 1) == 1)
{
ui32 matchlen;
const unsigned char *offset2;
#if QLZ_COMPRESSION_LEVEL == 1
ui32 hash;
cword_val = cword_val >> 1;
hash = (fetch >> 4) & 0xfff;
offset2 = (const unsigned char *)(size_t)state->hash[hash].offset;
if((fetch & 0xf) != 0)
{
matchlen = (fetch & 0xf) + 2;
src += 2;
}
else
{
matchlen = *(src + 2);
src += 3;
}
#elif QLZ_COMPRESSION_LEVEL == 2
ui32 hash;
unsigned char c;
cword_val = cword_val >> 1;
hash = (fetch >> 5) & 0x7ff;
c = (unsigned char)(fetch & 0x3);
offset2 = state->hash[hash].offset[c];
if((fetch & (28)) != 0)
{
matchlen = ((fetch >> 2) & 0x7) + 2;
src += 2;
}
else
{
matchlen = *(src + 2);
src += 3;
}
#elif QLZ_COMPRESSION_LEVEL == 3
ui32 offset;
cword_val = cword_val >> 1;
if ((fetch & 3) == 0)
{
offset = (fetch & 0xff) >> 2;
matchlen = 3;
src++;
}
else if ((fetch & 2) == 0)
{
offset = (fetch & 0xffff) >> 2;
matchlen = 3;
src += 2;
}
else if ((fetch & 1) == 0)
{
offset = (fetch & 0xffff) >> 6;
matchlen = ((fetch >> 2) & 15) + 3;
src += 2;
}
else if ((fetch & 127) != 3)
{
offset = (fetch >> 7) & 0x1ffff;
matchlen = ((fetch >> 2) & 0x1f) + 2;
src += 3;
}
else
{
offset = (fetch >> 15);
matchlen = ((fetch >> 7) & 255) + 3;
src += 4;
}
offset2 = dst - offset;
#endif
#ifdef QLZ_MEMORY_SAFE
if(offset2 < history || offset2 > dst - MINOFFSET - 1)
return 0;
if(matchlen > (ui32)(last_destination_byte - dst - UNCOMPRESSED_END + 1))
return 0;
#endif
memcpy_up(dst, offset2, matchlen);
dst += matchlen;
#if QLZ_COMPRESSION_LEVEL <= 2
update_hash_upto(state, &last_hashed, dst - matchlen);
last_hashed = dst - 1;
#endif
}
else
{
if (dst < last_matchstart)
{
unsigned int n = bitlut[cword_val & 0xf];
#ifdef X86X64
*(ui32 *)dst = *(ui32 *)src;
#else
memcpy_up(dst, src, 4);
#endif
cword_val = cword_val >> n;
dst += n;
src += n;
#if QLZ_COMPRESSION_LEVEL <= 2
update_hash_upto(state, &last_hashed, dst - 3);
#endif
}
else
{
while(dst <= last_destination_byte)
{
if (cword_val == 1)
{
src += CWORD_LEN;
cword_val = 1U << 31;
}
#ifdef QLZ_MEMORY_SAFE
if(src >= last_source_byte + 1)
return 0;
#endif
*dst = *src;
dst++;
src++;
cword_val = cword_val >> 1;
}
#if QLZ_COMPRESSION_LEVEL <= 2
update_hash_upto(state, &last_hashed, last_destination_byte - 3); // todo, use constant
#endif
return size;
}
}
}
}
size_t qlz_compress(const void *source, char *destination, size_t size, qlz_state_compress *state)
{
size_t r;
ui32 compressed;
size_t base;
if(size == 0 || size > 0xffffffff - 400)
return 0;
if(size < 216)
base = 3;
else
base = 9;
#if QLZ_STREAMING_BUFFER > 0
if (state->stream_counter + size - 1 >= QLZ_STREAMING_BUFFER)
#endif
{
reset_table_compress(state);
r = base + qlz_compress_core((const unsigned char *)source, (unsigned char*)destination + base, size, state);
#if QLZ_STREAMING_BUFFER > 0
reset_table_compress(state);
#endif
if(r == base)
{
memcpy(destination + base, source, size);
r = size + base;
compressed = 0;
}
else
{
compressed = 1;
}
state->stream_counter = 0;
}
#if QLZ_STREAMING_BUFFER > 0
else
{
unsigned char *src = state->stream_buffer + state->stream_counter;
memcpy(src, source, size);
r = base + qlz_compress_core(src, (unsigned char*)destination + base, size, state);
if(r == base)
{
memcpy(destination + base, src, size);
r = size + base;
compressed = 0;
reset_table_compress(state);
}
else
{
compressed = 1;
}
state->stream_counter += size;
}
#endif
if(base == 3)
{
*destination = (unsigned char)(0 | compressed);
*(destination + 1) = (unsigned char)r;
*(destination + 2) = (unsigned char)size;
}
else
{
*destination = (unsigned char)(2 | compressed);
fast_write((ui32)r, destination + 1, 4);
fast_write((ui32)size, destination + 5, 4);
}
*destination |= (QLZ_COMPRESSION_LEVEL << 2);
*destination |= (1 << 6);
*destination |= ((QLZ_STREAMING_BUFFER == 0 ? 0 : (QLZ_STREAMING_BUFFER == 100000 ? 1 : (QLZ_STREAMING_BUFFER == 1000000 ? 2 : 3))) << 4);
// 76543210
// 01SSLLHC
return r;
}
size_t qlz_decompress(const char *source, void *destination, qlz_state_decompress *state)
{
size_t dsiz = qlz_size_decompressed(source);
#if QLZ_STREAMING_BUFFER > 0
if (state->stream_counter + qlz_size_decompressed(source) - 1 >= QLZ_STREAMING_BUFFER)
#endif
{
if((*source & 1) == 1)
{
reset_table_decompress(state);
dsiz = qlz_decompress_core((const unsigned char *)source, (unsigned char *)destination, dsiz, state, (const unsigned char *)destination);
}
else
{
memcpy(destination, source + qlz_size_header(source), dsiz);
}
state->stream_counter = 0;
reset_table_decompress(state);
}
#if QLZ_STREAMING_BUFFER > 0
else
{
unsigned char *dst = state->stream_buffer + state->stream_counter;
if((*source & 1) == 1)
{
dsiz = qlz_decompress_core((const unsigned char *)source, dst, dsiz, state, (const unsigned char *)state->stream_buffer);
}
else
{
memcpy(dst, source + qlz_size_header(source), dsiz);
reset_table_decompress(state);
}
memcpy(destination, dst, dsiz);
state->stream_counter += dsiz;
}
#endif
return dsiz;
}

View File

@ -18,25 +18,10 @@
// 1.5.0 final
#ifndef QLZ_COMPRESSION_LEVEL
// 1 gives fastest compression speed. 3 gives fastest decompression speed and best
// compression ratio.
#define QLZ_COMPRESSION_LEVEL 3
//#define QLZ_COMPRESSION_LEVEL 2
//#define QLZ_COMPRESSION_LEVEL 3
#endif
#ifndef QLZ_STREAMING_BUFFER
// If > 0, zero out both states prior to first call to qlz_compress() or qlz_decompress()
// and decompress packets in the same order as they were compressed
/* Fixed definitions for TeaSpeak. */
#define QLZ_COMPRESSION_LEVEL 1
#define QLZ_STREAMING_BUFFER 0
//#define QLZ_STREAMING_BUFFER 100000
//#define QLZ_STREAMING_BUFFER 1000000
// Guarantees that decompression of corrupted data cannot crash. Decreases decompression
// speed 10-20%. Compression speed not affected.
//#define QLZ_MEMORY_SAFE
#endif
#define QLZ_MEMORY_SAFE
#define QLZ_VERSION_MAJOR 1
#define QLZ_VERSION_MINOR 5
@ -67,64 +52,68 @@ typedef unsigned short int ui16;
// Detect if pointer size is 64-bit. It's not fatal if some 64-bit target is not detected because this is only for adding an optional 64-bit optimization.
#if defined _LP64 || defined __LP64__ || defined __64BIT__ || _ADDR64 || defined _WIN64 || defined __arch64__ || __WORDSIZE == 64 || (defined __sparc && defined __sparcv9) || defined __x86_64 || defined __amd64 || defined __x86_64__ || defined _M_X64 || defined _M_IA64 || defined __ia64 || defined __IA64__
#define QLZ_PTR_64
#define QLZ_PTR_64
#endif
// hash entry
typedef struct {
typedef struct
{
#if QLZ_COMPRESSION_LEVEL == 1
ui32 cache;
ui32 cache;
#if defined QLZ_PTR_64 && QLZ_STREAMING_BUFFER == 0
unsigned int offset;
unsigned int offset;
#else
const unsigned char *offset;
const unsigned char *offset;
#endif
#else
const unsigned char *offset[QLZ_POINTERS];
const unsigned char *offset[QLZ_POINTERS];
#endif
} qlz_hash_compress;
typedef struct {
typedef struct
{
#if QLZ_COMPRESSION_LEVEL == 1
const unsigned char *offset;
const unsigned char *offset;
#else
const unsigned char *offset[QLZ_POINTERS];
const unsigned char *offset[QLZ_POINTERS];
#endif
} qlz_hash_decompress;
// states
typedef struct {
#if QLZ_STREAMING_BUFFER > 0
unsigned char stream_buffer[QLZ_STREAMING_BUFFER];
#endif
size_t stream_counter;
qlz_hash_compress hash[QLZ_HASH_VALUES];
unsigned char hash_counter[QLZ_HASH_VALUES];
typedef struct
{
#if QLZ_STREAMING_BUFFER > 0
unsigned char stream_buffer[QLZ_STREAMING_BUFFER];
#endif
size_t stream_counter;
qlz_hash_compress hash[QLZ_HASH_VALUES];
unsigned char hash_counter[QLZ_HASH_VALUES];
} qlz_state_compress;
#if QLZ_COMPRESSION_LEVEL == 1 || QLZ_COMPRESSION_LEVEL == 2
typedef struct {
typedef struct
{
#if QLZ_STREAMING_BUFFER > 0
unsigned char stream_buffer[QLZ_STREAMING_BUFFER];
unsigned char stream_buffer[QLZ_STREAMING_BUFFER];
#endif
qlz_hash_decompress hash[QLZ_HASH_VALUES];
unsigned char hash_counter[QLZ_HASH_VALUES];
size_t stream_counter;
} qlz_state_decompress;
qlz_hash_decompress hash[QLZ_HASH_VALUES];
unsigned char hash_counter[QLZ_HASH_VALUES];
size_t stream_counter;
} qlz_state_decompress;
#elif QLZ_COMPRESSION_LEVEL == 3
typedef struct
{
typedef struct
{
#if QLZ_STREAMING_BUFFER > 0
unsigned char stream_buffer[QLZ_STREAMING_BUFFER];
unsigned char stream_buffer[QLZ_STREAMING_BUFFER];
#endif
#if QLZ_COMPRESSION_LEVEL <= 2
qlz_hash_decompress hash[QLZ_HASH_VALUES];
qlz_hash_decompress hash[QLZ_HASH_VALUES];
#endif
size_t stream_counter;
} qlz_state_decompress;
size_t stream_counter;
} qlz_state_decompress;
#endif
@ -132,22 +121,6 @@ typedef struct
extern "C" {
#endif
#if QLZ_COMPRESSION_LEVEL == 1
#define qlz_size_decompressed qlz_size_decompressed_level1
#define qlz_size_compressed qlz_size_compressed_level1
#define qlz_compress qlz_compress_level1
#define qlz_decompress qlz_decompress_level1
#define qlz_get_setting qlz_get_setting_level1
#elif QLZ_COMPRESSION_LEVEL == 2
#define DEFINE_QLZ_FN(type, name, ...) type name##_level2(#__VA_ARGS__);
#elif QLZ_COMPRESSION_LEVEL == 3
#define qlz_size_decompressed qlz_size_decompressed_level3
#define qlz_size_compressed qlz_size_compressed_level3
#define qlz_compress qlz_compress_level3
#define qlz_decompress qlz_decompress_level3
#define qlz_get_setting qlz_get_setting_level3
#endif
// Public functions of QuickLZ
size_t qlz_size_decompressed(const char *source);
size_t qlz_size_compressed(const char *source);

View File

@ -1,843 +0,0 @@
// Fast data compression library
// Copyright (C) 2006-2011 Lasse Mikkel Reinhold
// lar@quicklz.com
//
// QuickLZ can be used for free under the GPL 1, 2 or 3 license (where anything
// released into public must be open source) or under a commercial license if such
// has been acquired (see http://www.quicklz.com/order.html). The commercial license
// does not cover derived or ported versions created by third parties under GPL.
// 1.5.0 final
#define QLZ_COMPRESSION_LEVEL 1
#define QLZ_MEMORY_SAFE
#include "QuickLZ.h"
#if QLZ_VERSION_MAJOR != 1 || QLZ_VERSION_MINOR != 5 || QLZ_VERSION_REVISION != 0
#error quicklz.c and quicklz.h have different versions
#endif
#if (defined(__X86__) || defined(__i386__) || defined(i386) || defined(_M_IX86) || defined(__386__) || defined(__x86_64__) || defined(_M_X64))
#define X86X64
#endif
#define MINOFFSET 2
#define UNCONDITIONAL_MATCHLEN 6
#define UNCOMPRESSED_END 4
#define CWORD_LEN 4
#if QLZ_COMPRESSION_LEVEL == 1 && defined QLZ_PTR_64 && QLZ_STREAMING_BUFFER == 0
#define OFFSET_BASE source
#define CAST (ui32)(size_t)
#else
#define OFFSET_BASE 0
#define CAST
#endif
int qlz_get_setting(int setting)
{
switch (setting)
{
case 0: return QLZ_COMPRESSION_LEVEL;
case 1: return sizeof(qlz_state_compress);
case 2: return sizeof(qlz_state_decompress);
case 3: return QLZ_STREAMING_BUFFER;
#ifdef QLZ_MEMORY_SAFE
case 6: return 1;
#else
case 6: return 0;
#endif
case 7: return QLZ_VERSION_MAJOR;
case 8: return QLZ_VERSION_MINOR;
case 9: return QLZ_VERSION_REVISION;
}
return -1;
}
#if QLZ_COMPRESSION_LEVEL == 1
static int same(const unsigned char *src, size_t n)
{
while(n > 0 && *(src + n) == *src)
n--;
return n == 0 ? 1 : 0;
}
#endif
static void reset_table_compress(qlz_state_compress *state)
{
int i;
for(i = 0; i < QLZ_HASH_VALUES; i++)
{
#if QLZ_COMPRESSION_LEVEL == 1
state->hash[i].offset = 0;
#else
state->hash_counter[i] = 0;
#endif
}
}
static void reset_table_decompress(qlz_state_decompress *state)
{
int i;
(void)state;
(void)i;
#if QLZ_COMPRESSION_LEVEL == 2
for(i = 0; i < QLZ_HASH_VALUES; i++)
{
state->hash_counter[i] = 0;
}
#endif
}
static __inline ui32 hash_func(ui32 i)
{
#if QLZ_COMPRESSION_LEVEL == 2
return ((i >> 9) ^ (i >> 13) ^ i) & (QLZ_HASH_VALUES - 1);
#else
return ((i >> 12) ^ i) & (QLZ_HASH_VALUES - 1);
#endif
}
static __inline ui32 fast_read(void const *src, ui32 bytes)
{
#ifndef X86X64
unsigned char *p = (unsigned char*)src;
switch (bytes)
{
case 4:
return(*p | *(p + 1) << 8 | *(p + 2) << 16 | *(p + 3) << 24);
case 3:
return(*p | *(p + 1) << 8 | *(p + 2) << 16);
case 2:
return(*p | *(p + 1) << 8);
case 1:
return(*p);
}
return 0;
#else
if (bytes >= 1 && bytes <= 4)
return *((ui32*)src);
else
return 0;
#endif
}
static __inline ui32 hashat(const unsigned char *src)
{
ui32 fetch, hash;
fetch = fast_read(src, 3);
hash = hash_func(fetch);
return hash;
}
static __inline void fast_write(ui32 f, void *dst, size_t bytes)
{
#ifndef X86X64
unsigned char *p = (unsigned char*)dst;
switch (bytes)
{
case 4:
*p = (unsigned char)f;
*(p + 1) = (unsigned char)(f >> 8);
*(p + 2) = (unsigned char)(f >> 16);
*(p + 3) = (unsigned char)(f >> 24);
return;
case 3:
*p = (unsigned char)f;
*(p + 1) = (unsigned char)(f >> 8);
*(p + 2) = (unsigned char)(f >> 16);
return;
case 2:
*p = (unsigned char)f;
*(p + 1) = (unsigned char)(f >> 8);
return;
case 1:
*p = (unsigned char)f;
return;
}
#else
switch (bytes)
{
case 4:
*((ui32*)dst) = f;
return;
case 3:
*((ui32*)dst) = f;
return;
case 2:
*((ui16 *)dst) = (ui16)f;
return;
case 1:
*((unsigned char*)dst) = (unsigned char)f;
return;
}
#endif
}
size_t qlz_size_decompressed(const char *source)
{
ui32 n, r;
n = (((*source) & 2) == 2) ? 4 : 1;
r = fast_read(source + 1 + n, n);
r = r & (0xffffffff >> ((4 - n)*8));
return r;
}
size_t qlz_size_compressed(const char *source)
{
ui32 n, r;
n = (((*source) & 2) == 2) ? 4 : 1;
r = fast_read(source + 1, n);
r = r & (0xffffffff >> ((4 - n)*8));
return r;
}
static __inline void memcpy_up(unsigned char *dst, const unsigned char *src, ui32 n)
{
// Caution if modifying memcpy_up! Overlap of dst and src must be special handled.
#ifndef X86X64
unsigned char *end = dst + n;
while(dst < end)
{
*dst = *src;
dst++;
src++;
}
#else
ui32 f = 0;
do
{
*(ui32 *)(dst + f) = *(ui32 *)(src + f);
f += MINOFFSET + 1;
}
while (f < n);
#endif
}
static __inline void update_hash(qlz_state_decompress *state, const unsigned char *s)
{
#if QLZ_COMPRESSION_LEVEL == 1
ui32 hash;
hash = hashat(s);
state->hash[hash].offset = s;
state->hash_counter[hash] = 1;
#elif QLZ_COMPRESSION_LEVEL == 2
ui32 hash;
unsigned char c;
hash = hashat(s);
c = state->hash_counter[hash];
state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = s;
c++;
state->hash_counter[hash] = c;
#endif
(void)state;
(void)s;
}
#if QLZ_COMPRESSION_LEVEL <= 2
static void update_hash_upto(qlz_state_decompress *state, unsigned char **lh, const unsigned char *max)
{
while(*lh < max)
{
(*lh)++;
update_hash(state, *lh);
}
}
#endif
static size_t qlz_compress_core(const unsigned char *source, unsigned char *destination, size_t size, qlz_state_compress *state)
{
const unsigned char *last_byte = source + size - 1;
const unsigned char *src = source;
unsigned char *cword_ptr = destination;
unsigned char *dst = destination + CWORD_LEN;
ui32 cword_val = 1U << 31;
const unsigned char *last_matchstart = last_byte - UNCONDITIONAL_MATCHLEN - UNCOMPRESSED_END;
ui32 fetch = 0;
unsigned int lits = 0;
(void) lits;
if(src <= last_matchstart)
fetch = fast_read(src, 3);
while(src <= last_matchstart)
{
if ((cword_val & 1) == 1)
{
// store uncompressed if compression ratio is too low
if (src > source + (size >> 1) && dst - destination > src - source - ((src - source) >> 5))
return 0;
fast_write((cword_val >> 1) | (1U << 31), cword_ptr, CWORD_LEN);
cword_ptr = dst;
dst += CWORD_LEN;
cword_val = 1U << 31;
fetch = fast_read(src, 3);
}
#if QLZ_COMPRESSION_LEVEL == 1
{
const unsigned char *o;
ui32 hash, cached;
hash = hash_func(fetch);
cached = fetch ^ state->hash[hash].cache;
state->hash[hash].cache = fetch;
o = state->hash[hash].offset + OFFSET_BASE;
state->hash[hash].offset = CAST(src - OFFSET_BASE);
#ifdef X86X64
if ((cached & 0xffffff) == 0 && o != OFFSET_BASE && (src - o > MINOFFSET || (src == o + 1 && lits >= 3 && src > source + 3 && same(src - 3, 6))))
{
if(cached != 0)
{
#else
if (cached == 0 && o != OFFSET_BASE && (src - o > MINOFFSET || (src == o + 1 && lits >= 3 && src > source + 3 && same(src - 3, 6))))
{
if (*(o + 3) != *(src + 3))
{
#endif
hash <<= 4;
cword_val = (cword_val >> 1) | (1U << 31);
fast_write((3 - 2) | hash, dst, 2);
src += 3;
dst += 2;
}
else
{
const unsigned char *old_src = src;
size_t matchlen;
hash <<= 4;
cword_val = (cword_val >> 1) | (1U << 31);
src += 4;
if(*(o + (src - old_src)) == *src)
{
src++;
if(*(o + (src - old_src)) == *src)
{
size_t q = last_byte - UNCOMPRESSED_END - (src - 5) + 1;
size_t remaining = q > 255 ? 255 : q;
src++;
while(*(o + (src - old_src)) == *src && (size_t)(src - old_src) < remaining)
src++;
}
}
matchlen = src - old_src;
if (matchlen < 18)
{
fast_write((ui32)(matchlen - 2) | hash, dst, 2);
dst += 2;
}
else
{
fast_write((ui32)(matchlen << 16) | hash, dst, 3);
dst += 3;
}
}
fetch = fast_read(src, 3);
lits = 0;
}
else
{
lits++;
*dst = *src;
src++;
dst++;
cword_val = (cword_val >> 1);
#ifdef X86X64
fetch = fast_read(src, 3);
#else
fetch = (fetch >> 8 & 0xffff) | (*(src + 2) << 16);
#endif
}
}
#elif QLZ_COMPRESSION_LEVEL >= 2
{
const unsigned char *o, *offset2;
ui32 hash, matchlen, k, m, best_k = 0;
unsigned char c;
size_t remaining = (last_byte - UNCOMPRESSED_END - src + 1) > 255 ? 255 : (last_byte - UNCOMPRESSED_END - src + 1);
(void)best_k;
//hash = hashat(src);
fetch = fast_read(src, 3);
hash = hash_func(fetch);
c = state->hash_counter[hash];
offset2 = state->hash[hash].offset[0];
if(offset2 < src - MINOFFSET && c > 0 && ((fast_read(offset2, 3) ^ fetch) & 0xffffff) == 0)
{
matchlen = 3;
if(*(offset2 + matchlen) == *(src + matchlen))
{
matchlen = 4;
while(*(offset2 + matchlen) == *(src + matchlen) && matchlen < remaining)
matchlen++;
}
}
else
matchlen = 0;
for(k = 1; k < QLZ_POINTERS && c > k; k++)
{
o = state->hash[hash].offset[k];
#if QLZ_COMPRESSION_LEVEL == 3
if(((fast_read(o, 3) ^ fetch) & 0xffffff) == 0 && o < src - MINOFFSET)
#elif QLZ_COMPRESSION_LEVEL == 2
if(*(src + matchlen) == *(o + matchlen) && ((fast_read(o, 3) ^ fetch) & 0xffffff) == 0 && o < src - MINOFFSET)
#endif
{
m = 3;
while(*(o + m) == *(src + m) && m < remaining)
m++;
#if QLZ_COMPRESSION_LEVEL == 3
if ((m > matchlen) || (m == matchlen && o > offset2))
#elif QLZ_COMPRESSION_LEVEL == 2
if (m > matchlen)
#endif
{
offset2 = o;
matchlen = m;
best_k = k;
}
}
}
o = offset2;
state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = src;
c++;
state->hash_counter[hash] = c;
#if QLZ_COMPRESSION_LEVEL == 3
if(matchlen > 2 && src - o < 131071)
{
ui32 u;
size_t offset = src - o;
for(u = 1; u < matchlen; u++)
{
hash = hashat(src + u);
c = state->hash_counter[hash]++;
state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = src + u;
}
cword_val = (cword_val >> 1) | (1U << 31);
src += matchlen;
if(matchlen == 3 && offset <= 63)
{
*dst = (unsigned char)(offset << 2);
dst++;
}
else if (matchlen == 3 && offset <= 16383)
{
ui32 f = (ui32)((offset << 2) | 1);
fast_write(f, dst, 2);
dst += 2;
}
else if (matchlen <= 18 && offset <= 1023)
{
ui32 f = ((matchlen - 3) << 2) | ((ui32)offset << 6) | 2;
fast_write(f, dst, 2);
dst += 2;
}
else if(matchlen <= 33)
{
ui32 f = ((matchlen - 2) << 2) | ((ui32)offset << 7) | 3;
fast_write(f, dst, 3);
dst += 3;
}
else
{
ui32 f = ((matchlen - 3) << 7) | ((ui32)offset << 15) | 3;
fast_write(f, dst, 4);
dst += 4;
}
}
else
{
*dst = *src;
src++;
dst++;
cword_val = (cword_val >> 1);
}
#elif QLZ_COMPRESSION_LEVEL == 2
if(matchlen > 2)
{
cword_val = (cword_val >> 1) | (1U << 31);
src += matchlen;
if (matchlen < 10)
{
ui32 f = best_k | ((matchlen - 2) << 2) | (hash << 5);
fast_write(f, dst, 2);
dst += 2;
}
else
{
ui32 f = best_k | (matchlen << 16) | (hash << 5);
fast_write(f, dst, 3);
dst += 3;
}
}
else
{
*dst = *src;
src++;
dst++;
cword_val = (cword_val >> 1);
}
#endif
}
#endif
}
while (src <= last_byte)
{
if ((cword_val & 1) == 1)
{
fast_write((cword_val >> 1) | (1U << 31), cword_ptr, CWORD_LEN);
cword_ptr = dst;
dst += CWORD_LEN;
cword_val = 1U << 31;
}
#if QLZ_COMPRESSION_LEVEL < 3
if (src <= last_byte - 3)
{
#if QLZ_COMPRESSION_LEVEL == 1
ui32 hash, fetch;
fetch = fast_read(src, 3);
hash = hash_func(fetch);
state->hash[hash].offset = CAST(src - OFFSET_BASE);
state->hash[hash].cache = fetch;
#elif QLZ_COMPRESSION_LEVEL == 2
ui32 hash;
unsigned char c;
hash = hashat(src);
c = state->hash_counter[hash];
state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = src;
c++;
state->hash_counter[hash] = c;
#endif
}
#endif
*dst = *src;
src++;
dst++;
cword_val = (cword_val >> 1);
}
while((cword_val & 1) != 1)
cword_val = (cword_val >> 1);
fast_write((cword_val >> 1) | (1U << 31), cword_ptr, CWORD_LEN);
// min. size must be 9 bytes so that the qlz_size functions can take 9 bytes as argument
return dst - destination < 9 ? 9 : dst - destination;
}
extern size_t qlz_size_header(const char *source);
static size_t qlz_decompress_core(const unsigned char *source, unsigned char *destination, size_t size, qlz_state_decompress *state, const unsigned char *history)
{
const unsigned char *src = source + qlz_size_header((const char *)source);
unsigned char *dst = destination;
const unsigned char *last_destination_byte = destination + size - 1;
ui32 cword_val = 1;
const unsigned char *last_matchstart = last_destination_byte - UNCONDITIONAL_MATCHLEN - UNCOMPRESSED_END;
unsigned char *last_hashed = destination - 1;
const unsigned char *last_source_byte = source + qlz_size_compressed((const char *)source) - 1;
static const ui32 bitlut[16] = {4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0};
(void) last_source_byte;
(void) last_hashed;
(void) state;
(void) history;
for(;;)
{
ui32 fetch;
if (cword_val == 1)
{
#ifdef QLZ_MEMORY_SAFE
if(src + CWORD_LEN - 1 > last_source_byte)
return 0;
#endif
cword_val = fast_read(src, CWORD_LEN);
src += CWORD_LEN;
}
#ifdef QLZ_MEMORY_SAFE
if(src + 4 - 1 > last_source_byte)
return 0;
#endif
fetch = fast_read(src, 4);
if ((cword_val & 1) == 1)
{
ui32 matchlen;
const unsigned char *offset2;
#if QLZ_COMPRESSION_LEVEL == 1
ui32 hash;
cword_val = cword_val >> 1;
hash = (fetch >> 4) & 0xfff;
offset2 = (const unsigned char *)(size_t)state->hash[hash].offset;
if((fetch & 0xf) != 0)
{
matchlen = (fetch & 0xf) + 2;
src += 2;
}
else
{
matchlen = *(src + 2);
src += 3;
}
#elif QLZ_COMPRESSION_LEVEL == 2
ui32 hash;
unsigned char c;
cword_val = cword_val >> 1;
hash = (fetch >> 5) & 0x7ff;
c = (unsigned char)(fetch & 0x3);
offset2 = state->hash[hash].offset[c];
if((fetch & (28)) != 0)
{
matchlen = ((fetch >> 2) & 0x7) + 2;
src += 2;
}
else
{
matchlen = *(src + 2);
src += 3;
}
#elif QLZ_COMPRESSION_LEVEL == 3
ui32 offset;
cword_val = cword_val >> 1;
if ((fetch & 3) == 0)
{
offset = (fetch & 0xff) >> 2;
matchlen = 3;
src++;
}
else if ((fetch & 2) == 0)
{
offset = (fetch & 0xffff) >> 2;
matchlen = 3;
src += 2;
}
else if ((fetch & 1) == 0)
{
offset = (fetch & 0xffff) >> 6;
matchlen = ((fetch >> 2) & 15) + 3;
src += 2;
}
else if ((fetch & 127) != 3)
{
offset = (fetch >> 7) & 0x1ffff;
matchlen = ((fetch >> 2) & 0x1f) + 2;
src += 3;
}
else
{
offset = (fetch >> 15);
matchlen = ((fetch >> 7) & 255) + 3;
src += 4;
}
offset2 = dst - offset;
#endif
#ifdef QLZ_MEMORY_SAFE
if(offset2 < history || offset2 > dst - MINOFFSET - 1)
return 0;
if(matchlen > (ui32)(last_destination_byte - dst - UNCOMPRESSED_END + 1))
return 0;
#endif
memcpy_up(dst, offset2, matchlen);
dst += matchlen;
#if QLZ_COMPRESSION_LEVEL <= 2
update_hash_upto(state, &last_hashed, dst - matchlen);
last_hashed = dst - 1;
#endif
}
else
{
if (dst < last_matchstart)
{
unsigned int n = bitlut[cword_val & 0xf];
#ifdef X86X64
*(ui32 *)dst = *(ui32 *)src;
#else
memcpy_up(dst, src, 4);
#endif
cword_val = cword_val >> n;
dst += n;
src += n;
#if QLZ_COMPRESSION_LEVEL <= 2
update_hash_upto(state, &last_hashed, dst - 3);
#endif
}
else
{
while(dst <= last_destination_byte)
{
if (cword_val == 1)
{
src += CWORD_LEN;
cword_val = 1U << 31;
}
#ifdef QLZ_MEMORY_SAFE
if(src >= last_source_byte + 1)
return 0;
#endif
*dst = *src;
dst++;
src++;
cword_val = cword_val >> 1;
}
#if QLZ_COMPRESSION_LEVEL <= 2
update_hash_upto(state, &last_hashed, last_destination_byte - 3); // todo, use constant
#endif
return size;
}
}
}
}
size_t qlz_compress(const void *source, char *destination, size_t size, qlz_state_compress *state)
{
size_t r;
ui32 compressed;
size_t base;
if(size == 0 || size > 0xffffffff - 400)
return 0;
if(size < 216)
base = 3;
else
base = 9;
#if QLZ_STREAMING_BUFFER > 0
if (state->stream_counter + size - 1 >= QLZ_STREAMING_BUFFER)
#endif
{
reset_table_compress(state);
r = base + qlz_compress_core((const unsigned char *)source, (unsigned char*)destination + base, size, state);
#if QLZ_STREAMING_BUFFER > 0
reset_table_compress(state);
#endif
if(r == base)
{
memcpy(destination + base, source, size);
r = size + base;
compressed = 0;
}
else
{
compressed = 1;
}
state->stream_counter = 0;
}
#if QLZ_STREAMING_BUFFER > 0
else
{
unsigned char *src = state->stream_buffer + state->stream_counter;
memcpy(src, source, size);
r = base + qlz_compress_core(src, (unsigned char*)destination + base, size, state);
if(r == base)
{
memcpy(destination + base, src, size);
r = size + base;
compressed = 0;
reset_table_compress(state);
}
else
{
compressed = 1;
}
state->stream_counter += size;
}
#endif
if(base == 3)
{
*destination = (unsigned char)(0 | compressed);
*(destination + 1) = (unsigned char)r;
*(destination + 2) = (unsigned char)size;
}
else
{
*destination = (unsigned char)(2 | compressed);
fast_write((ui32)r, destination + 1, 4);
fast_write((ui32)size, destination + 5, 4);
}
*destination |= (QLZ_COMPRESSION_LEVEL << 2);
*destination |= (1 << 6);
*destination |= ((QLZ_STREAMING_BUFFER == 0 ? 0 : (QLZ_STREAMING_BUFFER == 100000 ? 1 : (QLZ_STREAMING_BUFFER == 1000000 ? 2 : 3))) << 4);
// 76543210
// 01SSLLHC
return r;
}
size_t qlz_decompress(const char *source, void *destination, qlz_state_decompress *state)
{
size_t dsiz = qlz_size_decompressed(source);
#if QLZ_STREAMING_BUFFER > 0
if (state->stream_counter + qlz_size_decompressed(source) - 1 >= QLZ_STREAMING_BUFFER)
#endif
{
if((*source & 1) == 1)
{
reset_table_decompress(state);
dsiz = qlz_decompress_core((const unsigned char *)source, (unsigned char *)destination, dsiz, state, (const unsigned char *)destination);
}
else
{
memcpy(destination, source + qlz_size_header(source), dsiz);
}
state->stream_counter = 0;
reset_table_decompress(state);
}
#if QLZ_STREAMING_BUFFER > 0
else
{
unsigned char *dst = state->stream_buffer + state->stream_counter;
if((*source & 1) == 1)
{
dsiz = qlz_decompress_core((const unsigned char *)source, dst, dsiz, state, (const unsigned char *)state->stream_buffer);
}
else
{
memcpy(dst, source + qlz_size_header(source), dsiz);
reset_table_decompress(state);
}
memcpy(destination, dst, dsiz);
state->stream_counter += dsiz;
}
#endif
return dsiz;
}

View File

@ -1,848 +0,0 @@
// Fast data compression library
// Copyright (C) 2006-2011 Lasse Mikkel Reinhold
// lar@quicklz.com
//
// QuickLZ can be used for free under the GPL 1, 2 or 3 license (where anything
// released into public must be open source) or under a commercial license if such
// has been acquired (see http://www.quicklz.com/order.html). The commercial license
// does not cover derived or ported versions created by third parties under GPL.
// 1.5.0 final
#define QLZ_COMPRESSION_LEVEL 2
#include "QuickLZ.h"
#if QLZ_VERSION_MAJOR != 1 || QLZ_VERSION_MINOR != 5 || QLZ_VERSION_REVISION != 0
#error quicklz.c and quicklz.h have different versions
#endif
#if (defined(__X86__) || defined(__i386__) || defined(i386) || defined(_M_IX86) || defined(__386__) || defined(__x86_64__) || defined(_M_X64))
#define X86X64
#endif
#define MINOFFSET 2
#define UNCONDITIONAL_MATCHLEN 6
#define UNCOMPRESSED_END 4
#define CWORD_LEN 4
#if QLZ_COMPRESSION_LEVEL == 1 && defined QLZ_PTR_64 && QLZ_STREAMING_BUFFER == 0
#define OFFSET_BASE source
#define CAST (ui32)(size_t)
#else
#define OFFSET_BASE 0
#define CAST
#endif
int qlz_get_setting(int setting)
{
switch (setting)
{
case 0: return QLZ_COMPRESSION_LEVEL;
case 1: return sizeof(qlz_state_compress);
case 2: return sizeof(qlz_state_decompress);
case 3: return QLZ_STREAMING_BUFFER;
#ifdef QLZ_MEMORY_SAFE
case 6: return 1;
#else
case 6: return 0;
#endif
case 7: return QLZ_VERSION_MAJOR;
case 8: return QLZ_VERSION_MINOR;
case 9: return QLZ_VERSION_REVISION;
}
return -1;
}
#if QLZ_COMPRESSION_LEVEL == 1
static int same(const unsigned char *src, size_t n)
{
while(n > 0 && *(src + n) == *src)
n--;
return n == 0 ? 1 : 0;
}
#endif
static void reset_table_compress(qlz_state_compress *state)
{
int i;
for(i = 0; i < QLZ_HASH_VALUES; i++)
{
#if QLZ_COMPRESSION_LEVEL == 1
state->hash[i].offset = 0;
#else
state->hash_counter[i] = 0;
#endif
}
}
static void reset_table_decompress(qlz_state_decompress *state)
{
int i;
(void)state;
(void)i;
#if QLZ_COMPRESSION_LEVEL == 2
for(i = 0; i < QLZ_HASH_VALUES; i++)
{
state->hash_counter[i] = 0;
}
#endif
}
static __inline ui32 hash_func(ui32 i)
{
#if QLZ_COMPRESSION_LEVEL == 2
return ((i >> 9) ^ (i >> 13) ^ i) & (QLZ_HASH_VALUES - 1);
#else
return ((i >> 12) ^ i) & (QLZ_HASH_VALUES - 1);
#endif
}
static __inline ui32 fast_read(void const *src, ui32 bytes)
{
#ifndef X86X64
unsigned char *p = (unsigned char*)src;
switch (bytes)
{
case 4:
return(*p | *(p + 1) << 8 | *(p + 2) << 16 | *(p + 3) << 24);
case 3:
return(*p | *(p + 1) << 8 | *(p + 2) << 16);
case 2:
return(*p | *(p + 1) << 8);
case 1:
return(*p);
}
return 0;
#else
if (bytes >= 1 && bytes <= 4)
return *((ui32*)src);
else
return 0;
#endif
}
static __inline ui32 hashat(const unsigned char *src)
{
ui32 fetch, hash;
fetch = fast_read(src, 3);
hash = hash_func(fetch);
return hash;
}
static __inline void fast_write(ui32 f, void *dst, size_t bytes)
{
#ifndef X86X64
unsigned char *p = (unsigned char*)dst;
switch (bytes)
{
case 4:
*p = (unsigned char)f;
*(p + 1) = (unsigned char)(f >> 8);
*(p + 2) = (unsigned char)(f >> 16);
*(p + 3) = (unsigned char)(f >> 24);
return;
case 3:
*p = (unsigned char)f;
*(p + 1) = (unsigned char)(f >> 8);
*(p + 2) = (unsigned char)(f >> 16);
return;
case 2:
*p = (unsigned char)f;
*(p + 1) = (unsigned char)(f >> 8);
return;
case 1:
*p = (unsigned char)f;
return;
}
#else
switch (bytes)
{
case 4:
*((ui32*)dst) = f;
return;
case 3:
*((ui32*)dst) = f;
return;
case 2:
*((ui16 *)dst) = (ui16)f;
return;
case 1:
*((unsigned char*)dst) = (unsigned char)f;
return;
}
#endif
}
size_t qlz_size_decompressed(const char *source)
{
ui32 n, r;
n = (((*source) & 2) == 2) ? 4 : 1;
r = fast_read(source + 1 + n, n);
r = r & (0xffffffff >> ((4 - n)*8));
return r;
}
size_t qlz_size_compressed(const char *source)
{
ui32 n, r;
n = (((*source) & 2) == 2) ? 4 : 1;
r = fast_read(source + 1, n);
r = r & (0xffffffff >> ((4 - n)*8));
return r;
}
size_t qlz_size_header(const char *source)
{
size_t n = 2*((((*source) & 2) == 2) ? 4 : 1) + 1;
return n;
}
static __inline void memcpy_up(unsigned char *dst, const unsigned char *src, ui32 n)
{
// Caution if modifying memcpy_up! Overlap of dst and src must be special handled.
#ifndef X86X64
unsigned char *end = dst + n;
while(dst < end)
{
*dst = *src;
dst++;
src++;
}
#else
ui32 f = 0;
do
{
*(ui32 *)(dst + f) = *(ui32 *)(src + f);
f += MINOFFSET + 1;
}
while (f < n);
#endif
}
static __inline void update_hash(qlz_state_decompress *state, const unsigned char *s)
{
#if QLZ_COMPRESSION_LEVEL == 1
ui32 hash;
hash = hashat(s);
state->hash[hash].offset = s;
state->hash_counter[hash] = 1;
#elif QLZ_COMPRESSION_LEVEL == 2
ui32 hash;
unsigned char c;
hash = hashat(s);
c = state->hash_counter[hash];
state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = s;
c++;
state->hash_counter[hash] = c;
#endif
(void)state;
(void)s;
}
#if QLZ_COMPRESSION_LEVEL <= 2
static void update_hash_upto(qlz_state_decompress *state, unsigned char **lh, const unsigned char *max)
{
while(*lh < max)
{
(*lh)++;
update_hash(state, *lh);
}
}
#endif
static size_t qlz_compress_core(const unsigned char *source, unsigned char *destination, size_t size, qlz_state_compress *state)
{
const unsigned char *last_byte = source + size - 1;
const unsigned char *src = source;
unsigned char *cword_ptr = destination;
unsigned char *dst = destination + CWORD_LEN;
ui32 cword_val = 1U << 31;
const unsigned char *last_matchstart = last_byte - UNCONDITIONAL_MATCHLEN - UNCOMPRESSED_END;
ui32 fetch = 0;
unsigned int lits = 0;
(void) lits;
if(src <= last_matchstart)
fetch = fast_read(src, 3);
while(src <= last_matchstart)
{
if ((cword_val & 1) == 1)
{
// store uncompressed if compression ratio is too low
if (src > source + (size >> 1) && dst - destination > src - source - ((src - source) >> 5))
return 0;
fast_write((cword_val >> 1) | (1U << 31), cword_ptr, CWORD_LEN);
cword_ptr = dst;
dst += CWORD_LEN;
cword_val = 1U << 31;
fetch = fast_read(src, 3);
}
#if QLZ_COMPRESSION_LEVEL == 1
{
const unsigned char *o;
ui32 hash, cached;
hash = hash_func(fetch);
cached = fetch ^ state->hash[hash].cache;
state->hash[hash].cache = fetch;
o = state->hash[hash].offset + OFFSET_BASE;
state->hash[hash].offset = CAST(src - OFFSET_BASE);
#ifdef X86X64
if ((cached & 0xffffff) == 0 && o != OFFSET_BASE && (src - o > MINOFFSET || (src == o + 1 && lits >= 3 && src > source + 3 && same(src - 3, 6))))
{
if(cached != 0)
{
#else
if (cached == 0 && o != OFFSET_BASE && (src - o > MINOFFSET || (src == o + 1 && lits >= 3 && src > source + 3 && same(src - 3, 6))))
{
if (*(o + 3) != *(src + 3))
{
#endif
hash <<= 4;
cword_val = (cword_val >> 1) | (1U << 31);
fast_write((3 - 2) | hash, dst, 2);
src += 3;
dst += 2;
}
else
{
const unsigned char *old_src = src;
size_t matchlen;
hash <<= 4;
cword_val = (cword_val >> 1) | (1U << 31);
src += 4;
if(*(o + (src - old_src)) == *src)
{
src++;
if(*(o + (src - old_src)) == *src)
{
size_t q = last_byte - UNCOMPRESSED_END - (src - 5) + 1;
size_t remaining = q > 255 ? 255 : q;
src++;
while(*(o + (src - old_src)) == *src && (size_t)(src - old_src) < remaining)
src++;
}
}
matchlen = src - old_src;
if (matchlen < 18)
{
fast_write((ui32)(matchlen - 2) | hash, dst, 2);
dst += 2;
}
else
{
fast_write((ui32)(matchlen << 16) | hash, dst, 3);
dst += 3;
}
}
fetch = fast_read(src, 3);
lits = 0;
}
else
{
lits++;
*dst = *src;
src++;
dst++;
cword_val = (cword_val >> 1);
#ifdef X86X64
fetch = fast_read(src, 3);
#else
fetch = (fetch >> 8 & 0xffff) | (*(src + 2) << 16);
#endif
}
}
#elif QLZ_COMPRESSION_LEVEL >= 2
{
const unsigned char *o, *offset2;
ui32 hash, matchlen, k, m, best_k = 0;
unsigned char c;
size_t remaining = (last_byte - UNCOMPRESSED_END - src + 1) > 255 ? 255 : (last_byte - UNCOMPRESSED_END - src + 1);
(void)best_k;
//hash = hashat(src);
fetch = fast_read(src, 3);
hash = hash_func(fetch);
c = state->hash_counter[hash];
offset2 = state->hash[hash].offset[0];
if(offset2 < src - MINOFFSET && c > 0 && ((fast_read(offset2, 3) ^ fetch) & 0xffffff) == 0)
{
matchlen = 3;
if(*(offset2 + matchlen) == *(src + matchlen))
{
matchlen = 4;
while(*(offset2 + matchlen) == *(src + matchlen) && matchlen < remaining)
matchlen++;
}
}
else
matchlen = 0;
for(k = 1; k < QLZ_POINTERS && c > k; k++)
{
o = state->hash[hash].offset[k];
#if QLZ_COMPRESSION_LEVEL == 3
if(((fast_read(o, 3) ^ fetch) & 0xffffff) == 0 && o < src - MINOFFSET)
#elif QLZ_COMPRESSION_LEVEL == 2
if(*(src + matchlen) == *(o + matchlen) && ((fast_read(o, 3) ^ fetch) & 0xffffff) == 0 && o < src - MINOFFSET)
#endif
{
m = 3;
while(*(o + m) == *(src + m) && m < remaining)
m++;
#if QLZ_COMPRESSION_LEVEL == 3
if ((m > matchlen) || (m == matchlen && o > offset2))
#elif QLZ_COMPRESSION_LEVEL == 2
if (m > matchlen)
#endif
{
offset2 = o;
matchlen = m;
best_k = k;
}
}
}
o = offset2;
state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = src;
c++;
state->hash_counter[hash] = c;
#if QLZ_COMPRESSION_LEVEL == 3
if(matchlen > 2 && src - o < 131071)
{
ui32 u;
size_t offset = src - o;
for(u = 1; u < matchlen; u++)
{
hash = hashat(src + u);
c = state->hash_counter[hash]++;
state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = src + u;
}
cword_val = (cword_val >> 1) | (1U << 31);
src += matchlen;
if(matchlen == 3 && offset <= 63)
{
*dst = (unsigned char)(offset << 2);
dst++;
}
else if (matchlen == 3 && offset <= 16383)
{
ui32 f = (ui32)((offset << 2) | 1);
fast_write(f, dst, 2);
dst += 2;
}
else if (matchlen <= 18 && offset <= 1023)
{
ui32 f = ((matchlen - 3) << 2) | ((ui32)offset << 6) | 2;
fast_write(f, dst, 2);
dst += 2;
}
else if(matchlen <= 33)
{
ui32 f = ((matchlen - 2) << 2) | ((ui32)offset << 7) | 3;
fast_write(f, dst, 3);
dst += 3;
}
else
{
ui32 f = ((matchlen - 3) << 7) | ((ui32)offset << 15) | 3;
fast_write(f, dst, 4);
dst += 4;
}
}
else
{
*dst = *src;
src++;
dst++;
cword_val = (cword_val >> 1);
}
#elif QLZ_COMPRESSION_LEVEL == 2
if(matchlen > 2)
{
cword_val = (cword_val >> 1) | (1U << 31);
src += matchlen;
if (matchlen < 10)
{
ui32 f = best_k | ((matchlen - 2) << 2) | (hash << 5);
fast_write(f, dst, 2);
dst += 2;
}
else
{
ui32 f = best_k | (matchlen << 16) | (hash << 5);
fast_write(f, dst, 3);
dst += 3;
}
}
else
{
*dst = *src;
src++;
dst++;
cword_val = (cword_val >> 1);
}
#endif
}
#endif
}
while (src <= last_byte)
{
if ((cword_val & 1) == 1)
{
fast_write((cword_val >> 1) | (1U << 31), cword_ptr, CWORD_LEN);
cword_ptr = dst;
dst += CWORD_LEN;
cword_val = 1U << 31;
}
#if QLZ_COMPRESSION_LEVEL < 3
if (src <= last_byte - 3)
{
#if QLZ_COMPRESSION_LEVEL == 1
ui32 hash, fetch;
fetch = fast_read(src, 3);
hash = hash_func(fetch);
state->hash[hash].offset = CAST(src - OFFSET_BASE);
state->hash[hash].cache = fetch;
#elif QLZ_COMPRESSION_LEVEL == 2
ui32 hash;
unsigned char c;
hash = hashat(src);
c = state->hash_counter[hash];
state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = src;
c++;
state->hash_counter[hash] = c;
#endif
}
#endif
*dst = *src;
src++;
dst++;
cword_val = (cword_val >> 1);
}
while((cword_val & 1) != 1)
cword_val = (cword_val >> 1);
fast_write((cword_val >> 1) | (1U << 31), cword_ptr, CWORD_LEN);
// min. size must be 9 bytes so that the qlz_size functions can take 9 bytes as argument
return dst - destination < 9 ? 9 : dst - destination;
}
static size_t qlz_decompress_core(const unsigned char *source, unsigned char *destination, size_t size, qlz_state_decompress *state, const unsigned char *history)
{
const unsigned char *src = source + qlz_size_header((const char *)source);
unsigned char *dst = destination;
const unsigned char *last_destination_byte = destination + size - 1;
ui32 cword_val = 1;
const unsigned char *last_matchstart = last_destination_byte - UNCONDITIONAL_MATCHLEN - UNCOMPRESSED_END;
unsigned char *last_hashed = destination - 1;
const unsigned char *last_source_byte = source + qlz_size_compressed((const char *)source) - 1;
static const ui32 bitlut[16] = {4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0};
(void) last_source_byte;
(void) last_hashed;
(void) state;
(void) history;
for(;;)
{
ui32 fetch;
if (cword_val == 1)
{
#ifdef QLZ_MEMORY_SAFE
if(src + CWORD_LEN - 1 > last_source_byte)
return 0;
#endif
cword_val = fast_read(src, CWORD_LEN);
src += CWORD_LEN;
}
#ifdef QLZ_MEMORY_SAFE
if(src + 4 - 1 > last_source_byte)
return 0;
#endif
fetch = fast_read(src, 4);
if ((cword_val & 1) == 1)
{
ui32 matchlen;
const unsigned char *offset2;
#if QLZ_COMPRESSION_LEVEL == 1
ui32 hash;
cword_val = cword_val >> 1;
hash = (fetch >> 4) & 0xfff;
offset2 = (const unsigned char *)(size_t)state->hash[hash].offset;
if((fetch & 0xf) != 0)
{
matchlen = (fetch & 0xf) + 2;
src += 2;
}
else
{
matchlen = *(src + 2);
src += 3;
}
#elif QLZ_COMPRESSION_LEVEL == 2
ui32 hash;
unsigned char c;
cword_val = cword_val >> 1;
hash = (fetch >> 5) & 0x7ff;
c = (unsigned char)(fetch & 0x3);
offset2 = state->hash[hash].offset[c];
if((fetch & (28)) != 0)
{
matchlen = ((fetch >> 2) & 0x7) + 2;
src += 2;
}
else
{
matchlen = *(src + 2);
src += 3;
}
#elif QLZ_COMPRESSION_LEVEL == 3
ui32 offset;
cword_val = cword_val >> 1;
if ((fetch & 3) == 0)
{
offset = (fetch & 0xff) >> 2;
matchlen = 3;
src++;
}
else if ((fetch & 2) == 0)
{
offset = (fetch & 0xffff) >> 2;
matchlen = 3;
src += 2;
}
else if ((fetch & 1) == 0)
{
offset = (fetch & 0xffff) >> 6;
matchlen = ((fetch >> 2) & 15) + 3;
src += 2;
}
else if ((fetch & 127) != 3)
{
offset = (fetch >> 7) & 0x1ffff;
matchlen = ((fetch >> 2) & 0x1f) + 2;
src += 3;
}
else
{
offset = (fetch >> 15);
matchlen = ((fetch >> 7) & 255) + 3;
src += 4;
}
offset2 = dst - offset;
#endif
#ifdef QLZ_MEMORY_SAFE
if(offset2 < history || offset2 > dst - MINOFFSET - 1)
return 0;
if(matchlen > (ui32)(last_destination_byte - dst - UNCOMPRESSED_END + 1))
return 0;
#endif
memcpy_up(dst, offset2, matchlen);
dst += matchlen;
#if QLZ_COMPRESSION_LEVEL <= 2
update_hash_upto(state, &last_hashed, dst - matchlen);
last_hashed = dst - 1;
#endif
}
else
{
if (dst < last_matchstart)
{
unsigned int n = bitlut[cword_val & 0xf];
#ifdef X86X64
*(ui32 *)dst = *(ui32 *)src;
#else
memcpy_up(dst, src, 4);
#endif
cword_val = cword_val >> n;
dst += n;
src += n;
#if QLZ_COMPRESSION_LEVEL <= 2
update_hash_upto(state, &last_hashed, dst - 3);
#endif
}
else
{
while(dst <= last_destination_byte)
{
if (cword_val == 1)
{
src += CWORD_LEN;
cword_val = 1U << 31;
}
#ifdef QLZ_MEMORY_SAFE
if(src >= last_source_byte + 1)
return 0;
#endif
*dst = *src;
dst++;
src++;
cword_val = cword_val >> 1;
}
#if QLZ_COMPRESSION_LEVEL <= 2
update_hash_upto(state, &last_hashed, last_destination_byte - 3); // todo, use constant
#endif
return size;
}
}
}
}
size_t qlz_compress(const void *source, char *destination, size_t size, qlz_state_compress *state)
{
size_t r;
ui32 compressed;
size_t base;
if(size == 0 || size > 0xffffffff - 400)
return 0;
if(size < 216)
base = 3;
else
base = 9;
#if QLZ_STREAMING_BUFFER > 0
if (state->stream_counter + size - 1 >= QLZ_STREAMING_BUFFER)
#endif
{
reset_table_compress(state);
r = base + qlz_compress_core((const unsigned char *)source, (unsigned char*)destination + base, size, state);
#if QLZ_STREAMING_BUFFER > 0
reset_table_compress(state);
#endif
if(r == base)
{
memcpy(destination + base, source, size);
r = size + base;
compressed = 0;
}
else
{
compressed = 1;
}
state->stream_counter = 0;
}
#if QLZ_STREAMING_BUFFER > 0
else
{
unsigned char *src = state->stream_buffer + state->stream_counter;
memcpy(src, source, size);
r = base + qlz_compress_core(src, (unsigned char*)destination + base, size, state);
if(r == base)
{
memcpy(destination + base, src, size);
r = size + base;
compressed = 0;
reset_table_compress(state);
}
else
{
compressed = 1;
}
state->stream_counter += size;
}
#endif
if(base == 3)
{
*destination = (unsigned char)(0 | compressed);
*(destination + 1) = (unsigned char)r;
*(destination + 2) = (unsigned char)size;
}
else
{
*destination = (unsigned char)(2 | compressed);
fast_write((ui32)r, destination + 1, 4);
fast_write((ui32)size, destination + 5, 4);
}
*destination |= (QLZ_COMPRESSION_LEVEL << 2);
*destination |= (1 << 6);
*destination |= ((QLZ_STREAMING_BUFFER == 0 ? 0 : (QLZ_STREAMING_BUFFER == 100000 ? 1 : (QLZ_STREAMING_BUFFER == 1000000 ? 2 : 3))) << 4);
// 76543210
// 01SSLLHC
return r;
}
size_t qlz_decompress(const char *source, void *destination, qlz_state_decompress *state)
{
size_t dsiz = qlz_size_decompressed(source);
#if QLZ_STREAMING_BUFFER > 0
if (state->stream_counter + qlz_size_decompressed(source) - 1 >= QLZ_STREAMING_BUFFER)
#endif
{
if((*source & 1) == 1)
{
reset_table_decompress(state);
dsiz = qlz_decompress_core((const unsigned char *)source, (unsigned char *)destination, dsiz, state, (const unsigned char *)destination);
}
else
{
memcpy(destination, source + qlz_size_header(source), dsiz);
}
state->stream_counter = 0;
reset_table_decompress(state);
}
#if QLZ_STREAMING_BUFFER > 0
else
{
unsigned char *dst = state->stream_buffer + state->stream_counter;
if((*source & 1) == 1)
{
dsiz = qlz_decompress_core((const unsigned char *)source, dst, dsiz, state, (const unsigned char *)state->stream_buffer);
}
else
{
memcpy(dst, source + qlz_size_header(source), dsiz);
reset_table_decompress(state);
}
memcpy(destination, dst, dsiz);
state->stream_counter += dsiz;
}
#endif
return dsiz;
}

View File

@ -1,843 +0,0 @@
// Fast data compression library
// Copyright (C) 2006-2011 Lasse Mikkel Reinhold
// lar@quicklz.com
//
// QuickLZ can be used for free under the GPL 1, 2 or 3 license (where anything
// released into public must be open source) or under a commercial license if such
// has been acquired (see http://www.quicklz.com/order.html). The commercial license
// does not cover derived or ported versions created by third parties under GPL.
// 1.5.0 final
#define QLZ_COMPRESSION_LEVEL 3
#include "QuickLZ.h"
#if QLZ_VERSION_MAJOR != 1 || QLZ_VERSION_MINOR != 5 || QLZ_VERSION_REVISION != 0
#error quicklz.c and quicklz.h have different versions
#endif
#if (defined(__X86__) || defined(__i386__) || defined(i386) || defined(_M_IX86) || defined(__386__) || defined(__x86_64__) || defined(_M_X64))
#define X86X64
#endif
#define MINOFFSET 2
#define UNCONDITIONAL_MATCHLEN 6
#define UNCOMPRESSED_END 4
#define CWORD_LEN 4
#if QLZ_COMPRESSION_LEVEL == 1 && defined QLZ_PTR_64 && QLZ_STREAMING_BUFFER == 0
#define OFFSET_BASE source
#define CAST (ui32)(size_t)
#else
#define OFFSET_BASE 0
#define CAST
#endif
int qlz_get_setting(int setting)
{
switch (setting)
{
case 0: return QLZ_COMPRESSION_LEVEL;
case 1: return sizeof(qlz_state_compress);
case 2: return sizeof(qlz_state_decompress);
case 3: return QLZ_STREAMING_BUFFER;
#ifdef QLZ_MEMORY_SAFE
case 6: return 1;
#else
case 6: return 0;
#endif
case 7: return QLZ_VERSION_MAJOR;
case 8: return QLZ_VERSION_MINOR;
case 9: return QLZ_VERSION_REVISION;
}
return -1;
}
#if QLZ_COMPRESSION_LEVEL == 1
static int same(const unsigned char *src, size_t n)
{
while(n > 0 && *(src + n) == *src)
n--;
return n == 0 ? 1 : 0;
}
#endif
static void reset_table_compress(qlz_state_compress *state)
{
int i;
for(i = 0; i < QLZ_HASH_VALUES; i++)
{
#if QLZ_COMPRESSION_LEVEL == 1
state->hash[i].offset = 0;
#else
state->hash_counter[i] = 0;
#endif
}
}
static void reset_table_decompress(qlz_state_decompress *state)
{
int i;
(void)state;
(void)i;
#if QLZ_COMPRESSION_LEVEL == 2
for(i = 0; i < QLZ_HASH_VALUES; i++)
{
state->hash_counter[i] = 0;
}
#endif
}
static __inline ui32 hash_func(ui32 i)
{
#if QLZ_COMPRESSION_LEVEL == 2
return ((i >> 9) ^ (i >> 13) ^ i) & (QLZ_HASH_VALUES - 1);
#else
return ((i >> 12) ^ i) & (QLZ_HASH_VALUES - 1);
#endif
}
static __inline ui32 fast_read(void const *src, ui32 bytes)
{
#ifndef X86X64
unsigned char *p = (unsigned char*)src;
switch (bytes)
{
case 4:
return(*p | *(p + 1) << 8 | *(p + 2) << 16 | *(p + 3) << 24);
case 3:
return(*p | *(p + 1) << 8 | *(p + 2) << 16);
case 2:
return(*p | *(p + 1) << 8);
case 1:
return(*p);
}
return 0;
#else
if (bytes >= 1 && bytes <= 4)
return *((ui32*)src);
else
return 0;
#endif
}
static __inline ui32 hashat(const unsigned char *src)
{
ui32 fetch, hash;
fetch = fast_read(src, 3);
hash = hash_func(fetch);
return hash;
}
static __inline void fast_write(ui32 f, void *dst, size_t bytes)
{
#ifndef X86X64
unsigned char *p = (unsigned char*)dst;
switch (bytes)
{
case 4:
*p = (unsigned char)f;
*(p + 1) = (unsigned char)(f >> 8);
*(p + 2) = (unsigned char)(f >> 16);
*(p + 3) = (unsigned char)(f >> 24);
return;
case 3:
*p = (unsigned char)f;
*(p + 1) = (unsigned char)(f >> 8);
*(p + 2) = (unsigned char)(f >> 16);
return;
case 2:
*p = (unsigned char)f;
*(p + 1) = (unsigned char)(f >> 8);
return;
case 1:
*p = (unsigned char)f;
return;
}
#else
switch (bytes)
{
case 4:
*((ui32*)dst) = f;
return;
case 3:
*((ui32*)dst) = f;
return;
case 2:
*((ui16 *)dst) = (ui16)f;
return;
case 1:
*((unsigned char*)dst) = (unsigned char)f;
return;
}
#endif
}
size_t qlz_size_decompressed(const char *source)
{
ui32 n, r;
n = (((*source) & 2) == 2) ? 4 : 1;
r = fast_read(source + 1 + n, n);
r = r & (0xffffffff >> ((4 - n)*8));
return r;
}
size_t qlz_size_compressed(const char *source)
{
ui32 n, r;
n = (((*source) & 2) == 2) ? 4 : 1;
r = fast_read(source + 1, n);
r = r & (0xffffffff >> ((4 - n)*8));
return r;
}
static __inline void memcpy_up(unsigned char *dst, const unsigned char *src, ui32 n)
{
// Caution if modifying memcpy_up! Overlap of dst and src must be special handled.
#ifndef X86X64
unsigned char *end = dst + n;
while(dst < end)
{
*dst = *src;
dst++;
src++;
}
#else
ui32 f = 0;
do
{
*(ui32 *)(dst + f) = *(ui32 *)(src + f);
f += MINOFFSET + 1;
}
while (f < n);
#endif
}
static __inline void update_hash(qlz_state_decompress *state, const unsigned char *s)
{
#if QLZ_COMPRESSION_LEVEL == 1
ui32 hash;
hash = hashat(s);
state->hash[hash].offset = s;
state->hash_counter[hash] = 1;
#elif QLZ_COMPRESSION_LEVEL == 2
ui32 hash;
unsigned char c;
hash = hashat(s);
c = state->hash_counter[hash];
state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = s;
c++;
state->hash_counter[hash] = c;
#endif
(void)state;
(void)s;
}
#if QLZ_COMPRESSION_LEVEL <= 2
static void update_hash_upto(qlz_state_decompress *state, unsigned char **lh, const unsigned char *max)
{
while(*lh < max)
{
(*lh)++;
update_hash(state, *lh);
}
}
#endif
static size_t qlz_compress_core(const unsigned char *source, unsigned char *destination, size_t size, qlz_state_compress *state)
{
const unsigned char *last_byte = source + size - 1;
const unsigned char *src = source;
unsigned char *cword_ptr = destination;
unsigned char *dst = destination + CWORD_LEN;
ui32 cword_val = 1U << 31;
const unsigned char *last_matchstart = last_byte - UNCONDITIONAL_MATCHLEN - UNCOMPRESSED_END;
ui32 fetch = 0;
unsigned int lits = 0;
(void) lits;
if(src <= last_matchstart)
fetch = fast_read(src, 3);
while(src <= last_matchstart)
{
if ((cword_val & 1) == 1)
{
// store uncompressed if compression ratio is too low
if (src > source + (size >> 1) && dst - destination > src - source - ((src - source) >> 5))
return 0;
fast_write((cword_val >> 1) | (1U << 31), cword_ptr, CWORD_LEN);
cword_ptr = dst;
dst += CWORD_LEN;
cword_val = 1U << 31;
fetch = fast_read(src, 3);
}
#if QLZ_COMPRESSION_LEVEL == 1
{
const unsigned char *o;
ui32 hash, cached;
hash = hash_func(fetch);
cached = fetch ^ state->hash[hash].cache;
state->hash[hash].cache = fetch;
o = state->hash[hash].offset + OFFSET_BASE;
state->hash[hash].offset = CAST(src - OFFSET_BASE);
#ifdef X86X64
if ((cached & 0xffffff) == 0 && o != OFFSET_BASE && (src - o > MINOFFSET || (src == o + 1 && lits >= 3 && src > source + 3 && same(src - 3, 6))))
{
if(cached != 0)
{
#else
if (cached == 0 && o != OFFSET_BASE && (src - o > MINOFFSET || (src == o + 1 && lits >= 3 && src > source + 3 && same(src - 3, 6))))
{
if (*(o + 3) != *(src + 3))
{
#endif
hash <<= 4;
cword_val = (cword_val >> 1) | (1U << 31);
fast_write((3 - 2) | hash, dst, 2);
src += 3;
dst += 2;
}
else
{
const unsigned char *old_src = src;
size_t matchlen;
hash <<= 4;
cword_val = (cword_val >> 1) | (1U << 31);
src += 4;
if(*(o + (src - old_src)) == *src)
{
src++;
if(*(o + (src - old_src)) == *src)
{
size_t q = last_byte - UNCOMPRESSED_END - (src - 5) + 1;
size_t remaining = q > 255 ? 255 : q;
src++;
while(*(o + (src - old_src)) == *src && (size_t)(src - old_src) < remaining)
src++;
}
}
matchlen = src - old_src;
if (matchlen < 18)
{
fast_write((ui32)(matchlen - 2) | hash, dst, 2);
dst += 2;
}
else
{
fast_write((ui32)(matchlen << 16) | hash, dst, 3);
dst += 3;
}
}
fetch = fast_read(src, 3);
lits = 0;
}
else
{
lits++;
*dst = *src;
src++;
dst++;
cword_val = (cword_val >> 1);
#ifdef X86X64
fetch = fast_read(src, 3);
#else
fetch = (fetch >> 8 & 0xffff) | (*(src + 2) << 16);
#endif
}
}
#elif QLZ_COMPRESSION_LEVEL >= 2
{
const unsigned char *o, *offset2;
ui32 hash, matchlen, k, m, best_k = 0;
unsigned char c;
size_t remaining = (last_byte - UNCOMPRESSED_END - src + 1) > 255 ? 255 : (last_byte - UNCOMPRESSED_END - src + 1);
(void)best_k;
//hash = hashat(src);
fetch = fast_read(src, 3);
hash = hash_func(fetch);
c = state->hash_counter[hash];
offset2 = state->hash[hash].offset[0];
if(offset2 < src - MINOFFSET && c > 0 && ((fast_read(offset2, 3) ^ fetch) & 0xffffff) == 0)
{
matchlen = 3;
if(*(offset2 + matchlen) == *(src + matchlen))
{
matchlen = 4;
while(*(offset2 + matchlen) == *(src + matchlen) && matchlen < remaining)
matchlen++;
}
}
else
matchlen = 0;
for(k = 1; k < QLZ_POINTERS && c > k; k++)
{
o = state->hash[hash].offset[k];
#if QLZ_COMPRESSION_LEVEL == 3
if(((fast_read(o, 3) ^ fetch) & 0xffffff) == 0 && o < src - MINOFFSET)
#elif QLZ_COMPRESSION_LEVEL == 2
if(*(src + matchlen) == *(o + matchlen) && ((fast_read(o, 3) ^ fetch) & 0xffffff) == 0 && o < src - MINOFFSET)
#endif
{
m = 3;
while(*(o + m) == *(src + m) && m < remaining)
m++;
#if QLZ_COMPRESSION_LEVEL == 3
if ((m > matchlen) || (m == matchlen && o > offset2))
#elif QLZ_COMPRESSION_LEVEL == 2
if (m > matchlen)
#endif
{
offset2 = o;
matchlen = m;
best_k = k;
}
}
}
o = offset2;
state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = src;
c++;
state->hash_counter[hash] = c;
#if QLZ_COMPRESSION_LEVEL == 3
if(matchlen > 2 && src - o < 131071)
{
ui32 u;
size_t offset = src - o;
for(u = 1; u < matchlen; u++)
{
hash = hashat(src + u);
c = state->hash_counter[hash]++;
state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = src + u;
}
cword_val = (cword_val >> 1) | (1U << 31);
src += matchlen;
if(matchlen == 3 && offset <= 63)
{
*dst = (unsigned char)(offset << 2);
dst++;
}
else if (matchlen == 3 && offset <= 16383)
{
ui32 f = (ui32)((offset << 2) | 1);
fast_write(f, dst, 2);
dst += 2;
}
else if (matchlen <= 18 && offset <= 1023)
{
ui32 f = ((matchlen - 3) << 2) | ((ui32)offset << 6) | 2;
fast_write(f, dst, 2);
dst += 2;
}
else if(matchlen <= 33)
{
ui32 f = ((matchlen - 2) << 2) | ((ui32)offset << 7) | 3;
fast_write(f, dst, 3);
dst += 3;
}
else
{
ui32 f = ((matchlen - 3) << 7) | ((ui32)offset << 15) | 3;
fast_write(f, dst, 4);
dst += 4;
}
}
else
{
*dst = *src;
src++;
dst++;
cword_val = (cword_val >> 1);
}
#elif QLZ_COMPRESSION_LEVEL == 2
if(matchlen > 2)
{
cword_val = (cword_val >> 1) | (1U << 31);
src += matchlen;
if (matchlen < 10)
{
ui32 f = best_k | ((matchlen - 2) << 2) | (hash << 5);
fast_write(f, dst, 2);
dst += 2;
}
else
{
ui32 f = best_k | (matchlen << 16) | (hash << 5);
fast_write(f, dst, 3);
dst += 3;
}
}
else
{
*dst = *src;
src++;
dst++;
cword_val = (cword_val >> 1);
}
#endif
}
#endif
}
while (src <= last_byte)
{
if ((cword_val & 1) == 1)
{
fast_write((cword_val >> 1) | (1U << 31), cword_ptr, CWORD_LEN);
cword_ptr = dst;
dst += CWORD_LEN;
cword_val = 1U << 31;
}
#if QLZ_COMPRESSION_LEVEL < 3
if (src <= last_byte - 3)
{
#if QLZ_COMPRESSION_LEVEL == 1
ui32 hash, fetch;
fetch = fast_read(src, 3);
hash = hash_func(fetch);
state->hash[hash].offset = CAST(src - OFFSET_BASE);
state->hash[hash].cache = fetch;
#elif QLZ_COMPRESSION_LEVEL == 2
ui32 hash;
unsigned char c;
hash = hashat(src);
c = state->hash_counter[hash];
state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = src;
c++;
state->hash_counter[hash] = c;
#endif
}
#endif
*dst = *src;
src++;
dst++;
cword_val = (cword_val >> 1);
}
while((cword_val & 1) != 1)
cword_val = (cword_val >> 1);
fast_write((cword_val >> 1) | (1U << 31), cword_ptr, CWORD_LEN);
// min. size must be 9 bytes so that the qlz_size functions can take 9 bytes as argument
return dst - destination < 9 ? 9 : dst - destination;
}
extern size_t qlz_size_header(const char *source);
static size_t qlz_decompress_core(const unsigned char *source, unsigned char *destination, size_t size, qlz_state_decompress *state, const unsigned char *history)
{
const unsigned char *src = source + qlz_size_header((const char *)source);
unsigned char *dst = destination;
const unsigned char *last_destination_byte = destination + size - 1;
ui32 cword_val = 1;
const unsigned char *last_matchstart = last_destination_byte - UNCONDITIONAL_MATCHLEN - UNCOMPRESSED_END;
unsigned char *last_hashed = destination - 1;
const unsigned char *last_source_byte = source + qlz_size_compressed((const char *)source) - 1;
static const ui32 bitlut[16] = {4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0};
(void) last_source_byte;
(void) last_hashed;
(void) state;
(void) history;
for(;;)
{
ui32 fetch;
if (cword_val == 1)
{
#ifdef QLZ_MEMORY_SAFE
if(src + CWORD_LEN - 1 > last_source_byte)
return 0;
#endif
cword_val = fast_read(src, CWORD_LEN);
src += CWORD_LEN;
}
#ifdef QLZ_MEMORY_SAFE
if(src + 4 - 1 > last_source_byte)
return 0;
#endif
fetch = fast_read(src, 4);
if ((cword_val & 1) == 1)
{
ui32 matchlen;
const unsigned char *offset2;
#if QLZ_COMPRESSION_LEVEL == 1
ui32 hash;
cword_val = cword_val >> 1;
hash = (fetch >> 4) & 0xfff;
offset2 = (const unsigned char *)(size_t)state->hash[hash].offset;
if((fetch & 0xf) != 0)
{
matchlen = (fetch & 0xf) + 2;
src += 2;
}
else
{
matchlen = *(src + 2);
src += 3;
}
#elif QLZ_COMPRESSION_LEVEL == 2
ui32 hash;
unsigned char c;
cword_val = cword_val >> 1;
hash = (fetch >> 5) & 0x7ff;
c = (unsigned char)(fetch & 0x3);
offset2 = state->hash[hash].offset[c];
if((fetch & (28)) != 0)
{
matchlen = ((fetch >> 2) & 0x7) + 2;
src += 2;
}
else
{
matchlen = *(src + 2);
src += 3;
}
#elif QLZ_COMPRESSION_LEVEL == 3
ui32 offset;
cword_val = cword_val >> 1;
if ((fetch & 3) == 0)
{
offset = (fetch & 0xff) >> 2;
matchlen = 3;
src++;
}
else if ((fetch & 2) == 0)
{
offset = (fetch & 0xffff) >> 2;
matchlen = 3;
src += 2;
}
else if ((fetch & 1) == 0)
{
offset = (fetch & 0xffff) >> 6;
matchlen = ((fetch >> 2) & 15) + 3;
src += 2;
}
else if ((fetch & 127) != 3)
{
offset = (fetch >> 7) & 0x1ffff;
matchlen = ((fetch >> 2) & 0x1f) + 2;
src += 3;
}
else
{
offset = (fetch >> 15);
matchlen = ((fetch >> 7) & 255) + 3;
src += 4;
}
offset2 = dst - offset;
#endif
#ifdef QLZ_MEMORY_SAFE
if(offset2 < history || offset2 > dst - MINOFFSET - 1)
return 0;
if(matchlen > (ui32)(last_destination_byte - dst - UNCOMPRESSED_END + 1))
return 0;
#endif
memcpy_up(dst, offset2, matchlen);
dst += matchlen;
#if QLZ_COMPRESSION_LEVEL <= 2
update_hash_upto(state, &last_hashed, dst - matchlen);
last_hashed = dst - 1;
#endif
}
else
{
if (dst < last_matchstart)
{
unsigned int n = bitlut[cword_val & 0xf];
#ifdef X86X64
*(ui32 *)dst = *(ui32 *)src;
#else
memcpy_up(dst, src, 4);
#endif
cword_val = cword_val >> n;
dst += n;
src += n;
#if QLZ_COMPRESSION_LEVEL <= 2
update_hash_upto(state, &last_hashed, dst - 3);
#endif
}
else
{
while(dst <= last_destination_byte)
{
if (cword_val == 1)
{
src += CWORD_LEN;
cword_val = 1U << 31;
}
#ifdef QLZ_MEMORY_SAFE
if(src >= last_source_byte + 1)
return 0;
#endif
*dst = *src;
dst++;
src++;
cword_val = cword_val >> 1;
}
#if QLZ_COMPRESSION_LEVEL <= 2
update_hash_upto(state, &last_hashed, last_destination_byte - 3); // todo, use constant
#endif
return size;
}
}
}
}
size_t qlz_compress(const void *source, char *destination, size_t size, qlz_state_compress *state)
{
size_t r;
ui32 compressed;
size_t base;
if(size == 0 || size > 0xffffffff - 400)
return 0;
if(size < 216)
base = 3;
else
base = 9;
#if QLZ_STREAMING_BUFFER > 0
if (state->stream_counter + size - 1 >= QLZ_STREAMING_BUFFER)
#endif
{
reset_table_compress(state);
r = base + qlz_compress_core((const unsigned char *)source, (unsigned char*)destination + base, size, state);
#if QLZ_STREAMING_BUFFER > 0
reset_table_compress(state);
#endif
if(r == base)
{
memcpy(destination + base, source, size);
r = size + base;
compressed = 0;
}
else
{
compressed = 1;
}
state->stream_counter = 0;
}
#if QLZ_STREAMING_BUFFER > 0
else
{
unsigned char *src = state->stream_buffer + state->stream_counter;
memcpy(src, source, size);
r = base + qlz_compress_core(src, (unsigned char*)destination + base, size, state);
if(r == base)
{
memcpy(destination + base, src, size);
r = size + base;
compressed = 0;
reset_table_compress(state);
}
else
{
compressed = 1;
}
state->stream_counter += size;
}
#endif
if(base == 3)
{
*destination = (unsigned char)(0 | compressed);
*(destination + 1) = (unsigned char)r;
*(destination + 2) = (unsigned char)size;
}
else
{
*destination = (unsigned char)(2 | compressed);
fast_write((ui32)r, destination + 1, 4);
fast_write((ui32)size, destination + 5, 4);
}
*destination |= (QLZ_COMPRESSION_LEVEL << 2);
*destination |= (1 << 6);
*destination |= ((QLZ_STREAMING_BUFFER == 0 ? 0 : (QLZ_STREAMING_BUFFER == 100000 ? 1 : (QLZ_STREAMING_BUFFER == 1000000 ? 2 : 3))) << 4);
// 76543210
// 01SSLLHC
return r;
}
size_t qlz_decompress(const char *source, void *destination, qlz_state_decompress *state)
{
size_t dsiz = qlz_size_decompressed(source);
#if QLZ_STREAMING_BUFFER > 0
if (state->stream_counter + qlz_size_decompressed(source) - 1 >= QLZ_STREAMING_BUFFER)
#endif
{
if((*source & 1) == 1)
{
reset_table_decompress(state);
dsiz = qlz_decompress_core((const unsigned char *)source, (unsigned char *)destination, dsiz, state, (const unsigned char *)destination);
}
else
{
memcpy(destination, source + qlz_size_header(source), dsiz);
}
state->stream_counter = 0;
reset_table_decompress(state);
}
#if QLZ_STREAMING_BUFFER > 0
else
{
unsigned char *dst = state->stream_buffer + state->stream_counter;
if((*source & 1) == 1)
{
dsiz = qlz_decompress_core((const unsigned char *)source, dst, dsiz, state, (const unsigned char *)state->stream_buffer);
}
else
{
memcpy(dst, source + qlz_size_header(source), dsiz);
reset_table_decompress(state);
}
memcpy(destination, dst, dsiz);
state->stream_counter += dsiz;
}
#endif
return dsiz;
}

View File

@ -78,39 +78,37 @@ namespace ts {
}
*/
Command Command::parse(const pipes::buffer_view &buffer, bool expect_type, bool drop_non_utf8) {
string_view data{buffer.data_ptr<const char>(), buffer.length()};
Command Command::parse(const std::string_view& command_data, bool expect_type, bool drop_non_utf8) {
Command result;
size_t current_index = std::string::npos, end_index;
if(expect_type) {
current_index = data.find(' ', 0);
current_index = command_data.find(' ', 0);
if(current_index == std::string::npos){
result._command = std::string(data);
result._command = std::string{command_data};
return result;
} else {
result._command = std::string(data.substr(0, current_index));
result._command = std::string{command_data.substr(0, current_index)};
}
}
size_t bulk_index = 0;
while(++current_index > 0 || (current_index == 0 && !expect_type && (expect_type = true))) {
end_index = data.find_first_of(" |", current_index);
end_index = command_data.find_first_of(" |", current_index);
if(end_index != current_index && current_index < data.length()) { /* else we've found another space or a pipe */
if(data[current_index] == '-') {
string trigger(data.substr(current_index + 1, end_index - current_index - 1));
if(end_index != current_index && current_index < command_data.length()) { /* else we've found another space or a pipe */
if(command_data[current_index] == '-') {
string trigger(command_data.substr(current_index + 1, end_index - current_index - 1));
result.paramethers.push_back(trigger);
} else {
auto index_assign = data.find_first_of('=', current_index);
auto index_assign = command_data.find_first_of('=', current_index);
string key, value;
if(index_assign == string::npos || index_assign > end_index) {
key = data.substr(current_index, end_index - current_index);
key = command_data.substr(current_index, end_index - current_index);
} else {
key = data.substr(current_index, index_assign - current_index);
key = command_data.substr(current_index, index_assign - current_index);
try {
value = query::unescape(string(data.substr(index_assign + 1, end_index - index_assign - 1)), true);
value = query::unescape(string(command_data.substr(index_assign + 1, end_index - index_assign - 1)), true);
} catch(const std::invalid_argument& ex) {
(void) ex;
@ -134,7 +132,7 @@ namespace ts {
}
}
if(end_index < data.length() && data[end_index] == '|')
if(end_index < command_data.length() && command_data[end_index] == '|')
bulk_index++;
current_index = end_index;
}
@ -196,27 +194,6 @@ namespace ts {
return str;
}
#ifdef HAVE_JSON
Json::Value Command::buildJson() const {
Json::Value result;
result["command"] = this->_command;
int index = 0;
for(auto it = this->bulks.begin(); it != this->bulks.end(); it++){
Json::Value& node = result["data"][index++];
for(const auto& elm : it->parms)
node[elm.key()] = elm.value();
}
Json::Value& triggers = result["triggers"];
index = 0;
for(const auto& parm : this->paramethers)
triggers[index++] = parm;
return result;
}
#endif
std::deque<std::string> Command::parms() { return this->paramethers; }
bool Command::hasParm(std::string parm) { return std::find(this->paramethers.begin(), this->paramethers.end(), parm) != this->paramethers.end(); }

View File

@ -1,7 +1,6 @@
#pragma once
#ifdef byte
#define byte asdd
#ifndef WIN32
#warning The byte macro is already defined! Undefining it!
#endif
@ -14,25 +13,23 @@
#include <list>
#include <deque>
#include <memory>
#include <optional>
#include <pipes/buffer.h>
#include "../Variable.h"
#ifdef HAVE_JSON
#include <json/json.h>
#endif
namespace ts {
#define PARM_TYPE(type, fromString, toString) \
BaseCommandParm(std::string key, type value) : BaseCommandParm(std::pair<std::string, std::string>(key, "")) {\
toString; \
} \
BaseCommandParm& operator=(type value){ \
toString; \
return *this; \
} \
\
operator type(){ \
fromString; \
#define PARM_TYPE(type, fromString, toString) \
BaseCommandParm(std::string key, type value) : BaseCommandParm(std::pair<std::string, std::string>(key, "")) { \
toString; \
} \
\
BaseCommandParm& operator=(type value){ \
toString; \
return *this; \
} \
\
operator type() { \
fromString; \
}
class Command;
@ -47,7 +44,6 @@ operator type(){ \
public:
ParameterBulk(const ParameterBulk& ref) : parms(ref.parms) {}
variable operator[](size_t index){
if(parms.size() > index) return parms[index];
return variable{"", "", VARTYPE_NULL};
@ -101,19 +97,36 @@ operator type(){ \
ValueList(const ValueList& ref) : key(ref.key), bulkList(ref.bulkList) {}
variable operator[](int index){
while(index >= bulkList.size()) bulkList.push_back(ParameterBulk());
while(index >= bulkList.size()) {
bulkList.push_back(ParameterBulk{});
}
return bulkList[index][key];
}
variable first() const {
int index = 0;
while(index < bulkList.size() && !bulkList[index].has(key)) index++;
if(index < bulkList.size()) return bulkList[index][key];
auto value = this->optional_first();
if(value.has_value()) {
return std::move(*value);
}
return variable{this->key, "", VariableType::VARTYPE_NULL};
throw std::invalid_argument("could not find key [" + key + "]");
}
std::optional<variable> optional_first() const {
int index = 0;
while(index < bulkList.size() && !bulkList[index].has(key)) {
index++;
}
if(index < bulkList.size()) {
return std::make_optional(bulkList[index][key]);
}
return std::nullopt;
}
size_t size(){
size_t count = 0;
for(const auto& blk : this->bulkList)
@ -139,6 +152,15 @@ operator type(){ \
std::string string() const { return as<std::string>(); }
std::optional<std::string> optional_string() const {
auto value = this->optional_first();
if(value.has_value()) {
return std::make_optional(value->string());
}
return std::nullopt;
}
friend std::ostream& operator<<(std::ostream&, const ValueList&);
private:
ValueList(std::string key, std::deque<ParameterBulk>& bulkList) : key{std::move(key)}, bulkList(bulkList) {}
@ -161,7 +183,7 @@ operator type(){ \
class Command {
public:
static Command parse(const pipes::buffer_view& buffer, bool expect_command = true, bool drop_non_utf8 = false);
static Command parse(const std::string_view& command_data, bool expect_command = true, bool drop_non_utf8 = false);
explicit Command(const std::string& command);
explicit Command(const std::string& command, std::initializer_list<variable>);
@ -175,10 +197,6 @@ operator type(){ \
[[nodiscard]] std::string build(bool escaped = true) const;
#ifdef HAVE_JSON
Json::Value buildJson() const;
#endif
const ParameterBulk& operator[](size_t index) const {
if(bulks.size() <= index) throw std::invalid_argument("got out of length");
return bulks[index];

View File

@ -7,17 +7,14 @@
using namespace ts;
command_bulk command_parser::empty_bulk{std::string::npos, 0, ""};
bool command_parser::parse(bool command) {
this->data = this->_command;
this->index = std::string::npos;
this->bulk_index = std::string::npos;
size_t index{0}, findex;
if(command) {
index = this->_command.find(' ', index);
index = this->data.find(' ', index);
if(index == std::string::npos) {
this->command_type = this->_command;
this->command_type = this->data;
return true;
} else {
this->command_type = this->data.substr(0, index);
@ -26,11 +23,11 @@ bool command_parser::parse(bool command) {
index++;
}
this->_bulks.reserve(4);
while(index < this->_command.size()) {
findex = this->_command.find('|', index);
if(findex == std::string::npos)
findex = this->_command.size();
while(index < this->data.size()) {
findex = this->data.find('|', index);
if(findex == std::string::npos) {
findex = this->data.size();
}
this->_bulks.emplace_back(this->_bulks.size() - 1, index, this->data.substr(index, findex - index));
index = findex + 1;
@ -38,6 +35,15 @@ bool command_parser::parse(bool command) {
return true;
}
std::string_view command_parser::payload_view(size_t bulk_index) const noexcept {
if(bulk_index >= this->bulk_count()) {
return {};
}
auto bulk = this->bulk(bulk_index);
return this->data.substr(bulk.command_character_index());
}
std::optional<size_t> command_parser::next_bulk_containing(const std::string_view &key, size_t start) const {
if(start >= this->bulk_count()) return std::nullopt;
@ -46,8 +52,10 @@ std::optional<size_t> command_parser::next_bulk_containing(const std::string_vie
if(next == std::string::npos) return std::nullopt;
size_t upper_bulk{start + 1};
for(; upper_bulk < this->bulk_count(); upper_bulk++)
if(this->bulk(upper_bulk).command_character_index() > next)
for(; upper_bulk < this->bulk_count(); upper_bulk++) {
if(this->bulk(upper_bulk).command_character_index() > next) {
break;
}
}
return upper_bulk - 1;
}

View File

@ -3,6 +3,7 @@
#include <string>
#include <cstddef>
#include <vector>
#include <deque>
#include <optional>
#include <string_view>
@ -57,7 +58,7 @@ namespace ts {
[[nodiscard]] inline std::string_view value_raw(const std::string_view& key) const {
bool tmp;
auto result = this->value_raw(key, tmp);
if(!tmp) throw command_value_missing_exception{index, std::string{key}};
if(!tmp) throw command_value_missing_exception{this->bulk_index, std::string{key}};
return result;
}
@ -88,7 +89,25 @@ namespace ts {
static_assert(converter<T>::supported, "Target type isn't supported!");
static_assert(!converter<T>::supported || converter<T>::from_string_view, "Target type dosn't support parsing");
return converter<T>::from_string_view(this->value(key));
auto value = this->value(key);
try {
return converter<T>::from_string_view(value);
} catch (std::exception& ex) {
throw command_value_cast_failed{this->key_command_character_index(key), std::string{key}, value, typeid(T)};
}
}
template <typename T>
inline void expect_value_as(const std::string_view& key) const {
static_assert(converter<T>::supported, "Target type isn't supported!");
static_assert(!converter<T>::supported || converter<T>::from_string_view, "Target type dosn't support parsing");
auto value = this->value(key);
try {
converter<T>::from_string_view(value);
} catch (std::exception& ex) {
throw command_value_cast_failed{this->key_command_character_index(key), std::string{key}, value, typeid(T)};
}
}
[[nodiscard]] inline size_t command_character_index() const { return this->abs_index; }
@ -98,10 +117,10 @@ namespace ts {
return this->abs_index + begin;
}
protected:
command_string_parser(size_t index, size_t abs_index, std::string_view data) : index{index}, abs_index{abs_index}, data{data} {}
command_string_parser(size_t bulk_index, size_t abs_index, std::string_view data) : bulk_index{bulk_index}, abs_index{abs_index}, data{data} {}
size_t abs_index{};
size_t index{};
size_t bulk_index{};
std::string_view data{};
};
}
@ -141,14 +160,17 @@ namespace ts {
failed
};
explicit command_parser(std::string command) : impl::command_string_parser{std::string::npos, 0, command}, _command{std::move(command)} { }
explicit command_parser(std::string_view command) : impl::command_string_parser{std::string::npos, 0, command} { }
explicit command_parser(std::string command) : impl::command_string_parser{std::string::npos, 0, command}, _command_memory{std::move(command)} { }
bool parse(bool /* contains identifier */);
[[nodiscard]] inline const std::string_view& identifier() const noexcept { return this->command_type; }
[[nodiscard]] inline size_t bulk_count() const noexcept { return this->_bulks.size(); }
[[nodiscard]] inline const command_bulk& bulk(size_t index) const noexcept {
if(this->_bulks.size() <= index) return empty_bulk;
[[nodiscard]] inline const command_bulk& bulk(size_t index) const {
if(this->_bulks.size() <= index)
throw command_bulk_index_out_of_bounds_exception{index};
return this->_bulks[index];
}
@ -158,22 +180,39 @@ namespace ts {
size_t index{0};
do {
index = this->data.find(key, index);
index -= 1;
if(index > key.size()) return false;
if(this->data[index] == '-') return index == 0 || this->data[index - 1] == ' ';
index--;
if(index > this->data.length())
return false; /* index was zero or std::string::npos*/
if(this->data[index] != '-') {
index += 2;
continue;
}
if(index != 0 && this->data[index - 1] != ' ') {
index += 2;
continue;
}
if(index + 1 + key.length() >= this->data.length())
return true;
if(this->data[index + 1 + key.length()] == ' ')
return true;
index += 2;
} while(true);
}
[[nodiscard]] const std::vector<command_bulk>& bulks() const { return this->_bulks; }
[[nodiscard]] const std::deque<command_bulk>& bulks() const { return this->_bulks; }
[[nodiscard]] std::string_view payload_view(size_t bulk_index) const noexcept;
[[nodiscard]] std::optional<size_t> next_bulk_containing(const std::string_view& /* key */, size_t /* bulk offset */) const;
private:
static command_bulk empty_bulk;
const std::string _command{};
const std::string _command_memory{};
std::string_view command_type{};
std::vector<command_bulk> _bulks{};
std::deque<command_bulk> _bulks{};
};
class command_builder_bulk {
@ -181,18 +220,22 @@ namespace ts {
friend class command_builder_impl;
public:
inline void reserve(size_t length, bool accumulative = true) {
this->bulk.reserve(length + (accumulative ? this->bulk.size() : 0));
this->bulk->reserve(length + (accumulative ? this->bulk->size() : 0));
}
inline void reset() {
this->bulk->clear();
}
inline void put(const std::string_view& key, const std::string_view& value) {
size_t begin, end;
if(impl::value_raw_impl(this->bulk, key, begin, &end)) {
if(impl::value_raw_impl(*this->bulk, key, begin, &end)) {
std::string result{};
result.reserve(this->bulk.size());
result.reserve(this->bulk->size());
result.append(this->bulk, 0, begin - key.size() - 1); /* key incl = */
result.append(this->bulk, end + 1); /* get rid of the space */
this->bulk = result;
result.append(*this->bulk, 0, begin - key.size() - 1); /* key incl = */
result.append(*this->bulk, end + 1); /* get rid of the space */
*this->bulk = result;
}
this->impl_put_unchecked(key, value);
}
@ -235,58 +278,87 @@ namespace ts {
auto data = converter<T>::to_string(value);
this->put_unchecked(key, std::string_view{data});
}
protected:
explicit command_builder_bulk(bool& change_flag, std::string& bulk) : flag_changed{&change_flag}, bulk{&bulk} {}
private:
bool& flag_changed;
std::string& bulk;
explicit command_builder_bulk(bool& change_flag, std::string& bulk) : flag_changed{change_flag}, bulk{bulk} {}
bool* flag_changed;
std::string* bulk;
void impl_put_unchecked(const std::string_view& key, const std::string_view& value) {
auto escaped_value = ts::query::escape(std::string{value});
this->bulk.reserve(this->bulk.length() + key.size() + escaped_value.size() + 2);
this->bulk.append(key);
this->bulk->reserve(this->bulk->length() + key.size() + escaped_value.size() + 2);
this->bulk->append(key);
if(!escaped_value.empty()) {
this->bulk.append("=");
this->bulk.append(escaped_value);
this->bulk->append("=");
this->bulk->append(escaped_value);
}
this->bulk.append(" ");
this->flag_changed = true;
this->bulk->append(" ");
*this->flag_changed = true;
}
};
class standalone_command_builder_bulk : public command_builder_bulk {
public:
explicit standalone_command_builder_bulk(size_t expected_length = 0) : command_builder_bulk{this->flag_changed_, this->buffer_} {
if(expected_length > 0)
this->buffer_.reserve(expected_length);
}
[[nodiscard]] inline const std::string& buffer() const { return this->buffer_; }
[[nodiscard]] inline std::string& buffer() { return this->buffer_; }
private:
bool flag_changed_{};
std::string buffer_{};
};
template <typename vector_t = std::vector<std::string>>
class command_builder_impl {
public:
explicit command_builder_impl(std::string command, size_t expected_bulk_size = 128, size_t expected_bulks = 1) : _identifier{std::move(command)}, expected_bulk_size{expected_bulk_size} {
explicit command_builder_impl(std::string command, size_t expected_bulk_size = 128, size_t expected_bulks = 1) : identifier_{std::move(command)}, expected_bulk_size{expected_bulk_size} {
for(size_t index = 0; index < expected_bulks; index++)
this->bulks.emplace_back(" ").reserve(expected_bulk_size);
this->bulks.emplace_back("").reserve(expected_bulk_size);
}
inline command_builder_impl<std::vector<std::string>> as_normalized() {
return command_builder_impl<std::vector<std::string>>{this->expected_bulk_size, this->_identifier, this->bulks.begin(), this->bulks.end()};
return command_builder_impl<std::vector<std::string>>{this->expected_bulk_size, this->identifier_, this->bulks.begin(), this->bulks.end()};
}
inline std::string build(bool with_empty = false) const {
if(this->builded.has_value() && !this->flag_changed)
if(this->builded.has_value() && !this->flag_changed) {
return this->builded.value();
}
std::string result{};
size_t required_size{this->_identifier.size()};
for(auto& entry : this->bulks)
size_t required_size{this->identifier_.size()};
for(auto& entry : this->bulks) {
required_size += entry.size() + 1;
}
if(!this->identifier_.empty()) {
result.append(this->identifier_);
result.push_back(' ');
}
result.append(this->_identifier);
for(auto it = this->bulks.begin(); it != this->bulks.end(); it++) {
if(it->empty() && !with_empty) continue;
if(it->empty() && !with_empty) {
continue;
}
result.append(*it, 0, it->length() - 1);
if(it + 1 != this->bulks.end())
if(it + 1 != this->bulks.end()) {
result.append("|");
}
}
if(!with_empty && result.ends_with('|'))
if(!with_empty && !result.empty() && result.back() == '|') {
this->builded = result.substr(0, result.length() - 1);
else
} else {
this->builded = result;
}
this->flag_changed = false;
return this->builded.value();
}
@ -294,8 +366,9 @@ namespace ts {
inline void reserve_bulks(size_t count) { this->bulks.reserve(count); }
[[nodiscard]] inline command_builder_bulk bulk(size_t index) {
while(this->bulks.size() <= index)
while(this->bulks.size() <= index) {
this->bulks.emplace_back("").reserve(expected_bulk_size);
}
return command_builder_bulk{this->flag_changed, this->bulks[index]};
}
@ -320,19 +393,34 @@ namespace ts {
return result;
}
inline void push_bulk(standalone_command_builder_bulk&& bulk) {
this->bulks.push_back(std::move(bulk.buffer()));
this->flag_changed = true;
}
inline void set_bulk(size_t index, standalone_command_builder_bulk&& bulk) {
while(this->bulks.size() <= index) {
this->bulks.emplace_back("").reserve(expected_bulk_size);
}
this->bulks[index] = std::move(bulk.buffer());
this->flag_changed = true;
}
inline void reset() {
for(auto& bulk : this->bulks)
bulk = " ";
}
private:
command_builder_impl(size_t expected, std::string identifier, typename vector_t::iterator begin, typename vector_t::iterator end) : expected_bulk_size{expected}, _identifier{std::move(identifier)}, bulks{begin, end} {}
command_builder_impl(size_t expected, std::string identifier, typename vector_t::iterator begin, typename vector_t::iterator end) : expected_bulk_size{expected}, identifier_{std::move(identifier)}, bulks{begin, end} {}
const size_t expected_bulk_size;
const std::string _identifier;
const std::string identifier_;
mutable bool flag_changed{false};
mutable std::optional<std::string> builded{};
vector_t bulks{};
};
using command_builder = command_builder_impl<>;
}
}
#define COMMAND_BUILDER_DEFINED

View File

@ -1,26 +1,51 @@
#pragma once
#include <utility>
namespace ts {
class command_exception : public std::exception {};
class command_casted_exception : public command_exception { };
class command_cannot_uncast_exception : public command_exception { };
class command_bulk_exceed_index_exception : public command_exception { };
class command_bulk_index_out_of_bounds_exception : public command_exception {
public:
explicit command_bulk_index_out_of_bounds_exception(size_t index) : _index(index) { }
[[nodiscard]] inline size_t index() const { return this->_index; }
private:
size_t _index;
};
class command_value_missing_exception : public command_exception {
public:
command_value_missing_exception(size_t index, std::string key) : _index(index), _key(move(key)) { }
inline size_t index() const { return this->_index; }
inline std::string key() const { return this->_key; }
[[nodiscard]] inline size_t bulk_index() const { return this->_index; }
[[nodiscard]] inline std::string key() const { return this->_key; }
private:
size_t _index;
std::string _key;
};
class command_malformed_exception : public command_exception {
public:
command_malformed_exception(size_t index) : _index(index) {}
inline size_t index() const { return this->_index; }
explicit command_malformed_exception(size_t index) : _index(index) {}
[[nodiscard]] inline size_t index() const { return this->_index; }
private:
size_t _index;
};
class command_value_cast_failed : public command_exception {
public:
command_value_cast_failed(size_t index, std::string key, std::string value, const std::type_info& target_type)
: index_{index}, key_{std::move(key)}, value_{std::move(value)}, target_type_{target_type} {}
[[nodiscard]] inline size_t index() const { return this->index_; }
[[nodiscard]] inline const std::string& key() const { return this->key_; }
[[nodiscard]] inline const std::string& value() const { return this->value_; }
[[nodiscard]] inline const std::type_info& target_type() const { return this->target_type_; }
private:
size_t index_;
std::string key_;
std::string value_;
const std::type_info& target_type_;
};
}

View File

@ -102,40 +102,57 @@ std::string query::unescape(std::string in, bool throw_error) {
in.replace(index, 1, "", 0); //Cut the character out
index--;
} else if(current >= 194 && current <= 223) {
if(in.length() - index <= 1)
if(in.length() - index <= 1) {
in.replace(index, in.length() - index, "", 0);
else if((uint8_t) in[index + 1] >= 128 && (uint8_t) in[index + 1] <= 191) index += 1; //Valid
else {
if(throw_error) throw invalid_argument("Invalid UTF-8 character at index " + to_string(index));
} else if((uint8_t) in[index + 1] >= 128 && (uint8_t) in[index + 1] <= 191) {
index += 1; //Valid
} else {
if(throw_error) {
throw invalid_argument("Invalid UTF-8 character at index " + to_string(index));
}
in.replace(index, 2, "", 0); //Cut the two characters out
index--;
}
} else if(current >= 224 && current <= 239) {
if(in.length() - index <= 2)
if(in.length() - index <= 2) {
in.replace(index, in.length() - index, "", 0);
else if((uint8_t) in[index + 1] >= 128 && (uint8_t) in[index + 1] <= 191 &&
(uint8_t) in[index + 2] >= 128 && (uint8_t) in[index + 2] <= 191) index += 2; //Valid
else {
if(throw_error) throw invalid_argument("Invalid UTF-8 character at index " + to_string(index));
} else if((uint8_t) in[index + 1] >= 128 && (uint8_t) in[index + 1] <= 191 &&
(uint8_t) in[index + 2] >= 128 && (uint8_t) in[index + 2] <= 191) {
index += 2; //Valid
} else {
if(throw_error) {
throw invalid_argument("Invalid UTF-8 character at index " + to_string(index));
}
in.replace(index, 3, "", 0); //Cut the three characters out
index--;
}
} else if(current >= 240 && current <= 244) {
if(in.length() - index <= 3)
if(in.length() - index <= 3) {
in.replace(index, in.length() - index, "", 0);
else if((uint8_t) in[index + 1] >= 128 && (uint8_t) in[index + 1] <= 191 &&
} else if((uint8_t) in[index + 1] >= 128 && (uint8_t) in[index + 1] <= 191 &&
(uint8_t) in[index + 2] >= 128 && (uint8_t) in[index + 2] <= 191 &&
(uint8_t) in[index + 3] >= 128 && (uint8_t) in[index + 3] <= 191) index += 3; //Valid
else {
if(throw_error) throw invalid_argument("Invalid UTF-8 character at index " + to_string(index));
(uint8_t) in[index + 3] >= 128 && (uint8_t) in[index + 3] <= 191) {
index += 3; //Valid
} else {
if(throw_error) {
throw invalid_argument("Invalid UTF-8 character at index " + to_string(index));
}
in.replace(index, 4, "", 0); //Cut the three characters out
index--;
}
} else {
if(throw_error) throw invalid_argument("Invalid UTF-8 character at index " + to_string(index));
if(throw_error) {
throw invalid_argument("Invalid UTF-8 character at index " + to_string(index));
}
in.replace(index, 1, "", 0); //Cut the character out
index--;
}
} else if(current < 0x0A) {
in.replace(index, 1, " ", 1);
}
index++;
}

View File

@ -8,7 +8,7 @@ using namespace std;
using namespace std::chrono;
namespace sql {
result result::success = result("", 0, "success");
result result::success = result("", 0, -1, "success");
sql::command model::command() { return ::sql::command(*this); }
sql::model model::copy() { return sql::model(this->_data); }
model::model(const std::shared_ptr<CommandData>& data) : command_base(data->handle->copyCommandData(data)) {}
@ -33,7 +33,6 @@ namespace sql {
command copy = cmd;
result res;
while((res = copy.execute()).code() == SQLITE_BUSY){
cerr << "Execute busy!" << endl;
usleep(1000);
}
fut.executionSucceed(res);

View File

@ -8,12 +8,13 @@
#include <utility>
#include <ThreadPool/ThreadPool.h>
#include <ThreadPool/Future.h>
#include "../Variable.h"
#include <misc/memtracker.h>
#include <misc/lambda.h>
#include "../Variable.h"
#define ALLOW_STACK_ALLOCATION
#define LOG_SQL_CMD [](const sql::result &res){ if(!res) logCritical(LOG_GENERAL, "Failed to execute sql command: " + std::to_string(res.code()) + "/" + res.msg() + " (" __FILE__ + ":" + to_string(__LINE__) + ")"); }
#define LOG_SQL_CMD [](const sql::result &res){ if(!res) logCritical(LOG_GENERAL, "Failed to execute sql command: " + std::to_string(res.code()) + "/" + res.msg() + " (" __FILE__ + ":" + std::to_string(__LINE__) + ")"); }
namespace sql {
class result;
class SqlManager;
@ -32,26 +33,25 @@ namespace sql {
class result {
public:
static result success;
result() : result(success) { }
result(std::string query, int code, std::string msg) : _code(code), _msg(std::move(msg)), _sql(std::move(query)) { }
result(int code, const std::string &msg) : _code(code), _msg(std::move(msg)) { }
result(const result& ref) : _code(ref._code), _msg(ref._msg), _sql(ref._sql) { }
result(result&& ref) : _code(ref._code), _msg(std::move(ref._msg)), _sql(std::move(ref._sql)) { }
virtual ~result() { };
int code() const { return _code; }
std::string msg() const { return _msg; }
std::string sql() const { return _sql; }
result() : result{success} { }
result(int code, std::string msg)
: code_{code}, msg_{std::move(msg)}, sql_{""}, last_insert_rowid_{-1} { }
result(std::string query, int code, int64_t last_insert_rowid, std::string msg)
: code_{code}, msg_{std::move(msg)}, sql_{std::move(query)}, last_insert_rowid_{last_insert_rowid} { }
result(const result&) = default;
result(result&&) = default;
result& operator=(const result&) = default;
result& operator=(result&&) = default;
int code() const { return this->code_; }
std::string msg() const { return this->msg_; }
std::string sql() const { return this->sql_; }
int64_t last_insert_rowid() const { return this->last_insert_rowid_; };
//Returns true on success
operator bool() const { return _code == 0; }
result&operator=(const result& other) {
this->_code = other._code;
this->_msg = other._msg;
this->_sql = other._sql;
return *this;
}
operator bool() const { return code_ == 0; }
std::string fmtStr() const {
std::stringstream s;
@ -59,9 +59,10 @@ namespace sql {
return s.str();
}
private:
int _code = 0;
std::string _msg{};
std::string _sql{};
int code_{0};
int64_t last_insert_rowid_{0};
std::string msg_{};
std::string sql_{};
};
enum SqlType {
@ -262,25 +263,27 @@ namespace sql {
friend class ::sql::command;
friend class ::sql::model;
public:
explicit command_base(std::nullptr_t) : _data{nullptr} {}
command_base(SqlManager* handle, const std::string &sql, std::initializer_list<variable> values) {
assert(handle);
assert(!sql.empty());
this->_data = handle->allocateCommandData();
this->_data->handle = handle;
this->_data->sql_command = sql;
this->__data = this->_data.get();
for(const auto& val : values) this->value(val);
for(const auto& val : values)
this->value(val);
}
template<typename... Ts>
command_base(SqlManager* handle, std::string sql, Ts&&... vars) : command_base(handle, sql, {}) { values(vars...); }
command_base(SqlManager* handle, const std::string& sql, Ts&&... vars) : command_base(handle, sql, {}) { values(vars...); }
command_base(const command_base<SelfType>& ref) : _data(ref._data), __data(ref._data.get()) {}
command_base(command_base<SelfType>&& ref) noexcept : _data(ref._data), __data(ref._data.get()) { }
command_base(const command_base<SelfType>& ref) : _data(ref._data) {}
command_base(command_base<SelfType>&& ref) noexcept : _data(ref._data) { }
virtual ~command_base() = default;
virtual SelfType& value(const variable& val) {
SelfType& value(const variable& val) {
this->_data->variables.push_back(val);
return *(SelfType*) this;
}
@ -303,10 +306,20 @@ namespace sql {
std::string sqlCommand(){ return _data->sql_command; }
SqlManager* handle(){ return _data->handle; }
command_base& operator=(const command_base& other) {
this->_data = other._data;
return *this;
}
command_base& operator=(command_base&& other) {
this->_data = std::move(other._data);
return *this;
}
protected:
explicit command_base(const std::shared_ptr<CommandData>& data) : _data(data), __data(data.get()) {}
explicit command_base(std::shared_ptr<CommandData> data) : _data(std::move(data)) {}
std::shared_ptr<CommandData> _data;
CommandData* __data = nullptr;
};
}
@ -317,13 +330,13 @@ namespace sql {
template<typename... Ts>
model(SqlManager* handle, const std::string &sql, Ts... vars) : model(handle, sql, {}) { values(vars...); }
model(const model& v) : command_base(v) {};
model(model&& v) noexcept : command_base(v){};
~model() override {};
explicit model(std::nullptr_t) : command_base{nullptr} {};
//model(const model& v) : command_base{v} {};
//model(model&& v) noexcept : command_base{std::forward<model>(v)} {};
~model() override = default;
sql::command command();
sql::model copy();
private:
explicit model(const std::shared_ptr<CommandData>&);
};

View File

@ -14,7 +14,7 @@ namespace sql {
[[nodiscard]] static std::string generate(size_t index) {
assert(index > 0);
std::string result{};
result.resize(std::max(index >> 4U, 1UL));
result.resize(std::max(index >> 4U, (size_t) 1U));
for(auto it = result.begin(); index > 0; index >>= 4U)
*(it++) = number_map[index & 0xFU];
return result;
@ -49,6 +49,7 @@ namespace sql {
else
result += "OR IGNORE ";
}
result += "INTO ";
result += this->table_name + " (";
//"INSERT INTO " + this->table_name + " ("
@ -94,7 +95,7 @@ namespace sql {
ExecuteResult result{};
const auto chunk_size = std::min(2UL, this->entries.size());
const auto chunk_size = std::min((size_t) 2U, this->entries.size());
sql::model chunk_base{sql, this->generate_query(chunk_size, sql->getType(), ignore_fails)};
sql::model entry_model{sql, this->generate_query(1, sql->getType(), ignore_fails)};

View File

@ -26,8 +26,9 @@ MySQLManager::~MySQLManager() {}
#define MYSQL_PREFIX "mysql://"
inline result parse_url(const string& url, std::map<std::string, std::string>& connect_map) {
string target_url;
if(url.find(MYSQL_PREFIX) != 0)
return {ERROR_MYSQL_INVLID_URL, "Missing mysql:// at begin"};
if(url.find(MYSQL_PREFIX) != 0) {
return {"", ERROR_MYSQL_INVLID_URL, -1, "Missing mysql:// at begin"};
}
auto index_parms = url.find('?');
if(index_parms == string::npos) {
@ -184,7 +185,7 @@ result MySQLManager::connect(const std::string &url) {
if(!parse_mysql_data(url, error, host, port, database, properties)) {
error = "URL parsing failed: " + error;
return {ERROR_MYSQL_INVLID_URL, error};
return {"", ERROR_MYSQL_INVLID_URL, -1, error};
}
size_t connections = 4;
@ -192,17 +193,17 @@ result MySQLManager::connect(const std::string &url) {
try {
connections = stol(properties["connections"]);
} catch(std::exception& ex) {
return {ERROR_MYSQL_INVLID_PROPERTIES, "could not parse connection count"};
return {"", ERROR_MYSQL_INVLID_PROPERTIES, -1, "could not parse connection count"};
}
}
string username, password;
if(properties.count("userName") > 0) username = properties["userName"];
if(properties.count("username") > 0) username = properties["username"];
if(username.empty()) return {ERROR_MYSQL_INVLID_PROPERTIES, "missing username property"};
if(username.empty()) return {"", ERROR_MYSQL_INVLID_PROPERTIES, -1, "missing username property"};
if(properties.count("password") > 0) password = properties["password"];
if(password.empty()) return {ERROR_MYSQL_INVLID_PROPERTIES, "missing password property"};
if(password.empty()) return {"", ERROR_MYSQL_INVLID_PROPERTIES, -1, "missing password property"};
//debugMessage(LOG_GENERAL, R"([MYSQL] Starting {} connections to {}:{} with database "{}" as user "{}")", connections, host, port, database, username);
@ -210,10 +211,10 @@ result MySQLManager::connect(const std::string &url) {
auto connection = make_shared<Connection>();
connection->handle = mysql_init(nullptr);
if(!connection->handle)
return {-1, "failed to allocate connection " + to_string(index)};
return {"", -1, -1, "failed to allocate connection " + to_string(index)};
{
bool reconnect{true};
uint32_t reconnect{true};
mysql_options(connection->handle, MYSQL_OPT_RECONNECT, &reconnect);
}
mysql_options(connection->handle, MYSQL_SET_CHARSET_NAME, "utf8");
@ -221,7 +222,7 @@ result MySQLManager::connect(const std::string &url) {
auto result = mysql_real_connect(connection->handle, host.c_str(), username.c_str(), password.c_str(), database.c_str(), port, nullptr, 0); //CLIENT_MULTI_RESULTS | CLIENT_MULTI_STATEMENTS
if(!result)
return {-1, "failed to connect to server with connection " + to_string(index) + ": " + mysql_error(connection->handle)};
return {"", -1, -1, "failed to connect to server with connection " + to_string(index) + ": " + mysql_error(connection->handle)};
connection->used = false;
this->connections.push_back(connection);
@ -230,12 +231,12 @@ result MySQLManager::connect(const std::string &url) {
}
bool MySQLManager::connected() {
lock_guard<mutex> lock(this->connections_lock);
lock_guard<mutex> lock(this->connections_mutex);
return !this->connections.empty();
}
result MySQLManager::disconnect() {
lock_guard<mutex> lock(this->connections_lock);
lock_guard<mutex> lock(this->connections_mutex);
this->disconnecting = true;
this->connections.clear();
@ -634,14 +635,14 @@ AcquiredConnection::~AcquiredConnection() {
}
{
lock_guard lock(this->owner->connections_lock);
lock_guard lock(this->owner->connections_mutex);
this->owner->connections_condition.notify_one();
}
}
std::unique_ptr<AcquiredConnection> MySQLManager::next_connection() {
unique_ptr<AcquiredConnection> result;
{
unique_lock connections_lock(this->connections_lock);
unique_lock connections_lock(this->connections_mutex);
while(!result) {
size_t available_connections = 0;
@ -676,118 +677,136 @@ std::unique_ptr<AcquiredConnection> MySQLManager::next_connection() {
}
void MySQLManager::connection_closed(const std::shared_ptr<sql::mysql::Connection> &connection) {
bool call_disconnect = false;
bool call_disconnect;
{
unique_lock connections_lock(this->connections_lock);
auto index = find(this->connections.begin(), this->connections.end(), connection);
if(index == this->connections.end()) return;
unique_lock connections_lock{this->connections_mutex};
auto index = std::find(this->connections.begin(), this->connections.end(), connection);
if(index == this->connections.end()) {
return;
}
this->connections.erase(index);
call_disconnect = this->connections.empty();
}
auto dl = this->listener_disconnected;
if(call_disconnect && dl)
if(call_disconnect && dl) {
dl(this->disconnecting);
}
}
result MySQLManager::executeCommand(std::shared_ptr<CommandData> _ptr) {
auto ptr = static_pointer_cast<MySQLCommand>(_ptr);
if(!ptr) { return {-1, "invalid command handle"}; }
result MySQLManager::executeCommand(std::shared_ptr<CommandData> command_data) {
auto mysql_data = static_pointer_cast<MySQLCommand>(command_data);
if(!mysql_data) {
return {"", -1, -1, "invalid command handle"};
}
std::lock_guard<threads::Mutex> lock(ptr->lock);
auto command = ptr->sql_command;
std::lock_guard<threads::Mutex> lock(mysql_data->lock);
auto command = mysql_data->sql_command;
auto variables = ptr->variables;
auto variables = mysql_data->variables;
vector<variable> mapped_variables;
if(!sql::mysql::evaluate_sql_query(command, variables, mapped_variables))
return {ptr->sql_command, -1, "Could not map sqlite vars to mysql!"};
if(!sql::mysql::evaluate_sql_query(command, variables, mapped_variables)) {
return {mysql_data->sql_command, -1, -1, "Could not map sqlite vars to mysql!"};
}
FreeGuard<BindMemory> bind_parameter_memory{nullptr};
if(!sql::mysql::create_bind(bind_parameter_memory.ptr, mapped_variables))
return {ptr->sql_command, -1, "Failed to allocate bind memory!"};
if(!sql::mysql::create_bind(bind_parameter_memory.ptr, mapped_variables)) {
return {mysql_data->sql_command, -1, -1, "Failed to allocate bind memory!"};
}
ResultBind bind_result_data{.field_count = 0, .memory = nullptr, .descriptors = nullptr};
ResultBind bind_result_data{0, nullptr, nullptr};
auto connection = this->next_connection();
if(!connection) return {ptr->sql_command, -1, "Could not get a valid connection!"};
if(!connection) {
return {mysql_data->sql_command, -1, -1, "Could not get a valid connection!"};
}
StatementGuard stmt_guard{mysql_stmt_init(connection->connection->handle)};
if(!stmt_guard.stmt)
return {ptr->sql_command, -1, "failed to allocate statement"};
if(!stmt_guard.stmt) {
return {mysql_data->sql_command, -1, -1, "failed to allocate statement"};
}
if(mysql_stmt_prepare(stmt_guard.stmt, command.c_str(), command.length())) {
auto errc = mysql_stmt_errno(stmt_guard.stmt);
if(errc == CR_SERVER_GONE_ERROR || errc == CR_SERVER_LOST || errc == CR_CONNECTION_ERROR)
if(errc == CR_SERVER_GONE_ERROR || errc == CR_SERVER_LOST || errc == CR_CONNECTION_ERROR) {
this->connection_closed(connection->connection);
}
return {ptr->sql_command, -1, "failed to prepare statement: " + string(mysql_stmt_error(stmt_guard.stmt))};
return {mysql_data->sql_command, -1, -1, "failed to prepare statement: " + string(mysql_stmt_error(stmt_guard.stmt))};
}
/* validate all parameters */
auto parameter_count = mysql_stmt_param_count(stmt_guard.stmt);
if(parameter_count != mapped_variables.size())
return {ptr->sql_command, -1, "invalid parameter count. Statement contains " + to_string(parameter_count) + " parameters but only " + to_string(mapped_variables.size()) + " are given."};
if(parameter_count != mapped_variables.size()) {
return {mysql_data->sql_command, -1, -1, "invalid parameter count. Statement contains " + to_string(parameter_count) + " parameters but only " + to_string(mapped_variables.size()) + " are given."};
}
if(bind_parameter_memory.ptr) {
if(mysql_stmt_bind_param(stmt_guard.stmt, (MYSQL_BIND*) bind_parameter_memory.ptr))
return {ptr->sql_command, -1, "failed to bind parameters to statement: " + string(mysql_stmt_error(stmt_guard.stmt))};
} else if(parameter_count > 0)
return {ptr->sql_command, -1, "invalid parameter count. Statement contains " + to_string(parameter_count) + " parameters but only " + to_string(mapped_variables.size()) + " are given (bind nullptr)."};
if(mysql_stmt_bind_param(stmt_guard.stmt, (MYSQL_BIND*) bind_parameter_memory.ptr)) {
return {mysql_data->sql_command, -1, -1, "failed to bind parameters to statement: " + string(mysql_stmt_error(stmt_guard.stmt))};
}
} else if(parameter_count > 0) {
return {mysql_data->sql_command, -1, -1, "invalid parameter count. Statement contains " + to_string(parameter_count) + " parameters but only " + to_string(mapped_variables.size()) + " are given (bind nullptr)."};
}
if(mysql_stmt_execute(stmt_guard.stmt)) {
auto errc = mysql_stmt_errno(stmt_guard.stmt);
if(errc == CR_SERVER_GONE_ERROR || errc == CR_SERVER_LOST || errc == CR_CONNECTION_ERROR)
if(errc == CR_SERVER_GONE_ERROR || errc == CR_SERVER_LOST || errc == CR_CONNECTION_ERROR) {
this->connection_closed(connection->connection);
}
return {ptr->sql_command, -1, "failed to execute query statement: " + string(mysql_stmt_error(stmt_guard.stmt))};
return {mysql_data->sql_command, -1, -1, "failed to execute query statement: " + string(mysql_stmt_error(stmt_guard.stmt))};
}
return result::success;
auto insert_row_id = mysql_stmt_insert_id(stmt_guard.stmt);
return {mysql_data->sql_command, 0, (int64_t) insert_row_id, "success"};
}
result MySQLManager::queryCommand(shared_ptr<CommandData> _ptr, const QueryCallback &fn) {
auto ptr = static_pointer_cast<MySQLCommand>(_ptr);
if(!ptr) { return {-1, "invalid command handle"}; }
result MySQLManager::queryCommand(shared_ptr<CommandData> command_data, const QueryCallback &fn) {
auto mysql_data = static_pointer_cast<MySQLCommand>(command_data);
if(!mysql_data) {
return {"", -1, -1, "invalid command handle"};
}
std::lock_guard<threads::Mutex> lock(ptr->lock);
auto command = ptr->sql_command;
std::lock_guard<threads::Mutex> lock(mysql_data->lock);
auto command = mysql_data->sql_command;
auto variables = ptr->variables;
auto variables = mysql_data->variables;
vector<variable> mapped_variables;
if(!sql::mysql::evaluate_sql_query(command, variables, mapped_variables)) return {ptr->sql_command, -1, "Could not map sqlite vars to mysql!"};
if(!sql::mysql::evaluate_sql_query(command, variables, mapped_variables)) return {mysql_data->sql_command, -1, -1, "Could not map sqlite vars to mysql!"};
FreeGuard<BindMemory> bind_parameter_memory{nullptr};
if(!sql::mysql::create_bind(bind_parameter_memory.ptr, mapped_variables)) return {ptr->sql_command, -1, "Failed to allocate bind memory!"};
if(!sql::mysql::create_bind(bind_parameter_memory.ptr, mapped_variables)) return {mysql_data->sql_command, -1, -1, "Failed to allocate bind memory!"};
ResultBind bind_result_data{.field_count = 0, .memory = nullptr, .descriptors = nullptr};
ResultBind bind_result_data{0, nullptr, nullptr};
auto connection = this->next_connection();
if(!connection) return {ptr->sql_command, -1, "Could not get a valid connection!"};
if(!connection) return {mysql_data->sql_command, -1, -1, "Could not get a valid connection!"};
StatementGuard stmt_guard{mysql_stmt_init(connection->connection->handle)};
if(!stmt_guard.stmt)
return {ptr->sql_command, -1, "failed to allocate statement"};
return {mysql_data->sql_command, -1, -1, "failed to allocate statement"};
if(mysql_stmt_prepare(stmt_guard.stmt, command.c_str(), command.length())) {
auto errc = mysql_stmt_errno(stmt_guard.stmt);
if(errc == CR_SERVER_GONE_ERROR || errc == CR_SERVER_LOST || errc == CR_CONNECTION_ERROR)
this->connection_closed(connection->connection);
return {ptr->sql_command, -1, "failed to prepare statement: " + string(mysql_stmt_error(stmt_guard.stmt))};
return {mysql_data->sql_command, -1, -1, "failed to prepare statement: " + string(mysql_stmt_error(stmt_guard.stmt))};
}
/* validate all parameters */
{
auto parameter_count = mysql_stmt_param_count(stmt_guard.stmt);
if(parameter_count != mapped_variables.size())
return {ptr->sql_command, -1, "invalid parameter count. Statement contains " + to_string(parameter_count) + " parameters but only " + to_string(mapped_variables.size()) + " are given."};
return {mysql_data->sql_command, -1, -1, "invalid parameter count. Statement contains " + to_string(parameter_count) + " parameters but only " + to_string(mapped_variables.size()) + " are given."};
}
if(bind_parameter_memory.ptr) {
if(mysql_stmt_bind_param(stmt_guard.stmt, (MYSQL_BIND*) bind_parameter_memory.ptr))
return {ptr->sql_command, -1, "failed to bind parameters to statement: " + string(mysql_stmt_error(stmt_guard.stmt))};
return {mysql_data->sql_command, -1, -1, "failed to bind parameters to statement: " + string(mysql_stmt_error(stmt_guard.stmt))};
}
if(mysql_stmt_execute(stmt_guard.stmt)) {
@ -795,7 +814,7 @@ result MySQLManager::queryCommand(shared_ptr<CommandData> _ptr, const QueryCallb
if(errc == CR_SERVER_GONE_ERROR || errc == CR_SERVER_LOST || errc == CR_CONNECTION_ERROR)
this->connection_closed(connection->connection);
return {ptr->sql_command, -1, "failed to execute query statement: " + string(mysql_stmt_error(stmt_guard.stmt))};
return {mysql_data->sql_command, -1, -1, "failed to execute query statement: " + string(mysql_stmt_error(stmt_guard.stmt))};
}
//if(mysql_stmt_store_result(stmt_guard.stmt))
@ -803,7 +822,7 @@ result MySQLManager::queryCommand(shared_ptr<CommandData> _ptr, const QueryCallb
ResultGuard result_guard{mysql_stmt_result_metadata(stmt_guard.stmt)};
if(!result_guard.result)
return {ptr->sql_command, -1, "failed to query result metadata: " + string(mysql_stmt_error(stmt_guard.stmt))};
return {mysql_data->sql_command, -1, -1, "failed to query result metadata: " + string(mysql_stmt_error(stmt_guard.stmt))};
auto field_count = mysql_num_fields(result_guard.result);
@ -813,13 +832,13 @@ result MySQLManager::queryCommand(shared_ptr<CommandData> _ptr, const QueryCallb
{
auto field_meta = mysql_fetch_fields(result_guard.result);
if(!field_meta && field_count > 0)
return {ptr->sql_command, -1, "failed to fetch field meta"};
return {mysql_data->sql_command, -1, -1, "failed to fetch field meta"};
if(!sql::mysql::create_result_bind(field_count, field_meta, bind_result_data))
return {ptr->sql_command, -1, "failed to allocate result buffer"};
return {mysql_data->sql_command, -1, -1, "failed to allocate result buffer"};
if(mysql_stmt_bind_result(stmt_guard.stmt, (MYSQL_BIND*) bind_result_data.memory))
return {ptr->sql_command, -1, "failed to bind response buffer to statement: " + string(mysql_stmt_error(stmt_guard.stmt))};
return {mysql_data->sql_command, -1, -1, "failed to bind response buffer to statement: " + string(mysql_stmt_error(stmt_guard.stmt))};
for(size_t index = 0; index < field_count; index++) {
field_names.ptr[index] = field_meta[index].name; // field_meta cant be null because it has been checked above
@ -846,11 +865,11 @@ result MySQLManager::queryCommand(shared_ptr<CommandData> _ptr, const QueryCallb
if(errc == CR_SERVER_GONE_ERROR || errc == CR_SERVER_LOST || errc == CR_CONNECTION_ERROR)
this->connection_closed(connection->connection);
return {ptr->sql_command, -1, "failed to fetch response row " + to_string(row_id) + ": " + string(mysql_stmt_error(stmt_guard.stmt))};
return {mysql_data->sql_command, -1, -1, "failed to fetch response row " + to_string(row_id) + ": " + string(mysql_stmt_error(stmt_guard.stmt))};
} else if(stmt_code == MYSQL_NO_DATA)
;
else if(stmt_code == MYSQL_DATA_TRUNCATED)
return {ptr->sql_command, -1, "response data has been truncated"};
return {mysql_data->sql_command, -1, -1, "response data has been truncated"};
}
return result::success;
}

View File

@ -6,7 +6,7 @@
#include <condition_variable>
#include "sql/SqlQuery.h"
#include "../../misc/spin_lock.h"
#include "misc/spin_mutex.h"
#if defined(HAVE_MYSQL_MYSQL_H)
#include <mysql/mysql.h>
@ -31,7 +31,7 @@ namespace sql::mysql {
struct Connection {
MYSQL* handle = nullptr;
spin_lock used_lock;
spin_mutex used_lock;
bool used = false;
~Connection();
@ -65,17 +65,17 @@ namespace sql::mysql {
protected:
std::shared_ptr<CommandData> copyCommandData(std::shared_ptr<CommandData> ptr) override;
std::shared_ptr<CommandData> allocateCommandData() override;
result executeCommand(std::shared_ptr<CommandData> ptr) override;
result queryCommand(std::shared_ptr<CommandData> ptr, const QueryCallback &fn) override;
result executeCommand(std::shared_ptr<CommandData> command_data) override;
result queryCommand(std::shared_ptr<CommandData> command_data, const QueryCallback &fn) override;
public:
std::unique_ptr<AcquiredConnection> next_connection();
void connection_closed(const std::shared_ptr<Connection>& /* connection */);
std::mutex connections_lock;
std::mutex connections_mutex;
std::condition_variable connections_condition;
std::deque<std::shared_ptr<Connection>> connections;
bool disconnecting = false;
bool disconnecting{false};
};
}

View File

@ -17,7 +17,7 @@ result SqliteManager::connect(const std::string &string) {
auto result = sqlite3_open(url.c_str(), &this->database);
if(!this->database)
return {"connect", -1, "could not open database. Code: " + to_string(result)};
return {"connect", -1, -1, "could not open database. Code: " + to_string(result)};
return result::success;
}
@ -26,15 +26,17 @@ bool SqliteManager::connected() {
}
result SqliteManager::disconnect() {
if(!this->database)
return {"disconnect", -1, "database not open"};
if(!this->database) {
return {"disconnect", -1, -1, "database not open"};
}
this->pool->threads()->wait_for();
auto result = sqlite3_close(this->database);
if(result == 0) {
this->database = nullptr;
return result::success;
};
return {"disconnect", -1, "Failed to close database. Code: " + to_string(result)};
return {"disconnect", -1, -1, "Failed to close database. Code: " + to_string(result)};
}
std::shared_ptr<CommandData> SqliteManager::allocateCommandData() {
@ -88,8 +90,7 @@ std::shared_ptr<sqlite3_stmt> SqliteManager::allocateStatement(const std::string
return nullptr;
return std::shared_ptr<sqlite3_stmt>(stmt, [](void* _ptr) {
auto _stmt = static_cast<sqlite3_stmt*>(_ptr);
if(_stmt) sqlite3_finalize(_stmt);
sqlite3_finalize(static_cast<sqlite3_stmt*>(_ptr));
});
}
@ -104,8 +105,9 @@ result SqliteManager::queryCommand(std::shared_ptr<CommandData> _ptr, const Quer
sqlite3_reset(stmt.get());
} else {
ptr->stmt = this->allocateStatement(ptr->sql_command);
if(!ptr->stmt)
return {_ptr->sql_command,1, sqlite3_errmsg(ptr->sqlHandle<SqliteManager>()->database)};
if(!ptr->stmt) {
return {_ptr->sql_command, 1, -1, sqlite3_errmsg(ptr->sqlHandle<SqliteManager>()->database)};
}
stmt = ptr->stmt;
}
@ -135,35 +137,42 @@ result SqliteManager::queryCommand(std::shared_ptr<CommandData> _ptr, const Quer
}
}
if(result != SQLITE_DONE && !userQuit) return {_ptr->sql_command,result, sqlite3_errstr(result)};
return {_ptr->sql_command,0, "success"};
if(result != SQLITE_DONE && !userQuit) {
return {_ptr->sql_command, result, -1, sqlite3_errstr(result)};
}
return {_ptr->sql_command,0, 0, "success"};
}
result SqliteManager::executeCommand(std::shared_ptr<CommandData> _ptr) {
auto ptr = static_pointer_cast<SqliteCommand>(_ptr);
std::lock_guard<threads::Mutex> lock(ptr->lock);
result SqliteManager::executeCommand(std::shared_ptr<CommandData> command_data) {
auto sql_command = static_pointer_cast<SqliteCommand>(command_data);
std::lock_guard<threads::Mutex> lock(sql_command->lock);
result res;
sqlite3_stmt* stmt;
if(ptr->stmt){
stmt = ptr->stmt.get();
result res{};
sqlite3_stmt* stmt{};
if(sql_command->stmt){
stmt = sql_command->stmt.get();
sqlite3_reset(stmt);
} else {
ptr->stmt = this->allocateStatement(ptr->sql_command);
if(!ptr->stmt)
return {_ptr->sql_command,1, sqlite3_errmsg(ptr->sqlHandle<SqliteManager>()->database)};
stmt = ptr->stmt.get();
sql_command->stmt = this->allocateStatement(sql_command->sql_command);
if(!sql_command->stmt) {
return {sql_command->sql_command, 1, -1, sqlite3_errmsg(sql_command->sqlHandle<SqliteManager>()->database)};
}
stmt = sql_command->stmt.get();
}
int varIndex = 0;
for(const auto& var : ptr->variables)
bindVariable(stmt, varIndex, var);
int variable_index{0};
for(const auto& var : sql_command->variables) {
bindVariable(stmt, variable_index, var);
}
int result = sqlite3_step(stmt);
//logCritical(0, "Changes: {} SQL: {}", sqlite3_changes(this->database), ptr->sql_command);
if(result == SQLITE_DONE)
return {_ptr->sql_command,0, "success"};
if(result == SQLITE_ROW) return {_ptr->sql_command,-1, "query has a result"};
return {_ptr->sql_command, 1, sqlite3_errstr(result)};
if(result == SQLITE_DONE) {
auto last_row = sqlite3_last_insert_rowid(this->database);
return {sql_command->sql_command, 0, last_row, "success"};
} else if(result == SQLITE_ROW) {
return {sql_command->sql_command, -1, -1, "query has a result"};
} else {
return {sql_command->sql_command, 1, -1, sqlite3_errstr(result)};
}
}

View File

@ -21,7 +21,7 @@ namespace sql {
protected:
std::shared_ptr<CommandData> copyCommandData(std::shared_ptr<CommandData> ptr) override;
std::shared_ptr<CommandData> allocateCommandData() override;
result executeCommand(std::shared_ptr<CommandData> ptr) override;
result executeCommand(std::shared_ptr<CommandData> command_data) override;
result queryCommand(std::shared_ptr<CommandData> ptr, const QueryCallback&fn) override;
private:

View File

@ -393,8 +393,9 @@ std::shared_ptr<SSLKeyPair> SSLManager::loadSSL(const std::string &key_data, std
if(!fs::exists(key_path)) {
try {
if(key_path.has_parent_path())
if(key_path.has_parent_path()) {
fs::create_directories(key_path.parent_path());
}
} catch (fs::filesystem_error& error) {
logError(LOG_GENERAL, "Could not create key directory: " + string(error.what()));
}
@ -407,15 +408,19 @@ std::shared_ptr<SSLKeyPair> SSLManager::loadSSL(const std::string &key_data, std
if(!key_bio) SSL_ERROR("Could not load key: ");
}
if(readPublic)
if(readPublic) {
key = shared_ptr<EVP_PKEY>(PEM_read_bio_PUBKEY(key_bio.get(), nullptr, nullptr, nullptr), ::EVP_PKEY_free);
else
} else {
key = shared_ptr<EVP_PKEY>(PEM_read_bio_PrivateKey(key_bio.get(), nullptr, nullptr, nullptr), ::EVP_PKEY_free);
}
result->contains_private = !readPublic;
if(!key) {
if(readPublic) {
SSL_ERROR("Could not read key!");
} else return this->loadSSL(key_data, error, rawData, true);
} else {
return this->loadSSL(key_data, error, rawData, true);
}
}
result->key = key;

View File

@ -61,7 +61,7 @@ command_result test3() {
void eval_test(command_result x) {
if(x.is_detailed()) {
cout << "Detailed!" << endl;
x.release_details();
x.release_data();
} else {
auto a = x.permission_id();
auto b = x.error_code();

View File

@ -34,7 +34,7 @@ test_vector_t swap_elements(test_vector_t vector, int per, int max_distance) {
}
bool test_vector(const std::string_view& name, const test_vector_t& vector) {
generation_estimator gen{};
GenerationEstimator gen{};
size_t last_value{0}, last_gen{0}, index{0};
for(auto [id, exp] : vector) {
@ -50,7 +50,7 @@ bool test_vector(const std::string_view& name, const test_vector_t& vector) {
}
template <size_t N>
bool test_vector(generation_estimator& generator, const std::array<uint16_t, N>& packet_ids, const std::array<uint16_t, N>& expected) {
bool test_vector(GenerationEstimator& generator, const std::array<uint16_t, N>& packet_ids, const std::array<uint16_t, N>& expected) {
for(size_t index = 0; index < N; index++) {
auto result = generator.visit_packet(packet_ids[index]);
if(result != expected[index]) {
@ -67,7 +67,7 @@ bool test_vector(generation_estimator& generator, const std::array<uint16_t, N>&
}
int main() {
generation_estimator gen{};
GenerationEstimator gen{};
{
test_vector("00 loss", generate_test_vector(0x30000, 0));