From 22e0ffb2d4b7a77eb3c2fa14e0a4ad3e854ccbe7 Mon Sep 17 00:00:00 2001 From: radkesvat <134321679+radkesvat@users.noreply.github.com> Date: Mon, 22 Apr 2024 06:54:26 +0000 Subject: [PATCH] add libhv directly to ww --- ww/libhv/.clang-format | 95 ++ ww/libhv/.clang-tidy | 3 + ww/libhv/.gitattributes | 1 + ww/libhv/.gitignore | 78 + ww/libhv/CMakeLists.txt | 282 ++++ ww/libhv/LICENSE | 30 + ww/libhv/README.md | 9 + ww/libhv/base/README.md | 28 + ww/libhv/base/array.h | 152 ++ ww/libhv/base/hatomic.h | 130 ++ ww/libhv/base/hbase.c | 519 +++++++ ww/libhv/base/hbase.h | 144 ++ ww/libhv/base/hbuf.h | 257 ++++ ww/libhv/base/hdef.h | 271 ++++ ww/libhv/base/heap.h | 172 +++ ww/libhv/base/hendian.h | 244 +++ ww/libhv/base/herr.c | 19 + ww/libhv/base/herr.h | 122 ++ ww/libhv/base/hlog.c | 475 ++++++ ww/libhv/base/hlog.h | 179 +++ ww/libhv/base/hmain.c | 684 +++++++++ ww/libhv/base/hmain.h | 117 ++ ww/libhv/base/hmath.h | 68 + ww/libhv/base/hmutex.h | 268 ++++ ww/libhv/base/hplatform.h | 330 ++++ ww/libhv/base/hproc.h | 69 + ww/libhv/base/hsocket.c | 408 +++++ ww/libhv/base/hsocket.h | 285 ++++ ww/libhv/base/hsysinfo.h | 68 + ww/libhv/base/hthread.h | 217 +++ ww/libhv/base/htime.c | 290 ++++ ww/libhv/base/htime.h | 114 ++ ww/libhv/base/hversion.c | 48 + ww/libhv/base/hversion.h | 34 + ww/libhv/base/list.h | 733 +++++++++ ww/libhv/base/netinet.h | 194 +++ ww/libhv/base/queue.h | 105 ++ ww/libhv/base/rbtree.c | 386 +++++ ww/libhv/base/rbtree.h | 147 ++ ww/libhv/cmake/ios.toolchain.cmake | 1028 +++++++++++++ ww/libhv/cmake/libhvConfig.cmake | 87 ++ ww/libhv/cmake/utils.cmake | 42 + ww/libhv/cmake/vars.cmake | 48 + ww/libhv/event/README.md | 20 + ww/libhv/event/epoll.c | 145 ++ ww/libhv/event/evport.c | 137 ++ ww/libhv/event/hevent.c | 830 ++++++++++ ww/libhv/event/hevent.h | 285 ++++ ww/libhv/event/hloop.c | 1012 +++++++++++++ ww/libhv/event/hloop.h | 719 +++++++++ ww/libhv/event/iocp.c | 81 + ww/libhv/event/iowatcher.h | 39 + ww/libhv/event/kcp/LICENSE | 21 + ww/libhv/event/kcp/hkcp.c | 150 ++ ww/libhv/event/kcp/hkcp.h | 30 + ww/libhv/event/kcp/ikcp.c | 1299 ++++++++++++++++ ww/libhv/event/kcp/ikcp.h | 416 +++++ ww/libhv/event/kqueue.c | 174 +++ ww/libhv/event/nio.c | 515 +++++++ ww/libhv/event/nlog.c | 87 ++ ww/libhv/event/nlog.h | 44 + ww/libhv/event/noevent.c | 25 + ww/libhv/event/overlapio.c | 419 ++++++ ww/libhv/event/overlapio.h | 33 + ww/libhv/event/poll.c | 135 ++ ww/libhv/event/rudp.c | 167 +++ ww/libhv/event/rudp.h | 52 + ww/libhv/event/select.c | 169 +++ ww/libhv/event/unpack.c | 174 +++ ww/libhv/event/unpack.h | 11 + ww/libhv/event/wepoll/LICENSE | 28 + ww/libhv/event/wepoll/README.md | 202 +++ ww/libhv/event/wepoll/wepoll.c | 2253 ++++++++++++++++++++++++++++ ww/libhv/event/wepoll/wepoll.h | 113 ++ ww/libhv/hexport.h | 157 ++ ww/libhv/hv.h | 41 + ww/libhv/mqtt/mqtt_client.c | 602 ++++++++ ww/libhv/mqtt/mqtt_client.h | 335 +++++ ww/libhv/mqtt/mqtt_protocol.c | 22 + ww/libhv/mqtt/mqtt_protocol.h | 82 + ww/libhv/protocol/README.md | 10 + ww/libhv/protocol/dns.c | 336 +++++ ww/libhv/protocol/dns.h | 105 ++ ww/libhv/protocol/ftp.c | 247 +++ ww/libhv/protocol/ftp.h | 96 ++ ww/libhv/protocol/icmp.c | 125 ++ ww/libhv/protocol/icmp.h | 15 + ww/libhv/protocol/smtp.c | 256 ++++ ww/libhv/protocol/smtp.h | 74 + ww/libhv/util/README.md | 9 + ww/libhv/util/base64.c | 126 ++ ww/libhv/util/base64.h | 46 + ww/libhv/util/md5.c | 215 +++ ww/libhv/util/md5.h | 25 + ww/libhv/util/sha1.c | 313 ++++ ww/libhv/util/sha1.h | 55 + 96 files changed, 22057 insertions(+) create mode 100644 ww/libhv/.clang-format create mode 100644 ww/libhv/.clang-tidy create mode 100644 ww/libhv/.gitattributes create mode 100644 ww/libhv/.gitignore create mode 100644 ww/libhv/CMakeLists.txt create mode 100644 ww/libhv/LICENSE create mode 100644 ww/libhv/README.md create mode 100644 ww/libhv/base/README.md create mode 100644 ww/libhv/base/array.h create mode 100644 ww/libhv/base/hatomic.h create mode 100644 ww/libhv/base/hbase.c create mode 100644 ww/libhv/base/hbase.h create mode 100644 ww/libhv/base/hbuf.h create mode 100644 ww/libhv/base/hdef.h create mode 100644 ww/libhv/base/heap.h create mode 100644 ww/libhv/base/hendian.h create mode 100644 ww/libhv/base/herr.c create mode 100644 ww/libhv/base/herr.h create mode 100644 ww/libhv/base/hlog.c create mode 100644 ww/libhv/base/hlog.h create mode 100644 ww/libhv/base/hmain.c create mode 100644 ww/libhv/base/hmain.h create mode 100644 ww/libhv/base/hmath.h create mode 100644 ww/libhv/base/hmutex.h create mode 100644 ww/libhv/base/hplatform.h create mode 100644 ww/libhv/base/hproc.h create mode 100644 ww/libhv/base/hsocket.c create mode 100644 ww/libhv/base/hsocket.h create mode 100644 ww/libhv/base/hsysinfo.h create mode 100644 ww/libhv/base/hthread.h create mode 100644 ww/libhv/base/htime.c create mode 100644 ww/libhv/base/htime.h create mode 100644 ww/libhv/base/hversion.c create mode 100644 ww/libhv/base/hversion.h create mode 100644 ww/libhv/base/list.h create mode 100644 ww/libhv/base/netinet.h create mode 100644 ww/libhv/base/queue.h create mode 100644 ww/libhv/base/rbtree.c create mode 100644 ww/libhv/base/rbtree.h create mode 100644 ww/libhv/cmake/ios.toolchain.cmake create mode 100644 ww/libhv/cmake/libhvConfig.cmake create mode 100644 ww/libhv/cmake/utils.cmake create mode 100644 ww/libhv/cmake/vars.cmake create mode 100644 ww/libhv/event/README.md create mode 100644 ww/libhv/event/epoll.c create mode 100644 ww/libhv/event/evport.c create mode 100644 ww/libhv/event/hevent.c create mode 100644 ww/libhv/event/hevent.h create mode 100644 ww/libhv/event/hloop.c create mode 100644 ww/libhv/event/hloop.h create mode 100644 ww/libhv/event/iocp.c create mode 100644 ww/libhv/event/iowatcher.h create mode 100644 ww/libhv/event/kcp/LICENSE create mode 100644 ww/libhv/event/kcp/hkcp.c create mode 100644 ww/libhv/event/kcp/hkcp.h create mode 100644 ww/libhv/event/kcp/ikcp.c create mode 100644 ww/libhv/event/kcp/ikcp.h create mode 100644 ww/libhv/event/kqueue.c create mode 100644 ww/libhv/event/nio.c create mode 100644 ww/libhv/event/nlog.c create mode 100644 ww/libhv/event/nlog.h create mode 100644 ww/libhv/event/noevent.c create mode 100644 ww/libhv/event/overlapio.c create mode 100644 ww/libhv/event/overlapio.h create mode 100644 ww/libhv/event/poll.c create mode 100644 ww/libhv/event/rudp.c create mode 100644 ww/libhv/event/rudp.h create mode 100644 ww/libhv/event/select.c create mode 100644 ww/libhv/event/unpack.c create mode 100644 ww/libhv/event/unpack.h create mode 100644 ww/libhv/event/wepoll/LICENSE create mode 100644 ww/libhv/event/wepoll/README.md create mode 100644 ww/libhv/event/wepoll/wepoll.c create mode 100644 ww/libhv/event/wepoll/wepoll.h create mode 100644 ww/libhv/hexport.h create mode 100644 ww/libhv/hv.h create mode 100644 ww/libhv/mqtt/mqtt_client.c create mode 100644 ww/libhv/mqtt/mqtt_client.h create mode 100644 ww/libhv/mqtt/mqtt_protocol.c create mode 100644 ww/libhv/mqtt/mqtt_protocol.h create mode 100644 ww/libhv/protocol/README.md create mode 100644 ww/libhv/protocol/dns.c create mode 100644 ww/libhv/protocol/dns.h create mode 100644 ww/libhv/protocol/ftp.c create mode 100644 ww/libhv/protocol/ftp.h create mode 100644 ww/libhv/protocol/icmp.c create mode 100644 ww/libhv/protocol/icmp.h create mode 100644 ww/libhv/protocol/smtp.c create mode 100644 ww/libhv/protocol/smtp.h create mode 100644 ww/libhv/util/README.md create mode 100644 ww/libhv/util/base64.c create mode 100644 ww/libhv/util/base64.h create mode 100644 ww/libhv/util/md5.c create mode 100644 ww/libhv/util/md5.h create mode 100644 ww/libhv/util/sha1.c create mode 100644 ww/libhv/util/sha1.h diff --git a/ww/libhv/.clang-format b/ww/libhv/.clang-format new file mode 100644 index 00000000..80d5c60d --- /dev/null +++ b/ww/libhv/.clang-format @@ -0,0 +1,95 @@ +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlinesLeft: true +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: true +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: true + BeforeElse: true + IndentBraces: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 160 +CommentPragmas: '^ IWYU pragma:' +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: true +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] +IncludeCategories: + - Regex: '.*' + Priority: 1 + - Regex: '^".*/' + Priority: 2 + - Regex: '^<)' + Priority: 3 +IncludeIsMainRegex: '$' +IndentCaseLabels: false +IndentWidth: 4 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 4 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +ReflowComments: true +SortIncludes: false +SpaceAfterCStyleCast: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 4 +UseTab: Never +... + diff --git a/ww/libhv/.clang-tidy b/ww/libhv/.clang-tidy new file mode 100644 index 00000000..5c63cd42 --- /dev/null +++ b/ww/libhv/.clang-tidy @@ -0,0 +1,3 @@ +# silent +Checks: > + -* \ No newline at end of file diff --git a/ww/libhv/.gitattributes b/ww/libhv/.gitattributes new file mode 100644 index 00000000..25128799 --- /dev/null +++ b/ww/libhv/.gitattributes @@ -0,0 +1 @@ +cpputil/json.hpp linguist-vendored diff --git a/ww/libhv/.gitignore b/ww/libhv/.gitignore new file mode 100644 index 00000000..2c910579 --- /dev/null +++ b/ww/libhv/.gitignore @@ -0,0 +1,78 @@ +# Bazel +bazel-* +MODULE.bazel +MODULE.bazel.lock + +# Compiled Object files +*.o +*.lo +*.slo +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Compiled Static libraries +*.a +*.la +*.lai +*.lib + +# Executables +*.exe +*.out +*.app + +# cache +*~ +*.bk +*.bak +*.old +*.new + +# IDE +.vs +.vscode +.DS_Store + +tags +cscope* +.ycm* + +# generated +examples/protorpc/generated + +# output +*.pid +*.log +*.db +hconfig.h.in + +include +lib +bin +tmp +dist +test +*_test +build +config.mk +hconfig.h +html/uploads + +# msvc +*.VC.* +*.vcxproj.* +Debug +Release + +# cmake +CMakeFiles +CMakeCache.txt +cmake_install.cmake diff --git a/ww/libhv/CMakeLists.txt b/ww/libhv/CMakeLists.txt new file mode 100644 index 00000000..9e913ff1 --- /dev/null +++ b/ww/libhv/CMakeLists.txt @@ -0,0 +1,282 @@ +cmake_minimum_required(VERSION 3.6) + +project(hv VERSION 1.3.2) + +option(BUILD_SHARED "build shared library" ON) +option(BUILD_STATIC "build static library" ON) + +option(BUILD_EXAMPLES "build examples" ON) +option(BUILD_UNITTEST "build unittest" OFF) + +# see config.ini +option(WITH_PROTOCOL "compile protocol" OFF) + +option(WITH_EVPP "compile evpp" ON) +option(WITH_HTTP "compile http" ON) +option(WITH_HTTP_SERVER "compile http/server" ON) +option(WITH_HTTP_CLIENT "compile http/client" ON) +option(WITH_MQTT "compile mqtt" OFF) + +option(ENABLE_UDS "Unix Domain Socket" OFF) +option(USE_MULTIMAP "MultiMap" OFF) + +option(WITH_CURL "with curl library (deprecated)" OFF) +option(WITH_NGHTTP2 "with nghttp2 library" OFF) + +option(WITH_OPENSSL "with openssl library" OFF) +option(WITH_GNUTLS "with gnutls library" OFF) +option(WITH_MBEDTLS "with mbedtls library" OFF) + +option(WITH_KCP "compile event/kcp" OFF) + +if(WIN32) + option(WITH_WEPOLL "compile event/wepoll -> use iocp" ON) + option(ENABLE_WINDUMP "Windows MiniDumpWriteDump" OFF) + option(BUILD_FOR_MT "build for /MT" OFF) + if(BUILD_FOR_MT) + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /MTd") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd") + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /MT") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT") + endif() +endif() + +message(STATUS "CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR}") +message(STATUS "CMAKE_CURRENT_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}") +if(NOT "${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_SOURCE_DIR}") + set(BUILD_EXAMPLES OFF) +endif() + +if(IOS) + set(BUILD_SHARED OFF) + set(BUILD_EXAMPLES OFF) +endif() + +set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake;${CMAKE_MODULE_PATH}") +include(utils) +include(vars) + +# see configure +# Checks for header files +check_header("stdbool.h") +check_header("stdint.h") +check_header("stdatomic.h") +check_header("sys/types.h") +check_header("sys/stat.h") +check_header("sys/time.h") +check_header("fcntl.h") +check_header("pthread.h") +check_header("endian.h") +check_header("sys/endian.h") + +# Checks for functions +if(NOT MSVC) + set(CMAKE_REQUIRED_LIBRARIES "-pthread") +endif() +check_function("gettid" "unistd.h") +check_function("strlcpy" "string.h") +check_function("strlcat" "string.h") +check_function("clock_gettime" "time.h") +check_function("gettimeofday" "sys/time.h") +check_function("pthread_spin_lock" "pthread.h") +check_function("pthread_mutex_timedlock" "pthread.h") +check_function("sem_timedwait" "semaphore.h") +check_function("pipe" "unistd.h") +check_function("socketpair" "sys/socket.h") +check_function("eventfd" "sys/eventfd.h") +check_function("setproctitle" "unistd.h") + + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/hconfig.h.in ${CMAKE_CURRENT_SOURCE_DIR}/hconfig.h) + +# see Makefile.in +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED True) +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +set(INCDIR include) +set(SRCDIR src) +set(LIBDIR lib) +set(BINDIR bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${LIBDIR}) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${LIBDIR}) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${BINDIR}) +message(STATUS "CMAKE_LIBRARY_OUTPUT_DIRECTORY=${CMAKE_LIBRARY_OUTPUT_DIRECTORY}") + +set(INCDIRS . include 3rd/include) +set(LIBDIRS . lib 3rd/lib) +include_directories(${INCDIRS} ${SRCDIR}) +link_directories(${LIBDIRS}) + +message(STATUS "CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}") +if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + add_definitions(-DDEBUG) +else() + add_definitions(-DNDEBUG) +endif() + +if(ENABLE_UDS) + add_definitions(-DENABLE_UDS) +endif() + +if(USE_MULTIMAP) + add_definitions(-DUSE_MULTIMAP) +endif() + +if(WITH_CURL) + add_definitions(-DWITH_CURL) + set(LIBS ${LIBS} curl) + if(WIN32) + set(LIBS ${LIBS} wldap32 advapi32 crypt32) + endif() +endif() + +if(WITH_NGHTTP2) + add_definitions(-DWITH_NGHTTP2) + set(LIBS ${LIBS} nghttp2) +endif() + +if(WITH_OPENSSL) + add_definitions(-DWITH_OPENSSL) + find_package(OpenSSL) + if(OpenSSL_FOUND) + set(LIBS ${LIBS} OpenSSL::SSL OpenSSL::Crypto) + else() + set(LIBS ${LIBS} ssl crypto) + endif() +endif() + +if(WITH_GNUTLS) + add_definitions(-DWITH_GNUTLS) + set(LIBS ${LIBS} gnutls) +endif() + +if(WITH_MBEDTLS) + add_definitions(-DWITH_MBEDTLS) + set(LIBS ${LIBS} mbedtls mbedx509 mbedcrypto) +endif() + +if(WIN32) + add_definitions(-DWIN32_LEAN_AND_MEAN -D_CRT_SECURE_NO_WARNINGS -D_WIN32_WINNT=0x0600) + set(LIBS ${LIBS} secur32 crypt32 winmm iphlpapi ws2_32) + if(ENABLE_WINDUMP) + add_definitions(-DENABLE_WINDUMP) + set(LIBS ${LIBS} dbghelp) + endif() +endif() + +if(ANDROID) + set(LIBS ${LIBS} log) +elseif(UNIX) + set(LIBS ${LIBS} pthread m dl) + if(CMAKE_COMPILER_IS_GNUCC) + set(LIBS ${LIBS} rt) + endif() +endif() + +if(APPLE) + set(LIBS ${LIBS} "-framework CoreFoundation" "-framework Security") +endif() + +# see Makefile +set(ALL_SRCDIRS . base ssl event event/kcp util cpputil evpp protocol http http/client http/server mqtt) +set(CORE_SRCDIRS . base ssl event) +if(WIN32) + if(WITH_WEPOLL) + set(CORE_SRCDIRS ${CORE_SRCDIRS} event/wepoll) + endif() +endif() +if(WITH_KCP) + set(CORE_SRCDIRS ${CORE_SRCDIRS} event/kcp) +endif() +set(LIBHV_SRCDIRS ${CORE_SRCDIRS} util) +set(LIBHV_HEADERS hv.h hconfig.h hexport.h) +set(LIBHV_HEADERS ${LIBHV_HEADERS} ${BASE_HEADERS} ${SSL_HEADERS} ${EVENT_HEADERS} ${UTIL_HEADERS}) + +if(WITH_PROTOCOL) + set(LIBHV_HEADERS ${LIBHV_HEADERS} ${PROTOCOL_HEADERS}) + set(LIBHV_SRCDIRS ${LIBHV_SRCDIRS} protocol) +endif() + +if(WITH_EVPP) + set(LIBHV_HEADERS ${LIBHV_HEADERS} ${CPPUTIL_HEADERS} ${EVPP_HEADERS}) + set(LIBHV_SRCDIRS ${LIBHV_SRCDIRS} cpputil evpp) + if(WITH_HTTP) + set(LIBHV_HEADERS ${LIBHV_HEADERS} ${HTTP_HEADERS}) + set(LIBHV_SRCDIRS ${LIBHV_SRCDIRS} http) + if(WITH_NGHTTP2) + set(LIBHV_HEADERS ${LIBHV_HEADERS} ${HTTP2_HEADERS}) + endif() + if(WITH_HTTP_SERVER) + set(LIBHV_HEADERS ${LIBHV_HEADERS} ${HTTP_SERVER_HEADERS}) + set(LIBHV_SRCDIRS ${LIBHV_SRCDIRS} http/server) + endif() + if(WITH_HTTP_CLIENT) + set(LIBHV_HEADERS ${LIBHV_HEADERS} ${HTTP_CLIENT_HEADERS}) + set(LIBHV_SRCDIRS ${LIBHV_SRCDIRS} http/client) + endif() + endif() + + if(CMAKE_SYSTEM_NAME MATCHES "Linux" AND CMAKE_COMPILER_IS_GNUCC) + set(LIBS ${LIBS} stdc++) + endif() +endif() + +if(WITH_MQTT) + set(LIBHV_HEADERS ${LIBHV_HEADERS} ${MQTT_HEADERS}) + set(LIBHV_SRCDIRS ${LIBHV_SRCDIRS} mqtt) +endif() + +list_source_directories(LIBHV_SRCS ${LIBHV_SRCDIRS}) + +file(INSTALL ${LIBHV_HEADERS} DESTINATION include/hv) +file(INSTALL ${LIBHV_HEADERS} DESTINATION ${PROJECT_SOURCE_DIR}/include/hv) + +if(BUILD_SHARED) + add_library(hv SHARED ${LIBHV_SRCS}) + target_compile_definitions(hv PRIVATE HV_DYNAMICLIB) + target_include_directories(hv PRIVATE ${LIBHV_SRCDIRS} + INTERFACE $ $) + target_link_libraries(hv ${LIBS}) + install(TARGETS hv + EXPORT libhvConfig + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin) + add_custom_target(libhv DEPENDS hv) +endif() + +if(BUILD_STATIC) + add_library(hv_static STATIC ${LIBHV_SRCS}) + target_compile_definitions(hv_static PUBLIC HV_STATICLIB) + target_include_directories(hv_static PRIVATE ${LIBHV_SRCDIRS} + INTERFACE $ $) + target_link_libraries(hv_static ${LIBS}) + install(TARGETS hv_static + EXPORT libhvConfig + ARCHIVE DESTINATION lib) + add_custom_target(libhv_static DEPENDS hv_static) +endif() + +install(FILES ${LIBHV_HEADERS} DESTINATION include/hv) +install(EXPORT libhvConfig DESTINATION lib/cmake/libhv) + +if(BUILD_SHARED) + set(HV_LIBRARIES hv CACHE INTERNAL "link hv libraries") +else() + add_definitions(-DHV_STATICLIB) + set(HV_LIBRARIES hv_static ${LIBS} CACHE INTERNAL "link hv libraries") +endif() + +if(BUILD_EXAMPLES) + add_subdirectory(examples) + # for httpd -c etc/httpd.conf + file(INSTALL etc DESTINATION ${CMAKE_BINARY_DIR}) + file(INSTALL etc DESTINATION ${CMAKE_BINARY_DIR}/bin) + file(INSTALL etc DESTINATION ${CMAKE_BINARY_DIR}/examples) +endif() + +if(BUILD_UNITTEST) + add_subdirectory(unittest) +endif() diff --git a/ww/libhv/LICENSE b/ww/libhv/LICENSE new file mode 100644 index 00000000..77c29c11 --- /dev/null +++ b/ww/libhv/LICENSE @@ -0,0 +1,30 @@ +BSD 3-Clause License + +Copyright (c) 2020, ithewei +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/ww/libhv/README.md b/ww/libhv/README.md new file mode 100644 index 00000000..97cb6f9a --- /dev/null +++ b/ww/libhv/README.md @@ -0,0 +1,9 @@ +Waterwall fork of (libhv)[https://github.com/ithewei/libhv] + +changes: + +removed most of the things we did not use in waterwall (probably all of c++ code) , ssl , unpack , etc.. + +added small changes such as splice call support (not yet integrated into waterwall) + +and a try to change loop buffer to ww/shiftbuffer \ No newline at end of file diff --git a/ww/libhv/base/README.md b/ww/libhv/base/README.md new file mode 100644 index 00000000..399dc505 --- /dev/null +++ b/ww/libhv/base/README.md @@ -0,0 +1,28 @@ +## 目录结构 + +``` +. +├── array.h 动态数组 +├── hatomic.h 原子操作 +├── hbase.h 基础函数 +├── hbuf.h 缓存 +├── hdef.h 常见宏定义 +├── heap.h 堆 +├── hendian.h 大小端 +├── herr.h 错误码表 +├── hlog.h 日志 +├── hmain.h 命令行解析 +├── hmath.h 数学函数 +├── hmutex.h 线程同步锁 +├── hplatform.h 平台相关宏 +├── hproc.h 进程 +├── hsocket.h 套接字 +├── hsysinfo.h 系统信息 +├── hthread.h 线程 +├── htime.h 时间 +├── hversion.h 版本 +├── list.h 链表 +├── queue.h 队列 +└── rbtree.h 红黑树 + +``` diff --git a/ww/libhv/base/array.h b/ww/libhv/base/array.h new file mode 100644 index 00000000..4370d5b3 --- /dev/null +++ b/ww/libhv/base/array.h @@ -0,0 +1,152 @@ +#ifndef HV_ARRAY_H_ +#define HV_ARRAY_H_ + +/* + * array + * at: random access by pos + * @effective + * push_back,pop_back,del_nomove,swap + * @ineffective + * add,del + */ + +#include // for assert +#include // for NULL +#include // for malloc,realloc,free +#include // for memset,memmove + +#include "hbase.h" // for HV_ALLOC, HV_FREE + +#define ARRAY_INIT_SIZE 16 + +// #include +// typedef std::vector atype; +#define ARRAY_DECL(type, atype) \ +struct atype { \ + type* ptr; \ + size_t size; \ + size_t maxsize;\ +}; \ +typedef struct atype atype;\ +\ +static inline type* atype##_data(atype* p) {\ + return p->ptr;\ +}\ +\ +static inline int atype##_size(atype* p) {\ + return p->size;\ +}\ +\ +static inline int atype##_maxsize(atype* p) {\ + return p->maxsize;\ +}\ +\ +static inline int atype##_empty(atype* p) {\ + return p->size == 0;\ +}\ +\ +static inline type* atype##_at(atype* p, int pos) {\ + if (pos < 0) {\ + pos += p->size;\ + }\ + assert(pos >= 0 && pos < p->size);\ + return p->ptr + pos;\ +}\ +\ +static inline type* atype##_front(atype* p) {\ + return p->size == 0 ? NULL : p->ptr;\ +}\ +\ +static inline type* atype##_back(atype* p) {\ + return p->size == 0 ? NULL : p->ptr + p->size - 1;\ +}\ +\ +static inline void atype##_init(atype* p, int maxsize) {\ + p->size = 0;\ + p->maxsize = maxsize;\ + HV_ALLOC(p->ptr, sizeof(type) * maxsize);\ +}\ +\ +static inline void atype##_clear(atype* p) {\ + p->size = 0;\ + memset(p->ptr, 0, sizeof(type) * p->maxsize);\ +}\ +\ +static inline void atype##_cleanup(atype* p) {\ + HV_FREE(p->ptr);\ + p->size = p->maxsize = 0;\ +}\ +\ +static inline void atype##_resize(atype* p, int maxsize) {\ + if (maxsize == 0) maxsize = ARRAY_INIT_SIZE;\ + p->ptr = (type*)hv_realloc(p->ptr, sizeof(type) * maxsize, sizeof(type) * p->maxsize);\ + p->maxsize = maxsize;\ +}\ +\ +static inline void atype##_double_resize(atype* p) {\ + atype##_resize(p, p->maxsize * 2);\ +}\ +\ +static inline void atype##_push_back(atype* p, type* elem) {\ + if (p->size == p->maxsize) {\ + atype##_double_resize(p);\ + }\ + p->ptr[p->size] = *elem;\ + p->size++;\ +}\ +\ +static inline void atype##_pop_back(atype* p) {\ + assert(p->size > 0);\ + p->size--;\ +}\ +\ +static inline void atype##_add(atype* p, type* elem, int pos) {\ + if (pos < 0) {\ + pos += p->size;\ + }\ + assert(pos >= 0 && pos <= p->size);\ + if (p->size == p->maxsize) {\ + atype##_double_resize(p);\ + }\ + if (pos < p->size) {\ + memmove(p->ptr + pos+1, p->ptr + pos, sizeof(type) * (p->size - pos));\ + }\ + p->ptr[pos] = *elem;\ + p->size++;\ +}\ +\ +static inline void atype##_del(atype* p, int pos) {\ + if (pos < 0) {\ + pos += p->size;\ + }\ + assert(pos >= 0 && pos < p->size);\ + p->size--;\ + if (pos < p->size) {\ + memmove(p->ptr + pos, p->ptr + pos+1, sizeof(type) * (p->size - pos));\ + }\ +}\ +\ +static inline void atype##_del_nomove(atype* p, int pos) {\ + if (pos < 0) {\ + pos += p->size;\ + }\ + assert(pos >= 0 && pos < p->size);\ + p->size--;\ + if (pos < p->size) {\ + p->ptr[pos] = p->ptr[p->size];\ + }\ +}\ +\ +static inline void atype##_swap(atype* p, int pos1, int pos2) {\ + if (pos1 < 0) {\ + pos1 += p->size;\ + }\ + if (pos2 < 0) {\ + pos2 += p->size;\ + }\ + type tmp = p->ptr[pos1];\ + p->ptr[pos1] = p->ptr[pos2];\ + p->ptr[pos2] = tmp;\ +}\ + +#endif // HV_ARRAY_H_ diff --git a/ww/libhv/base/hatomic.h b/ww/libhv/base/hatomic.h new file mode 100644 index 00000000..eea6a617 --- /dev/null +++ b/ww/libhv/base/hatomic.h @@ -0,0 +1,130 @@ +#ifndef HV_ATOMIC_H_ +#define HV_ATOMIC_H_ + +#ifdef __cplusplus + +// c++11 +#include + +using std::atomic_flag; +using std::atomic_long; + +#define ATOMIC_FLAG_TEST_AND_SET(p) ((p)->test_and_set()) +#define ATOMIC_FLAG_CLEAR(p) ((p)->clear()) + +#else + +#include "hplatform.h" // for HAVE_STDATOMIC_H +#if HAVE_STDATOMIC_H + +// c11 +#include + +#define ATOMIC_FLAG_TEST_AND_SET atomic_flag_test_and_set +#define ATOMIC_FLAG_CLEAR atomic_flag_clear +#define ATOMIC_ADD atomic_fetch_add +#define ATOMIC_SUB atomic_fetch_sub +#define ATOMIC_INC(p) ATOMIC_ADD(p, 1) +#define ATOMIC_DEC(p) ATOMIC_SUB(p, 1) + +#else + +typedef volatile bool atomic_bool; +typedef volatile char atomic_char; +typedef volatile unsigned char atomic_uchar; +typedef volatile short atomic_short; +typedef volatile unsigned short atomic_ushort; +typedef volatile int atomic_int; +typedef volatile unsigned int atomic_uint; +typedef volatile long atomic_long; +typedef volatile unsigned long atomic_ulong; +typedef volatile long long atomic_llong; +typedef volatile unsigned long long atomic_ullong; +typedef volatile size_t atomic_size_t; + +typedef struct atomic_flag { atomic_bool _Value; } atomic_flag; + +#ifdef _WIN32 + +#define ATOMIC_FLAG_TEST_AND_SET atomic_flag_test_and_set +static inline bool atomic_flag_test_and_set(atomic_flag* p) { + // return InterlockedIncrement((LONG*)&p->_Value, 1); + return InterlockedCompareExchange((LONG*)&p->_Value, 1, 0); +} + +#define ATOMIC_ADD InterlockedAdd +#define ATOMIC_SUB(p, n) InterlockedAdd(p, -n) +#define ATOMIC_INC InterlockedIncrement +#define ATOMIC_DEC InterlockedDecrement + +#elif defined(__GNUC__) + +#define ATOMIC_FLAG_TEST_AND_SET atomic_flag_test_and_set +static inline bool atomic_flag_test_and_set(atomic_flag* p) { + return !__sync_bool_compare_and_swap(&p->_Value, 0, 1); +} + +#define ATOMIC_ADD __sync_fetch_and_add +#define ATOMIC_SUB __sync_fetch_and_sub +#define ATOMIC_INC(p) ATOMIC_ADD(p, 1) +#define ATOMIC_DEC(p) ATOMIC_SUB(p, 1) + +#endif + +#endif // HAVE_STDATOMIC_H + +#endif // __cplusplus + +#ifndef ATOMIC_FLAG_INIT +#define ATOMIC_FLAG_INIT { 0 } +#endif + +#ifndef ATOMIC_VAR_INIT +#define ATOMIC_VAR_INIT(value) (value) +#endif + +#ifndef ATOMIC_FLAG_TEST_AND_SET +#define ATOMIC_FLAG_TEST_AND_SET atomic_flag_test_and_set +static inline bool atomic_flag_test_and_set(atomic_flag* p) { + bool ret = p->_Value; + p->_Value = 1; + return ret; +} +#endif + +#ifndef ATOMIC_FLAG_CLEAR +#define ATOMIC_FLAG_CLEAR atomic_flag_clear +static inline void atomic_flag_clear(atomic_flag* p) { + p->_Value = 0; +} +#endif + +#ifndef ATOMIC_ADD +#define ATOMIC_ADD(p, n) (*(p) += (n)) +#endif + +#ifndef ATOMIC_SUB +#define ATOMIC_SUB(p, n) (*(p) -= (n)) +#endif + +#ifndef ATOMIC_INC +#define ATOMIC_INC(p) ((*(p))++) +#endif + +#ifndef ATOMIC_DEC +#define ATOMIC_DEC(p) ((*(p))--) +#endif + +typedef atomic_flag hatomic_flag_t; +#define HATOMIC_FLAG_INIT ATOMIC_FLAG_INIT +#define hatomic_flag_test_and_set ATOMIC_FLAG_TEST_AND_SET +#define hatomic_flag_clear ATOMIC_FLAG_CLEAR + +typedef atomic_long hatomic_t; +#define HATOMIC_VAR_INIT ATOMIC_VAR_INIT +#define hatomic_add ATOMIC_ADD +#define hatomic_sub ATOMIC_SUB +#define hatomic_inc ATOMIC_INC +#define hatomic_dec ATOMIC_DEC + +#endif // HV_ATOMIC_H_ diff --git a/ww/libhv/base/hbase.c b/ww/libhv/base/hbase.c new file mode 100644 index 00000000..a7d600da --- /dev/null +++ b/ww/libhv/base/hbase.c @@ -0,0 +1,519 @@ +#include "hbase.h" + +#ifdef OS_DARWIN +#include // for _NSGetExecutablePath +#endif + +#include "hatomic.h" + +#ifndef RAND_MAX +#define RAND_MAX 2147483647 +#endif + +static hatomic_t s_alloc_cnt = HATOMIC_VAR_INIT(0); +static hatomic_t s_free_cnt = HATOMIC_VAR_INIT(0); + +long hv_alloc_cnt() { + return s_alloc_cnt; +} + +long hv_free_cnt() { + return s_free_cnt; +} + +void* hv_malloc(size_t size) { + hatomic_inc(&s_alloc_cnt); + void* ptr = malloc(size); + if (!ptr) { + fprintf(stderr, "malloc failed!\n"); + exit(-1); + } + return ptr; +} + +void* hv_realloc(void* oldptr, size_t newsize, size_t oldsize) { + hatomic_inc(&s_alloc_cnt); + if (oldptr) hatomic_inc(&s_free_cnt); + void* ptr = realloc(oldptr, newsize); + if (!ptr) { + fprintf(stderr, "realloc failed!\n"); + exit(-1); + } + if (newsize > oldsize) { + memset((char*)ptr + oldsize, 0, newsize - oldsize); + } + return ptr; +} + +void* hv_calloc(size_t nmemb, size_t size) { + hatomic_inc(&s_alloc_cnt); + void* ptr = calloc(nmemb, size); + if (!ptr) { + fprintf(stderr, "calloc failed!\n"); + exit(-1); + } + return ptr; +} + +void* hv_zalloc(size_t size) { + hatomic_inc(&s_alloc_cnt); + void* ptr = malloc(size); + if (!ptr) { + fprintf(stderr, "malloc failed!\n"); + exit(-1); + } + memset(ptr, 0, size); + return ptr; +} + +void hv_free(void* ptr) { + if (ptr) { + free(ptr); + ptr = NULL; + hatomic_inc(&s_free_cnt); + } +} + +char* hv_strupper(char* str) { + char* p = str; + while (*p != '\0') { + if (*p >= 'a' && *p <= 'z') { + *p &= ~0x20; + } + ++p; + } + return str; +} + +char* hv_strlower(char* str) { + char* p = str; + while (*p != '\0') { + if (*p >= 'A' && *p <= 'Z') { + *p |= 0x20; + } + ++p; + } + return str; +} + +char* hv_strreverse(char* str) { + if (str == NULL) return NULL; + char* b = str; + char* e = str; + while(*e) {++e;} + --e; + char tmp; + while (e > b) { + tmp = *e; + *e = *b; + *b = tmp; + --e; + ++b; + } + return str; +} + +// n = sizeof(dest_buf) +char* hv_strncpy(char* dest, const char* src, size_t n) { + assert(dest != NULL && src != NULL); + char* ret = dest; + while (*src != '\0' && --n > 0) { + *dest++ = *src++; + } + *dest = '\0'; + return ret; +} + +// n = sizeof(dest_buf) +char* hv_strncat(char* dest, const char* src, size_t n) { + assert(dest != NULL && src != NULL); + char* ret = dest; + while (*dest) {++dest;--n;} + while (*src != '\0' && --n > 0) { + *dest++ = *src++; + } + *dest = '\0'; + return ret; +} + +bool hv_strstartswith(const char* str, const char* start) { + assert(str != NULL && start != NULL); + while (*str && *start && *str == *start) { + ++str; + ++start; + } + return *start == '\0'; +} + +bool hv_strendswith(const char* str, const char* end) { + assert(str != NULL && end != NULL); + int len1 = 0; + int len2 = 0; + while (*str) {++str; ++len1;} + while (*end) {++end; ++len2;} + if (len1 < len2) return false; + while (len2-- > 0) { + --str; + --end; + if (*str != *end) { + return false; + } + } + return true; +} + +bool hv_strcontains(const char* str, const char* sub) { + assert(str != NULL && sub != NULL); + return strstr(str, sub) != NULL; +} + +bool hv_wildcard_match(const char* str, const char* pattern) { + assert(str != NULL && pattern != NULL); + bool match = false; + while (*str && *pattern) { + if (*pattern == '*') { + match = hv_strendswith(str, pattern + 1); + break; + } else if (*str != *pattern) { + match = false; + break; + } else { + ++str; + ++pattern; + } + } + return match ? match : (*str == '\0' && *pattern == '\0'); +} + +char* hv_strnchr(const char* s, char c, size_t n) { + assert(s != NULL); + const char* p = s; + while (*p != '\0' && n-- > 0) { + if (*p == c) return (char*)p; + ++p; + } + return NULL; +} + +char* hv_strrchr_dir(const char* filepath) { + char* p = (char*)filepath; + while (*p) ++p; + while (--p >= filepath) { +#ifdef OS_WIN + if (*p == '/' || *p == '\\') +#else + if (*p == '/') +#endif + return p; + } + return NULL; +} + +const char* hv_basename(const char* filepath) { + const char* pos = hv_strrchr_dir(filepath); + return pos ? pos+1 : filepath; +} + +const char* hv_suffixname(const char* filename) { + const char* pos = hv_strrchr_dot(filename); + return pos ? pos+1 : ""; +} + +int hv_mkdir_p(const char* dir) { + if (access(dir, 0) == 0) { + return EEXIST; + } + char tmp[MAX_PATH] = {0}; + hv_strncpy(tmp, dir, sizeof(tmp)); + char* p = tmp; + char delim = '/'; + while (*p) { +#ifdef OS_WIN + if (*p == '/' || *p == '\\') { + delim = *p; +#else + if (*p == '/') { +#endif + *p = '\0'; + hv_mkdir(tmp); + *p = delim; + } + ++p; + } + if (hv_mkdir(tmp) != 0) { + return EPERM; + } + return 0; +} + +int hv_rmdir_p(const char* dir) { + if (access(dir, 0) != 0) { + return ENOENT; + } + if (rmdir(dir) != 0) { + return EPERM; + } + char tmp[MAX_PATH] = {0}; + hv_strncpy(tmp, dir, sizeof(tmp)); + char* p = tmp; + while (*p) ++p; + while (--p >= tmp) { +#ifdef OS_WIN + if (*p == '/' || *p == '\\') { +#else + if (*p == '/') { +#endif + *p = '\0'; + if (rmdir(tmp) != 0) { + return 0; + } + } + } + return 0; +} + +bool hv_exists(const char* path) { + return access(path, 0) == 0; +} + +bool hv_isdir(const char* path) { + if (access(path, 0) != 0) return false; + struct stat st; + memset(&st, 0, sizeof(st)); + stat(path, &st); + return S_ISDIR(st.st_mode); +} + +bool hv_isfile(const char* path) { + if (access(path, 0) != 0) return false; + struct stat st; + memset(&st, 0, sizeof(st)); + stat(path, &st); + return S_ISREG(st.st_mode); +} + +bool hv_islink(const char* path) { +#ifdef OS_WIN + return hv_isdir(path) && (GetFileAttributes(path) & FILE_ATTRIBUTE_REPARSE_POINT); +#else + if (access(path, 0) != 0) return false; + struct stat st; + memset(&st, 0, sizeof(st)); + lstat(path, &st); + return S_ISLNK(st.st_mode); +#endif +} + +size_t hv_filesize(const char* filepath) { + struct stat st; + memset(&st, 0, sizeof(st)); + stat(filepath, &st); + return st.st_size; +} + +char* get_executable_path(char* buf, int size) { +#ifdef OS_WIN + GetModuleFileName(NULL, buf, size); +#elif defined(OS_LINUX) + if (readlink("/proc/self/exe", buf, size) == -1) { + return NULL; + } +#elif defined(OS_DARWIN) + _NSGetExecutablePath(buf, (uint32_t*)&size); +#endif + return buf; +} + +char* get_executable_dir(char* buf, int size) { + char filepath[MAX_PATH] = {0}; + get_executable_path(filepath, sizeof(filepath)); + char* pos = hv_strrchr_dir(filepath); + if (pos) { + *pos = '\0'; + strncpy(buf, filepath, size); + } + return buf; +} + +char* get_executable_file(char* buf, int size) { + char filepath[MAX_PATH] = {0}; + get_executable_path(filepath, sizeof(filepath)); + char* pos = hv_strrchr_dir(filepath); + if (pos) { + strncpy(buf, pos+1, size); + } + return buf; +} + +char* get_run_dir(char* buf, int size) { + return getcwd(buf, size); +} + +int hv_rand(int min, int max) { + static int s_seed = 0; + assert(max > min); + + if (s_seed == 0) { + s_seed = time(NULL); + srand(s_seed); + } + + int _rand = rand(); + _rand = min + (int) ((double) ((double) (max) - (min) + 1.0) * ((_rand) / ((RAND_MAX) + 1.0))); + return _rand; +} + +char* hv_random_string(char *buf, int len) { + static char s_characters[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', + 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + }; + int i = 0; + for (; i < len; i++) { + buf[i] = s_characters[hv_rand(0, sizeof(s_characters) - 1)]; + } + buf[i] = '\0'; + return buf; +} + +bool hv_getboolean(const char* str) { + if (str == NULL) return false; + int len = strlen(str); + if (len == 0) return false; + switch (len) { + case 1: return *str == '1' || *str == 'y' || *str == 'Y'; + case 2: return stricmp(str, "on") == 0; + case 3: return stricmp(str, "yes") == 0; + case 4: return stricmp(str, "true") == 0; + case 6: return stricmp(str, "enable") == 0; + default: return false; + } +} + +size_t hv_parse_size(const char* str) { + size_t size = 0, n = 0; + const char* p = str; + char c; + while ((c = *p) != '\0') { + if (c >= '0' && c <= '9') { + n = n * 10 + c - '0'; + } else { + switch (c) { + case 'K': case 'k': n <<= 10; break; + case 'M': case 'm': n <<= 20; break; + case 'G': case 'g': n <<= 30; break; + case 'T': case 't': n <<= 40; break; + default: break; + } + size += n; + n = 0; + } + ++p; + } + return size + n; +} + +time_t hv_parse_time(const char* str) { + time_t time = 0, n = 0; + const char* p = str; + char c; + while ((c = *p) != '\0') { + if (c >= '0' && c <= '9') { + n = n * 10 + c - '0'; + } else { + switch (c) { + case 's': break; + case 'm': n *= 60; break; + case 'h': n *= 60 * 60; break; + case 'd': n *= 24 * 60 * 60; break; + case 'w': n *= 7 * 24 * 60 * 60; break; + default: break; + } + time += n; + n = 0; + } + ++p; + } + return time + n; +} + +int hv_parse_url(hurl_t* stURL, const char* strURL) { + if (stURL == NULL || strURL == NULL) return -1; + memset(stURL, 0, sizeof(hurl_t)); + const char* begin = strURL; + const char* end = strURL; + while (*end != '\0') ++end; + if (end - begin > 65535) return -2; + // scheme:// + const char* sp = strURL; + const char* ep = strstr(sp, "://"); + if (ep) { + // stURL->fields[HV_URL_SCHEME].off = sp - begin; + stURL->fields[HV_URL_SCHEME].len = ep - sp; + sp = ep + 3; + } + // user:pswd@host:port + ep = strchr(sp, '/'); + if (ep == NULL) ep = end; + const char* user = sp; + const char* host = sp; + const char* pos = hv_strnchr(sp, '@', ep - sp); + if (pos) { + // user:pswd + const char* pswd = hv_strnchr(user, ':', pos - user); + if (pswd) { + stURL->fields[HV_URL_PASSWORD].off = pswd + 1 - begin; + stURL->fields[HV_URL_PASSWORD].len = pos - pswd - 1; + } else { + pswd = pos; + } + stURL->fields[HV_URL_USERNAME].off = user - begin; + stURL->fields[HV_URL_USERNAME].len = pswd - user; + // @ + host = pos + 1; + } + // port + const char* port = hv_strnchr(host, ':', ep - host); + if (port) { + stURL->fields[HV_URL_PORT].off = port + 1 - begin; + stURL->fields[HV_URL_PORT].len = ep - port - 1; + // atoi + for (unsigned short i = 1; i <= stURL->fields[HV_URL_PORT].len; ++i) { + stURL->port = stURL->port * 10 + (port[i] - '0'); + } + } else { + port = ep; + // set default port + stURL->port = 80; + if (stURL->fields[HV_URL_SCHEME].len > 0) { + if (strncmp(strURL, "https://", 8) == 0) { + stURL->port = 443; + } + } + } + // host + stURL->fields[HV_URL_HOST].off = host - begin; + stURL->fields[HV_URL_HOST].len = port - host; + if (ep == end) return 0; + // /path + sp = ep; + ep = strchr(sp, '?'); + if (ep == NULL) ep = end; + stURL->fields[HV_URL_PATH].off = sp - begin; + stURL->fields[HV_URL_PATH].len = ep - sp; + if (ep == end) return 0; + // ?query + sp = ep + 1; + ep = strchr(sp, '#'); + if (ep == NULL) ep = end; + stURL->fields[HV_URL_QUERY].off = sp - begin; + stURL->fields[HV_URL_QUERY].len = ep - sp; + if (ep == end) return 0; + // #fragment + sp = ep + 1; + ep = end; + stURL->fields[HV_URL_FRAGMENT].off = sp - begin; + stURL->fields[HV_URL_FRAGMENT].len = ep - sp; + return 0; +} diff --git a/ww/libhv/base/hbase.h b/ww/libhv/base/hbase.h new file mode 100644 index 00000000..1a9041af --- /dev/null +++ b/ww/libhv/base/hbase.h @@ -0,0 +1,144 @@ +#ifndef HV_BASE_H_ +#define HV_BASE_H_ + +#include "hexport.h" +#include "hplatform.h" // for bool +#include "hdef.h" // for printd + +BEGIN_EXTERN_C + +//--------------------alloc/free--------------------------- +HV_EXPORT void* hv_malloc(size_t size); +HV_EXPORT void* hv_realloc(void* oldptr, size_t newsize, size_t oldsize); +HV_EXPORT void* hv_calloc(size_t nmemb, size_t size); +HV_EXPORT void* hv_zalloc(size_t size); +HV_EXPORT void hv_free(void* ptr); + +#define HV_ALLOC(ptr, size)\ + do {\ + *(void**)&(ptr) = hv_zalloc(size);\ + printd("alloc(%p, size=%llu)\tat [%s:%d:%s]\n", ptr, (unsigned long long)size, __FILE__, __LINE__, __FUNCTION__);\ + } while(0) + +#define HV_ALLOC_SIZEOF(ptr) HV_ALLOC(ptr, sizeof(*(ptr))) + +#define HV_FREE(ptr)\ + do {\ + if (ptr) {\ + hv_free(ptr);\ + printd("free( %p )\tat [%s:%d:%s]\n", ptr, __FILE__, __LINE__, __FUNCTION__);\ + ptr = NULL;\ + }\ + } while(0) + +#define STACK_OR_HEAP_ALLOC(ptr, size, stack_size)\ + unsigned char _stackbuf_[stack_size] = { 0 };\ + if ((size) > (stack_size)) {\ + HV_ALLOC(ptr, size);\ + } else {\ + *(unsigned char**)&(ptr) = _stackbuf_;\ + } + +#define STACK_OR_HEAP_FREE(ptr)\ + if ((unsigned char*)(ptr) != _stackbuf_) {\ + HV_FREE(ptr);\ + } + +#define HV_DEFAULT_STACKBUF_SIZE 1024 +#define HV_STACK_ALLOC(ptr, size) STACK_OR_HEAP_ALLOC(ptr, size, HV_DEFAULT_STACKBUF_SIZE) +#define HV_STACK_FREE(ptr) STACK_OR_HEAP_FREE(ptr) + +HV_EXPORT long hv_alloc_cnt(); +HV_EXPORT long hv_free_cnt(); +HV_INLINE void hv_memcheck(void) { + printf("Memcheck => alloc:%ld free:%ld\n", hv_alloc_cnt(), hv_free_cnt()); +} +#define HV_MEMCHECK atexit(hv_memcheck); + +//--------------------string------------------------------- +HV_EXPORT char* hv_strupper(char* str); +HV_EXPORT char* hv_strlower(char* str); +HV_EXPORT char* hv_strreverse(char* str); + +HV_EXPORT bool hv_strstartswith(const char* str, const char* start); +HV_EXPORT bool hv_strendswith(const char* str, const char* end); +HV_EXPORT bool hv_strcontains(const char* str, const char* sub); +HV_EXPORT bool hv_wildcard_match(const char* str, const char* pattern); + +// strncpy n = sizeof(dest_buf)-1 +// hv_strncpy n = sizeof(dest_buf) +HV_EXPORT char* hv_strncpy(char* dest, const char* src, size_t n); + +// strncat n = sizeof(dest_buf)-1-strlen(dest) +// hv_strncpy n = sizeof(dest_buf) +HV_EXPORT char* hv_strncat(char* dest, const char* src, size_t n); + +#if !HAVE_STRLCPY +#define strlcpy hv_strncpy +#endif + +#if !HAVE_STRLCAT +#define strlcat hv_strncat +#endif + +HV_EXPORT char* hv_strnchr(const char* s, char c, size_t n); + +#define hv_strrchr_dot(str) strrchr(str, '.') +HV_EXPORT char* hv_strrchr_dir(const char* filepath); + +// basename +HV_EXPORT const char* hv_basename(const char* filepath); +HV_EXPORT const char* hv_suffixname(const char* filename); +// mkdir -p +HV_EXPORT int hv_mkdir_p(const char* dir); +// rmdir -p +HV_EXPORT int hv_rmdir_p(const char* dir); +// path +HV_EXPORT bool hv_exists(const char* path); +HV_EXPORT bool hv_isdir(const char* path); +HV_EXPORT bool hv_isfile(const char* path); +HV_EXPORT bool hv_islink(const char* path); +HV_EXPORT size_t hv_filesize(const char* filepath); + +HV_EXPORT char* get_executable_path(char* buf, int size); +HV_EXPORT char* get_executable_dir(char* buf, int size); +HV_EXPORT char* get_executable_file(char* buf, int size); +HV_EXPORT char* get_run_dir(char* buf, int size); + +// random +HV_EXPORT int hv_rand(int min, int max); +HV_EXPORT char* hv_random_string(char *buf, int len); + +// 1 y on yes true enable => true +HV_EXPORT bool hv_getboolean(const char* str); +// 1T2G3M4K5B => ?B +HV_EXPORT size_t hv_parse_size(const char* str); +// 1w2d3h4m5s => ?s +HV_EXPORT time_t hv_parse_time(const char* str); + +// scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment] +typedef enum { + HV_URL_SCHEME, + HV_URL_USERNAME, + HV_URL_PASSWORD, + HV_URL_HOST, + HV_URL_PORT, + HV_URL_PATH, + HV_URL_QUERY, + HV_URL_FRAGMENT, + HV_URL_FIELD_NUM, +} hurl_field_e; + +typedef struct hurl_s { + struct { + unsigned short off; + unsigned short len; + } fields[HV_URL_FIELD_NUM]; + unsigned short port; +} hurl_t; + +HV_EXPORT int hv_parse_url(hurl_t* stURL, const char* strURL); + +END_EXTERN_C + +#endif // HV_BASE_H_ diff --git a/ww/libhv/base/hbuf.h b/ww/libhv/base/hbuf.h new file mode 100644 index 00000000..903e164b --- /dev/null +++ b/ww/libhv/base/hbuf.h @@ -0,0 +1,257 @@ +#ifndef HV_BUF_H_ +#define HV_BUF_H_ + +#include "hdef.h" // for MAX +#include "hbase.h" // for HV_ALLOC, HV_FREE + +typedef struct hbuf_s { + char* base; + size_t len; + +#ifdef __cplusplus + hbuf_s() { + base = NULL; + len = 0; + } + + hbuf_s(void* data, size_t len) { + this->base = (char*)data; + this->len = len; + } +#endif +} hbuf_t; + +typedef struct offset_buf_s { + char* base; + size_t len; + size_t offset; +#ifdef __cplusplus + offset_buf_s() { + base = NULL; + len = 0; + offset = 0; + } + + offset_buf_s(void* data, size_t len) { + this->base = (char*)data; + this->len = len; + offset = 0; + } +#endif +} offset_buf_t; + +typedef struct fifo_buf_s { + char* base; + size_t len; + size_t head; + size_t tail; +#ifdef __cplusplus + fifo_buf_s() { + base = NULL; + len = 0; + head = tail = 0; + } + + fifo_buf_s(void* data, size_t len) { + this->base = (char*)data; + this->len = len; + head = tail = 0; + } +#endif +} fifo_buf_t; + +#ifdef __cplusplus +class HBuf : public hbuf_t { +public: + HBuf() : hbuf_t() { + cleanup_ = false; + } + HBuf(void* data, size_t len) : hbuf_t(data, len) { + cleanup_ = false; + } + HBuf(size_t cap) { resize(cap); } + + virtual ~HBuf() { + cleanup(); + } + + void* data() { return base; } + size_t size() { return len; } + + bool isNull() { return base == NULL || len == 0; } + + void cleanup() { + if (cleanup_) { + HV_FREE(base); + len = 0; + cleanup_ = false; + } + } + + void resize(size_t cap) { + if (cap == len) return; + + if (base == NULL) { + HV_ALLOC(base, cap); + } + else { + base = (char*)hv_realloc(base, cap, len); + } + len = cap; + cleanup_ = true; + } + + void copy(void* data, size_t len) { + resize(len); + memcpy(base, data, len); + } + + void copy(hbuf_t* buf) { + copy(buf->base, buf->len); + } + +private: + bool cleanup_; +}; + +// VL: Variable-Length +class HVLBuf : public HBuf { +public: + HVLBuf() : HBuf() {_offset = _size = 0;} + HVLBuf(void* data, size_t len) : HBuf(data, len) {_offset = 0; _size = len;} + HVLBuf(size_t cap) : HBuf(cap) {_offset = _size = 0;} + virtual ~HVLBuf() {} + + char* data() { return base + _offset; } + size_t size() { return _size; } + + void push_front(void* ptr, size_t len) { + if (len > this->len - _size) { + size_t newsize = MAX(this->len, len)*2; + resize(newsize); + } + + if (_offset < len) { + // move => end + memmove(base+this->len-_size, data(), _size); + _offset = this->len-_size; + } + + memcpy(data()-len, ptr, len); + _offset -= len; + _size += len; + } + + void push_back(void* ptr, size_t len) { + if (len > this->len - _size) { + size_t newsize = MAX(this->len, len)*2; + resize(newsize); + } + else if (len > this->len - _offset - _size) { + // move => start + memmove(base, data(), _size); + _offset = 0; + } + memcpy(data()+_size, ptr, len); + _size += len; + } + + void pop_front(void* ptr, size_t len) { + if (len <= _size) { + if (ptr) { + memcpy(ptr, data(), len); + } + _offset += len; + if (_offset >= this->len) _offset = 0; + _size -= len; + } + } + + void pop_back(void* ptr, size_t len) { + if (len <= _size) { + if (ptr) { + memcpy(ptr, data()+_size-len, len); + } + _size -= len; + } + } + + void clear() { + _offset = _size = 0; + } + + void prepend(void* ptr, size_t len) { + push_front(ptr, len); + } + + void append(void* ptr, size_t len) { + push_back(ptr, len); + } + + void insert(void* ptr, size_t len) { + push_back(ptr, len); + } + + void remove(size_t len) { + pop_front(NULL, len); + } + +private: + size_t _offset; + size_t _size; +}; + +class HRingBuf : public HBuf { +public: + HRingBuf() : HBuf() {_head = _tail = _size = 0;} + HRingBuf(size_t cap) : HBuf(cap) {_head = _tail = _size = 0;} + virtual ~HRingBuf() {} + + char* alloc(size_t len) { + char* ret = NULL; + if (_head < _tail || _size == 0) { + // [_tail, this->len) && [0, _head) + if (this->len - _tail >= len) { + ret = base + _tail; + _tail += len; + if (_tail == this->len) _tail = 0; + } + else if (_head >= len) { + ret = base; + _tail = len; + } + } + else { + // [_tail, _head) + if (_head - _tail >= len) { + ret = base + _tail; + _tail += len; + } + } + _size += ret ? len : 0; + return ret; + } + + void free(size_t len) { + _size -= len; + if (len <= this->len - _head) { + _head += len; + if (_head == this->len) _head = 0; + } + else { + _head = len; + } + } + + void clear() {_head = _tail = _size = 0;} + + size_t size() {return _size;} + +private: + size_t _head; + size_t _tail; + size_t _size; +}; +#endif + +#endif // HV_BUF_H_ diff --git a/ww/libhv/base/hdef.h b/ww/libhv/base/hdef.h new file mode 100644 index 00000000..0f79c75f --- /dev/null +++ b/ww/libhv/base/hdef.h @@ -0,0 +1,271 @@ +#ifndef HV_DEF_H_ +#define HV_DEF_H_ + +#include "hplatform.h" + +#ifndef ABS +#define ABS(n) ((n) > 0 ? (n) : -(n)) +#endif + +#ifndef NABS +#define NABS(n) ((n) < 0 ? (n) : -(n)) +#endif + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a))) +#endif + +#ifndef BITSET +#define BITSET(p, n) (*(p) |= (1u << (n))) +#endif + +#ifndef BITCLR +#define BITCLR(p, n) (*(p) &= ~(1u << (n))) +#endif + +#ifndef BITGET +#define BITGET(i, n) ((i) & (1u << (n))) +#endif + +/* +#ifndef CR +#define CR '\r' +#endif + +#ifndef LF +#define LF '\n' +#endif + +#ifndef CRLF +#define CRLF "\r\n" +#endif +*/ + +#define FLOAT_PRECISION 1e-6 +#define FLOAT_EQUAL_ZERO(f) (ABS(f) < FLOAT_PRECISION) + +#ifndef INFINITE +#define INFINITE (uint32_t)-1 +#endif + +/* +ASCII: +[0, 0x20) control-charaters +[0x20, 0x7F) printable-charaters + +0x0A => LF +0x0D => CR +0x20 => SPACE +0x7F => DEL + +[0x09, 0x0D] => \t\n\v\f\r +[0x30, 0x39] => 0~9 +[0x41, 0x5A] => A~Z +[0x61, 0x7A] => a~z +*/ + +#ifndef IS_ALPHA +#define IS_ALPHA(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z')) +#endif + +// NOTE: IS_NUM conflicts with mysql.h +#ifndef IS_DIGIT +#define IS_DIGIT(c) ((c) >= '0' && (c) <= '9') +#endif + +#ifndef IS_ALPHANUM +#define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_DIGIT(c)) +#endif + +#ifndef IS_CNTRL +#define IS_CNTRL(c) ((c) >= 0 && (c) < 0x20) +#endif + +#ifndef IS_GRAPH +#define IS_GRAPH(c) ((c) >= 0x20 && (c) < 0x7F) +#endif + +#ifndef IS_HEX +#define IS_HEX(c) (IS_DIGIT(c) || ((c) >= 'a' && (c) <= 'f') || ((c) >= 'A' && (c) <= 'F')) +#endif + +#ifndef IS_LOWER +#define IS_LOWER(c) (((c) >= 'a' && (c) <= 'z')) +#endif + +#ifndef IS_UPPER +#define IS_UPPER(c) (((c) >= 'A' && (c) <= 'Z')) +#endif + +#ifndef LOWER +#define LOWER(c) ((c) | 0x20) +#endif + +#ifndef UPPER +#define UPPER(c) ((c) & ~0x20) +#endif + +// LD, LU, LLD, LLU for explicit conversion of integer +// #ifndef LD +// #define LD(v) ((long)(v)) +// #endif + +// #ifndef LU +// #define LU(v) ((unsigned long)(v)) +// #endif + +#ifndef LLD +#define LLD(v) ((long long)(v)) +#endif + +#ifndef LLU +#define LLU(v) ((unsigned long long)(v)) +#endif + +#ifndef _WIN32 + +// MAKEWORD, HIBYTE, LOBYTE +#ifndef MAKEWORD +#define MAKEWORD(h, l) ( (((WORD)h) << 8) | (l & 0xff) ) +#endif + +#ifndef HIBYTE +#define HIBYTE(w) ( (BYTE)(((WORD)w) >> 8) ) +#endif + +#ifndef LOBYTE +#define LOBYTE(w) ( (BYTE)(w & 0xff) ) +#endif + +// MAKELONG, HIWORD, LOWORD +#ifndef MAKELONG +#define MAKELONG(h, l) ( ((int32_t)h) << 16 | (l & 0xffff) ) +#endif + +#ifndef HIWORD +#define HIWORD(n) ( (WORD)(((int32_t)n) >> 16) ) +#endif + +#ifndef LOWORD +#define LOWORD(n) ( (WORD)(n & 0xffff) ) +#endif + +#endif // _WIN32 + +// MAKEINT64, HIINT, LOINT +#ifndef MAKEINT64 +#define MAKEINT64(h, l) ( ((int64_t)h) << 32 | (l & 0xffffffff) ) +#endif + +#ifndef HIINT +#define HIINT(n) ( (int32_t)(((int64_t)n) >> 32) ) +#endif + +#ifndef LOINT +#define LOINT(n) ( (int32_t)(n & 0xffffffff) ) +#endif + +#ifndef MAKE_FOURCC +#define MAKE_FOURCC(a, b, c, d) \ +( ((uint32)d) | ( ((uint32)c) << 8 ) | ( ((uint32)b) << 16 ) | ( ((uint32)a) << 24 ) ) +#endif + +#ifndef MAX +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#endif + +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +#ifndef LIMIT +#define LIMIT(lower, v, upper) ((v) < (lower) ? (lower) : (v) > (upper) ? (upper) : (v)) +#endif + +#ifndef MAX_PATH +#define MAX_PATH 260 +#endif + +#ifndef NULL +#ifdef __cplusplus + #define NULL 0 +#else + #define NULL ((void*)0) +#endif +#endif + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef SAFE_ALLOC +#define SAFE_ALLOC(p, size)\ + do {\ + void* ptr = malloc(size);\ + if (!ptr) {\ + fprintf(stderr, "malloc failed!\n");\ + exit(-1);\ + }\ + memset(ptr, 0, size);\ + *(void**)&(p) = ptr;\ + } while(0) +#endif + +#ifndef SAFE_FREE +#define SAFE_FREE(p) do {if (p) {free(p); (p) = NULL;}} while(0) +#endif + +#ifndef SAFE_DELETE +#define SAFE_DELETE(p) do {if (p) {delete (p); (p) = NULL;}} while(0) +#endif + +#ifndef SAFE_DELETE_ARRAY +#define SAFE_DELETE_ARRAY(p) do {if (p) {delete[] (p); (p) = NULL;}} while(0) +#endif + +#ifndef SAFE_RELEASE +#define SAFE_RELEASE(p) do {if (p) {(p)->release(); (p) = NULL;}} while(0) +#endif + +#ifndef SAFE_CLOSE +#define SAFE_CLOSE(fd) do {if ((fd) >= 0) {close(fd); (fd) = -1;}} while(0) +#endif + +#define STRINGIFY(x) STRINGIFY_HELPER(x) +#define STRINGIFY_HELPER(x) #x + +#define STRINGCAT(x, y) STRINGCAT_HELPER(x, y) +#define STRINGCAT_HELPER(x, y) x##y + +#ifndef offsetof +#define offsetof(type, member) \ +((size_t)(&((type*)0)->member)) +#endif + +#ifndef offsetofend +#define offsetofend(type, member) \ +(offsetof(type, member) + sizeof(((type*)0)->member)) +#endif + +#ifndef container_of +#define container_of(ptr, type, member) \ +((type*)((char*)(ptr) - offsetof(type, member))) +#endif + +#ifdef PRINT_DEBUG +#define printd(...) printf(__VA_ARGS__) +#else +#define printd(...) +#endif + +#ifdef PRINT_ERROR +#define printe(...) fprintf(stderr, __VA_ARGS__) +#else +#define printe(...) +#endif + +#endif // HV_DEF_H_ diff --git a/ww/libhv/base/heap.h b/ww/libhv/base/heap.h new file mode 100644 index 00000000..a0c5b568 --- /dev/null +++ b/ww/libhv/base/heap.h @@ -0,0 +1,172 @@ +#ifndef HV_HEAP_H_ +#define HV_HEAP_H_ + +#include // for assert +#include // for NULL + +struct heap_node { + struct heap_node* parent; + struct heap_node* left; + struct heap_node* right; +}; + +typedef int (*heap_compare_fn)(const struct heap_node* lhs, const struct heap_node* rhs); +struct heap { + struct heap_node* root; + int nelts; + // if compare is less_than, root is min of heap + // if compare is larger_than, root is max of heap + heap_compare_fn compare; +}; + +static inline void heap_init(struct heap* heap, heap_compare_fn fn) { + heap->root = NULL; + heap->nelts = 0; + heap->compare = fn; +} + +// replace s with r +static inline void heap_replace(struct heap* heap, struct heap_node* s, struct heap_node* r) { + // s->parent->child, s->left->parent, s->right->parent + if (s->parent == NULL) heap->root = r; + else if (s->parent->left == s) s->parent->left = r; + else if (s->parent->right == s) s->parent->right = r; + + if (s->left) s->left->parent = r; + if (s->right) s->right->parent = r; + if (r) { + //*r = *s; + r->parent = s->parent; + r->left = s->left; + r->right = s->right; + } +} + +static inline void heap_swap(struct heap* heap, struct heap_node* parent, struct heap_node* child) { + assert(child->parent == parent && (parent->left == child || parent->right == child)); + struct heap_node* pparent = parent->parent; + struct heap_node* lchild = child->left; + struct heap_node* rchild = child->right; + struct heap_node* sibling = NULL; + + if (pparent == NULL) heap->root = child; + else if (pparent->left == parent) pparent->left = child; + else if (pparent->right == parent) pparent->right = child; + + if (lchild) lchild->parent = parent; + if (rchild) rchild->parent = parent; + + child->parent = pparent; + if (parent->left == child) { + sibling = parent->right; + child->left = parent; + child->right = sibling; + } else { + sibling = parent->left; + child->left = sibling; + child->right = parent; + } + if (sibling) sibling->parent = child; + + parent->parent = child; + parent->left = lchild; + parent->right = rchild; +} + +static inline void heap_insert(struct heap* heap, struct heap_node* node) { + // get last => insert node => sift up + // 0: left, 1: right + int path = 0; + int n,d; + ++heap->nelts; + // traverse from bottom to up, get path of last node + for (d = 0, n = heap->nelts; n >= 2; ++d, n>>=1) { + path = (path << 1) | (n & 1); + } + + // get last->parent by path + struct heap_node* parent = heap->root; + while(d > 1) { + parent = (path & 1) ? parent->right : parent->left; + --d; + path >>= 1; + } + + // insert node + node->parent = parent; + if (parent == NULL) heap->root = node; + else if (path & 1) parent->right = node; + else parent->left = node; + + // sift up + if (heap->compare) { + while (node->parent && heap->compare(node, node->parent)) { + heap_swap(heap, node->parent, node); + } + } +} + +static inline void heap_remove(struct heap* heap, struct heap_node* node) { + if (heap->nelts == 0) return; + // get last => replace node with last => sift down and sift up + // 0: left, 1: right + int path = 0; + int n,d; + // traverse from bottom to up, get path of last node + for (d = 0, n = heap->nelts; n >= 2; ++d, n>>=1) { + path = (path << 1) | (n & 1); + } + --heap->nelts; + + // get last->parent by path + struct heap_node* parent = heap->root; + while(d > 1) { + parent = (path & 1) ? parent->right : parent->left; + --d; + path >>= 1; + } + + // replace node with last + struct heap_node* last = NULL; + if (parent == NULL) { + return; + } + else if (path & 1) { + last = parent->right; + parent->right = NULL; + } + else { + last = parent->left; + parent->left = NULL; + } + if (last == NULL) { + if (heap->root == node) { + heap->root = NULL; + } + return; + } + heap_replace(heap, node, last); + node->parent = node->left = node->right = NULL; + + if (!heap->compare) return; + struct heap_node* v = last; + struct heap_node* est = NULL; + // sift down + while (1) { + est = v; + if (v->left) est = heap->compare(est, v->left) ? est : v->left; + if (v->right) est = heap->compare(est, v->right) ? est : v->right; + if (est == v) break; + heap_swap(heap, v, est); + } + // sift up + while (v->parent && heap->compare(v, v->parent)) { + heap_swap(heap, v->parent, v); + } +} + +static inline void heap_dequeue(struct heap* heap) { + heap_remove(heap, heap->root); +} + +#endif // HV_HEAP_H_ diff --git a/ww/libhv/base/hendian.h b/ww/libhv/base/hendian.h new file mode 100644 index 00000000..573af6c6 --- /dev/null +++ b/ww/libhv/base/hendian.h @@ -0,0 +1,244 @@ +#ifndef HV_ENDIAN_H_ +#define HV_ENDIAN_H_ + +#include "hplatform.h" +#if defined(OS_MAC) +#include +#define htobe16(v) OSSwapHostToBigInt16(v) +#define htobe32(v) OSSwapHostToBigInt32(v) +#define htobe64(v) OSSwapHostToBigInt64(v) +#define be16toh(v) OSSwapBigToHostInt16(v) +#define be32toh(v) OSSwapBigToHostInt32(v) +#define be64toh(v) OSSwapBigToHostInt64(v) + +#define htole16(v) OSSwapHostToLittleInt16(v) +#define htole32(v) OSSwapHostToLittleInt32(v) +#define htole64(v) OSSwapHostToLittleInt64(v) +#define le16toh(v) OSSwapLittleToHostInt16(v) +#define le32toh(v) OSSwapLittleToHostInt32(v) +#define le64toh(v) OSSwapLittleToHostInt64(v) +#elif defined(OS_WIN) + +#if _WIN32_WINNT < _WIN32_WINNT_WIN8 + /* + * Byte order conversion functions for 64-bit integers and 32 + 64 bit + * floating-point numbers. IEEE big-endian format is used for the + * network floating point format. + */ + #define _WS2_32_WINSOCK_SWAP_LONG(l) \ + ( ( ((l) >> 24) & 0x000000FFL ) | \ + ( ((l) >> 8) & 0x0000FF00L ) | \ + ( ((l) << 8) & 0x00FF0000L ) | \ + ( ((l) << 24) & 0xFF000000L ) ) + + #define _WS2_32_WINSOCK_SWAP_LONGLONG(l) \ + ( ( ((l) >> 56) & 0x00000000000000FFLL ) | \ + ( ((l) >> 40) & 0x000000000000FF00LL ) | \ + ( ((l) >> 24) & 0x0000000000FF0000LL ) | \ + ( ((l) >> 8) & 0x00000000FF000000LL ) | \ + ( ((l) << 8) & 0x000000FF00000000LL ) | \ + ( ((l) << 24) & 0x0000FF0000000000LL ) | \ + ( ((l) << 40) & 0x00FF000000000000LL ) | \ + ( ((l) << 56) & 0xFF00000000000000LL ) ) + + + #ifndef htonll + __inline unsigned __int64 htonll ( unsigned __int64 Value ) + { + const unsigned __int64 Retval = _WS2_32_WINSOCK_SWAP_LONGLONG (Value); + return Retval; + } + #endif /* htonll */ + + #ifndef ntohll + __inline unsigned __int64 ntohll ( unsigned __int64 Value ) + { + const unsigned __int64 Retval = _WS2_32_WINSOCK_SWAP_LONGLONG (Value); + return Retval; + } + #endif /* ntohll */ + + #ifndef htonf + __inline unsigned __int32 htonf ( float Value ) + { + unsigned __int32 Tempval; + unsigned __int32 Retval; + Tempval = *(unsigned __int32*)(&Value); + Retval = _WS2_32_WINSOCK_SWAP_LONG (Tempval); + return Retval; + } + #endif /* htonf */ + + #ifndef ntohf + __inline float ntohf ( unsigned __int32 Value ) + { + const unsigned __int32 Tempval = _WS2_32_WINSOCK_SWAP_LONG (Value); + float Retval; + *((unsigned __int32*)&Retval) = Tempval; + return Retval; + } + #endif /* ntohf */ + + #ifndef htond + __inline unsigned __int64 htond ( double Value ) + { + unsigned __int64 Tempval; + unsigned __int64 Retval; + Tempval = *(unsigned __int64*)(&Value); + Retval = _WS2_32_WINSOCK_SWAP_LONGLONG (Tempval); + return Retval; + } + #endif /* htond */ + + #ifndef ntohd + __inline double ntohd ( unsigned __int64 Value ) + { + const unsigned __int64 Tempval = _WS2_32_WINSOCK_SWAP_LONGLONG (Value); + double Retval; + *((unsigned __int64*)&Retval) = Tempval; + return Retval; + } + #endif /* ntohd */ +#endif + +#define htobe16(v) htons(v) +#define htobe32(v) htonl(v) +#define htobe64(v) htonll(v) +#define be16toh(v) ntohs(v) +#define be32toh(v) ntohl(v) +#define be64toh(v) ntohll(v) + +#if (BYTE_ORDER == LITTLE_ENDIAN) +#define htole16(v) (v) +#define htole32(v) (v) +#define htole64(v) (v) +#define le16toh(v) (v) +#define le32toh(v) (v) +#define le64toh(v) (v) +#elif (BYTE_ORDER == BIG_ENDIAN) +#define htole16(v) __builtin_bswap16(v) +#define htole32(v) __builtin_bswap32(v) +#define htole64(v) __builtin_bswap64(v) +#define le16toh(v) __builtin_bswap16(v) +#define le32toh(v) __builtin_bswap32(v) +#define le64toh(v) __builtin_bswap64(v) +#endif +#elif HAVE_ENDIAN_H +#include +#elif HAVE_SYS_ENDIAN_H +#include +#else +#warning "Not found endian.h!" +#endif + +#define PI8(p) *(int8_t*)(p) +#define PI16(p) *(int16_t*)(p) +#define PI32(p) *(int32_t*)(p) +#define PI64(p) *(int64_t*)(p) + +#define PU8(p) *(uint8_t*)(p) +#define PU16(p) *(uint16_t*)(p) +#define PU32(p) *(uint32_t*)(p) +#define PU64(p) *(uint64_t*)(p) + +#define PF32(p) *(float*)(p) +#define PF64(p) *(double*)(p) + +#define GET_BE16(p) be16toh(PU16(p)) +#define GET_BE32(p) be32toh(PU32(p)) +#define GET_BE64(p) be64toh(PU64(p)) + +#define GET_LE16(p) le16toh(PU16(p)) +#define GET_LE32(p) le32toh(PU32(p)) +#define GET_LE64(p) le64toh(PU64(p)) + +#define PUT_BE16(p, v) PU16(p) = htobe16(v) +#define PUT_BE32(p, v) PU32(p) = htobe32(v) +#define PUT_BE64(p, v) PU64(p) = htobe64(v) + +#define PUT_LE16(p, v) PU16(p) = htole16(v) +#define PUT_LE32(p, v) PU32(p) = htole32(v) +#define PUT_LE64(p, v) PU64(p) = htole64(v) + +// NOTE: uint8_t* p = (uint8_t*)buf; +#define POP_BE8(p, v) v = *p; ++p +#define POP_BE16(p, v) v = be16toh(PU16(p)); p += 2 +#define POP_BE32(p, v) v = be32toh(PU32(p)); p += 4 +#define POP_BE64(p, v) v = be64toh(PU64(p)); p += 8 + +#define POP_LE8(p, v) v= *p; ++p +#define POP_LE16(p, v) v = le16toh(PU16(p)); p += 2 +#define POP_LE32(p, v) v = le32toh(PU32(p)); p += 4 +#define POP_LE64(p, v) v = le64toh(PU64(p)); p += 8 + +#define PUSH_BE8(p, v) *p = v; ++p +#define PUSH_BE16(p, v) PU16(p) = htobe16(v); p += 2 +#define PUSH_BE32(p, v) PU32(p) = htobe32(v); p += 4 +#define PUSH_BE64(p, v) PU64(p) = htobe64(v); p += 8 + +#define PUSH_LE8(p, v) *p = v; ++p +#define PUSH_LE16(p, v) PU16(p) = htole16(v); p += 2 +#define PUSH_LE32(p, v) PU32(p) = htole32(v); p += 4 +#define PUSH_LE64(p, v) PU64(p) = htole64(v); p += 8 + +// NOTE: NET_ENDIAN = BIG_ENDIAN +#define POP8(p, v) POP_BE8(p, v) +#define POP16(p, v) POP_BE16(p, v) +#define POP32(p, v) POP_BE32(p, v) +#define POP64(p, v) POP_BE64(p, v) +#define POP_N(p, v, n) memcpy(v, p, n); p += n + +#define PUSH8(p, v) PUSH_BE8(p, v) +#define PUSH16(p, v) PUSH_BE16(p, v) +#define PUSH32(p, v) PUSH_BE32(p, v) +#define PUSH64(p, v) PUSH_BE64(p, v) +#define PUSH_N(p, v, n) memcpy(p, v, n); p += n + +static inline int detect_endian() { + union { + char c; + short s; + } u; + u.s = 0x1122; + return u.c ==0x11 ? BIG_ENDIAN : LITTLE_ENDIAN; +} + +#ifdef __cplusplus +template +uint8_t* serialize(uint8_t* buf, T value, int host_endian = LITTLE_ENDIAN, int buf_endian = BIG_ENDIAN) { + size_t size = sizeof(T); + uint8_t* pDst = buf; + uint8_t* pSrc = (uint8_t*)&value; + + if (host_endian == buf_endian) { + memcpy(pDst, pSrc, size); + } + else { + for (int i = 0; i < size; ++i) { + pDst[i] = pSrc[size-i-1]; + } + } + + return buf+size; +} + +template +uint8_t* deserialize(uint8_t* buf, T* value, int host_endian = LITTLE_ENDIAN, int buf_endian = BIG_ENDIAN) { + size_t size = sizeof(T); + uint8_t* pSrc = buf; + uint8_t* pDst = (uint8_t*)value; + + if (host_endian == buf_endian) { + memcpy(pDst, pSrc, size); + } + else { + for (int i = 0; i < size; ++i) { + pDst[i] = pSrc[size-i-1]; + } + } + + return buf+size; +} +#endif // __cplusplus + +#endif // HV_ENDIAN_H_ diff --git a/ww/libhv/base/herr.c b/ww/libhv/base/herr.c new file mode 100644 index 00000000..d3adf165 --- /dev/null +++ b/ww/libhv/base/herr.c @@ -0,0 +1,19 @@ +#include "herr.h" + +#include // for strerror + +// errcode => errmsg +const char* hv_strerror(int err) { + if (err > 0 && err <= SYS_NERR) { + return strerror(err); + } + + switch (err) { +#define F(errcode, name, errmsg) \ + case errcode: return errmsg; + FOREACH_ERR(F) +#undef F + default: + return "Undefined error"; + } +} diff --git a/ww/libhv/base/herr.h b/ww/libhv/base/herr.h new file mode 100644 index 00000000..adb6d07b --- /dev/null +++ b/ww/libhv/base/herr.h @@ -0,0 +1,122 @@ +#ifndef HV_ERR_H_ +#define HV_ERR_H_ + +#include + +#include "hexport.h" + +#ifndef SYS_NERR +#define SYS_NERR 133 +#endif + +// F(errcode, name, errmsg) +// [1, 133] +#define FOREACH_ERR_SYS(F) + +// [1xx~5xx] +#define FOREACH_ERR_STATUS(F) + +// [1xxx] +#define FOREACH_ERR_COMMON(F) \ + F(0, OK, "OK") \ + F(1000, UNKNOWN, "Unknown error") \ + \ + F(1001, NULL_PARAM, "Null parameter") \ + F(1002, NULL_POINTER, "Null pointer") \ + F(1003, NULL_DATA, "Null data") \ + F(1004, NULL_HANDLE, "Null handle") \ + \ + F(1011, INVALID_PARAM, "Invalid parameter")\ + F(1012, INVALID_POINTER, "Invalid pointer") \ + F(1013, INVALID_DATA, "Invalid data") \ + F(1014, INVALID_HANDLE, "Invalid handle") \ + F(1015, INVALID_JSON, "Invalid json") \ + F(1016, INVALID_XML, "Invalid xml") \ + F(1017, INVALID_FMT, "Invalid format") \ + F(1018, INVALID_PROTOCOL, "Invalid protocol") \ + F(1019, INVALID_PACKAGE, "Invalid package") \ + \ + F(1021, OUT_OF_RANGE, "Out of range") \ + F(1022, OVER_LIMIT, "Over the limit") \ + F(1023, MISMATCH, "Mismatch") \ + F(1024, PARSE, "Parse failed") \ + \ + F(1030, OPEN_FILE, "Open file failed") \ + F(1031, SAVE_FILE, "Save file failed") \ + F(1032, READ_FILE, "Read file failed") \ + F(1033, WRITE_FILE, "Write file failed")\ + \ + F(1040, SSL, "SSL/TLS error") \ + F(1041, NEW_SSL_CTX, "New SSL_CTX failed") \ + F(1042, NEW_SSL, "New SSL failed") \ + F(1043, SSL_HANDSHAKE, "SSL handshake failed") \ + \ + F(1100, TASK_TIMEOUT, "Task timeout") \ + F(1101, TASK_QUEUE_FULL, "Task queue full") \ + F(1102, TASK_QUEUE_EMPTY, "Task queue empty") \ + \ + F(1400, REQUEST, "Bad request") \ + F(1401, RESPONSE, "Bad response") \ + +// [-1xxx] +#define FOREACH_ERR_FUNC(F) \ + F(-1001, MALLOC, "malloc() error") \ + F(-1002, REALLOC, "realloc() error") \ + F(-1003, CALLOC, "calloc() error") \ + F(-1004, FREE, "free() error") \ + \ + F(-1011, SOCKET, "socket() error") \ + F(-1012, BIND, "bind() error") \ + F(-1013, LISTEN, "listen() error") \ + F(-1014, ACCEPT, "accept() error") \ + F(-1015, CONNECT, "connect() error") \ + F(-1016, RECV, "recv() error") \ + F(-1017, SEND, "send() error") \ + F(-1018, RECVFROM, "recvfrom() error") \ + F(-1019, SENDTO, "sendto() error") \ + F(-1020, SETSOCKOPT, "setsockopt() error") \ + F(-1021, GETSOCKOPT, "getsockopt() error") \ + +// grpc [4xxx] +#define FOREACH_ERR_GRPC(F) \ + F(4000, GRPC_FIRST, "grpc no error") \ + F(4001, GRPC_STATUS_CANCELLED, "grpc status: cancelled") \ + F(4002, GRPC_STATUS_UNKNOWN, "grpc unknown error") \ + F(4003, GRPC_STATUS_INVALID_ARGUMENT, "grpc status: invalid argument")\ + F(4004, GRPC_STATUS_DEADLINE, "grpc status: deadline") \ + F(4005, GRPC_STATUS_NOT_FOUND, "grpc status: not found") \ + F(4006, GRPC_STATUS_ALREADY_EXISTS, "grpc status: already exists") \ + F(4007, GRPC_STATUS_PERMISSION_DENIED, "grpc status: permission denied") \ + F(4008, GRPC_STATUS_RESOURCE_EXHAUSTED, "grpc status: resource exhausted") \ + F(4009, GRPC_STATUS_FAILED_PRECONDITION,"grpc status: failed precondition") \ + F(4010, GRPC_STATUS_ABORTED, "grpc status: aborted") \ + F(4011, GRPC_STATUS_OUT_OF_RANGE, "grpc status: out of range") \ + F(4012, GRPC_STATUS_UNIMPLEMENTED, "grpc status: unimplemented") \ + F(4013, GRPC_STATUS_INTERNAL, "grpc internal error") \ + F(4014, GRPC_STATUS_UNAVAILABLE, "grpc service unavailable") \ + F(4015, GRPC_STATUS_DATA_LOSS, "grpc status: data loss") \ + +#define FOREACH_ERR(F) \ + FOREACH_ERR_COMMON(F) \ + FOREACH_ERR_FUNC(F) \ + FOREACH_ERR_GRPC(F) \ + +#undef ERR_OK // prevent conflict +enum { +#define F(errcode, name, errmsg) ERR_##name = errcode, + FOREACH_ERR(F) +#undef F +}; + +#ifdef __cplusplus +extern "C" { +#endif + +// errcode => errmsg +HV_EXPORT const char* hv_strerror(int err); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // HV_ERR_H_ diff --git a/ww/libhv/base/hlog.c b/ww/libhv/base/hlog.c new file mode 100644 index 00000000..aa351d85 --- /dev/null +++ b/ww/libhv/base/hlog.c @@ -0,0 +1,475 @@ +#include "hlog.h" + +#include +#include +#include +#include +#include + +//#include "hmutex.h" +#ifdef _WIN32 +#pragma warning (disable: 4244) // conversion loss of data +#include +#define hmutex_t CRITICAL_SECTION +#define hmutex_init InitializeCriticalSection +#define hmutex_destroy DeleteCriticalSection +#define hmutex_lock EnterCriticalSection +#define hmutex_unlock LeaveCriticalSection +#else +#include // for gettimeofday +#include +#define hmutex_t pthread_mutex_t +#define hmutex_init(mutex) pthread_mutex_init(mutex, NULL) +#define hmutex_destroy pthread_mutex_destroy +#define hmutex_lock pthread_mutex_lock +#define hmutex_unlock pthread_mutex_unlock +#endif + +//#include "htime.h" +#define SECONDS_PER_HOUR 3600 +#define SECONDS_PER_DAY 86400 // 24*3600 +#define SECONDS_PER_WEEK 604800 // 7*24*3600; + +static int s_gmtoff = 28800; // 8*3600 + +struct logger_s { + logger_handler handler; + unsigned int bufsize; + char* buf; + + int level; + int enable_color; + char format[64]; + + // for file logger + char filepath[256]; + unsigned long long max_filesize; + int remain_days; + int enable_fsync; + FILE* fp_; + char cur_logfile[256]; + time_t last_logfile_ts; + int can_write_cnt; + + hmutex_t mutex_; // thread-safe +}; + +static void logger_init(logger_t* logger) { + logger->handler = NULL; + logger->bufsize = DEFAULT_LOG_MAX_BUFSIZE; + logger->buf = (char*)malloc(logger->bufsize); + + logger->level = DEFAULT_LOG_LEVEL; + logger->enable_color = 0; + // NOTE: format is faster 6% than snprintf + // logger->format[0] = '\0'; + strncpy(logger->format, DEFAULT_LOG_FORMAT, sizeof(logger->format) - 1); + + logger->fp_ = NULL; + logger->max_filesize = DEFAULT_LOG_MAX_FILESIZE; + logger->remain_days = DEFAULT_LOG_REMAIN_DAYS; + logger->enable_fsync = 1; + logger_set_file(logger, DEFAULT_LOG_FILE); + logger->last_logfile_ts = 0; + logger->can_write_cnt = -1; + hmutex_init(&logger->mutex_); +} + +logger_t* logger_create() { + // init gmtoff here + time_t ts = time(NULL); + struct tm* local_tm = localtime(&ts); + int local_hour = local_tm->tm_hour; + struct tm* gmt_tm = gmtime(&ts); + int gmt_hour = gmt_tm->tm_hour; + s_gmtoff = (local_hour - gmt_hour) * SECONDS_PER_HOUR; + + logger_t* logger = (logger_t*)malloc(sizeof(logger_t)); + logger_init(logger); + return logger; +} + +void logger_destroy(logger_t* logger) { + if (logger) { + if (logger->buf) { + free(logger->buf); + logger->buf = NULL; + } + if (logger->fp_) { + fclose(logger->fp_); + logger->fp_ = NULL; + } + hmutex_destroy(&logger->mutex_); + free(logger); + } +} + +void logger_set_handler(logger_t* logger, logger_handler fn) { + logger->handler = fn; +} + +void logger_set_level(logger_t* logger, int level) { + logger->level = level; +} + +void logger_set_level_by_str(logger_t* logger, const char* szLoglevel) { + int loglevel = DEFAULT_LOG_LEVEL; + if (strcmp(szLoglevel, "VERBOSE") == 0) { + loglevel = LOG_LEVEL_VERBOSE; + } else if (strcmp(szLoglevel, "DEBUG") == 0) { + loglevel = LOG_LEVEL_DEBUG; + } else if (strcmp(szLoglevel, "INFO") == 0) { + loglevel = LOG_LEVEL_INFO; + } else if (strcmp(szLoglevel, "WARN") == 0) { + loglevel = LOG_LEVEL_WARN; + } else if (strcmp(szLoglevel, "ERROR") == 0) { + loglevel = LOG_LEVEL_ERROR; + } else if (strcmp(szLoglevel, "FATAL") == 0) { + loglevel = LOG_LEVEL_FATAL; + } else if (strcmp(szLoglevel, "SILENT") == 0) { + loglevel = LOG_LEVEL_SILENT; + } else { + loglevel = DEFAULT_LOG_LEVEL; + } + logger->level = loglevel; +} + +int logger_will_write_level(logger_t* logger, log_level_e level) { + return (logger->level) >= level; +} + +void logger_set_format(logger_t* logger, const char* format) { + if (format) { + strncpy(logger->format, format, sizeof(logger->format) - 1); + } else { + logger->format[0] = '\0'; + } +} + +void logger_set_remain_days(logger_t* logger, int days) { + logger->remain_days = days; +} + +void logger_set_max_bufsize(logger_t* logger, unsigned int bufsize) { + logger->bufsize = bufsize; + logger->buf = (char*)realloc(logger->buf, bufsize); +} + +void logger_enable_color(logger_t* logger, int on) { + logger->enable_color = on; +} + +void logger_set_file(logger_t* logger, const char* filepath) { + strncpy(logger->filepath, filepath, sizeof(logger->filepath) - 1); + // remove suffix .log + char* suffix = strrchr(logger->filepath, '.'); + if (suffix && strcmp(suffix, ".log") == 0) { + *suffix = '\0'; + } +} + +void logger_set_max_filesize(logger_t* logger, unsigned long long filesize) { + logger->max_filesize = filesize; +} + +void logger_set_max_filesize_by_str(logger_t* logger, const char* str) { + int num = atoi(str); + if (num <= 0) return; + // 16 16M 16MB + const char* e = str; + while (*e != '\0') ++e; + --e; + char unit; + if (*e >= '0' && *e <= '9') unit = 'M'; + else if (*e == 'B') unit = *(e-1); + else unit = *e; + unsigned long long filesize = num; + switch (unit) { + case 'K': filesize <<= 10; break; + case 'M': filesize <<= 20; break; + case 'G': filesize <<= 30; break; + default: filesize <<= 20; break; + } + logger->max_filesize = filesize; +} + +void logger_enable_fsync(logger_t* logger, int on) { + logger->enable_fsync = on; +} + +void logger_fsync(logger_t* logger) { + hmutex_lock(&logger->mutex_); + if (logger->fp_) { + fflush(logger->fp_); + } + hmutex_unlock(&logger->mutex_); +} + +const char* logger_get_cur_file(logger_t* logger) { + return logger->cur_logfile; +} + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-truncation" +#endif + +static void logfile_name(const char* filepath, time_t ts, char* buf, int len) { + struct tm* tm = localtime(&ts); + snprintf(buf, len, "%s.%04d%02d%02d.log", + filepath, + tm->tm_year+1900, + tm->tm_mon+1, + tm->tm_mday); +} +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + +static FILE* logfile_shift(logger_t* logger) { + time_t ts_now = time(NULL); + int interval_days = logger->last_logfile_ts == 0 ? 0 : (ts_now+s_gmtoff) / SECONDS_PER_DAY - (logger->last_logfile_ts+s_gmtoff) / SECONDS_PER_DAY; + if (logger->fp_ == NULL || interval_days > 0) { + // close old logfile + if (logger->fp_) { + fclose(logger->fp_); + logger->fp_ = NULL; + } + else { + interval_days = 30; + } + + if (logger->remain_days >= 0) { + char rm_logfile[256] = {0}; + if (interval_days >= logger->remain_days) { + // remove [today-interval_days, today-remain_days] logfile + for (int i = interval_days; i >= logger->remain_days; --i) { + time_t ts_rm = ts_now - i * SECONDS_PER_DAY; + logfile_name(logger->filepath, ts_rm, rm_logfile, sizeof(rm_logfile)); + remove(rm_logfile); + } + } + else { + // remove today-remain_days logfile + time_t ts_rm = ts_now - logger->remain_days * SECONDS_PER_DAY; + logfile_name(logger->filepath, ts_rm, rm_logfile, sizeof(rm_logfile)); + remove(rm_logfile); + } + } + } + + // open today logfile + if (logger->fp_ == NULL) { + logfile_name(logger->filepath, ts_now, logger->cur_logfile, sizeof(logger->cur_logfile)); + logger->fp_ = fopen(logger->cur_logfile, "a"); + logger->last_logfile_ts = ts_now; + } + + // NOTE: estimate can_write_cnt to avoid frequent fseek/ftell + if (logger->fp_ && --logger->can_write_cnt < 0) { + fseek(logger->fp_, 0, SEEK_END); + long filesize = ftell(logger->fp_); + if (filesize > logger->max_filesize) { + fclose(logger->fp_); + logger->fp_ = NULL; + // ftruncate + logger->fp_ = fopen(logger->cur_logfile, "w"); + // reopen with O_APPEND for multi-processes + if (logger->fp_) { + fclose(logger->fp_); + logger->fp_ = fopen(logger->cur_logfile, "a"); + } + } + else { + logger->can_write_cnt = (logger->max_filesize - filesize) / logger->bufsize; + } + } + + return logger->fp_; +} + +static void logfile_write(logger_t* logger, const char* buf, int len) { + FILE* fp = logfile_shift(logger); + if (fp) { + fwrite(buf, 1, len, fp); + if (logger->enable_fsync) { + fflush(fp); + } + } +} + +static int i2a(int i, char* buf, int len) { + for (int l = len - 1; l >= 0; --l) { + if (i == 0) { + buf[l] = '0'; + } else { + buf[l] = i % 10 + '0'; + i /= 10; + } + } + return len; +} + +int logger_print(logger_t* logger, int level, const char* fmt, ...) { + if (level < logger->level) + return -10; + + int year,month,day,hour,min,sec,us; +#ifdef _WIN32 + SYSTEMTIME tm; + GetLocalTime(&tm); + year = tm.wYear; + month = tm.wMonth; + day = tm.wDay; + hour = tm.wHour; + min = tm.wMinute; + sec = tm.wSecond; + us = tm.wMilliseconds * 1000; +#else + struct timeval tv; + struct tm* tm = NULL; + gettimeofday(&tv, NULL); + time_t tt = tv.tv_sec; + tm = localtime(&tt); + year = tm->tm_year + 1900; + month = tm->tm_mon + 1; + day = tm->tm_mday; + hour = tm->tm_hour; + min = tm->tm_min; + sec = tm->tm_sec; + us = tv.tv_usec; +#endif + + const char* pcolor = ""; + const char* plevel = ""; +#define XXX(id, str, clr) \ + case id: plevel = str; pcolor = clr; break; + + switch (level) { + LOG_LEVEL_MAP(XXX) + } +#undef XXX + + // lock logger->buf + hmutex_lock(&logger->mutex_); + + char* buf = logger->buf; + int bufsize = logger->bufsize; + int len = 0; + + if (logger->enable_color) { + len = snprintf(buf, bufsize, "%s", pcolor); + } + + const char* p = logger->format; + if (*p) { + while (*p) { + if (*p == '%') { + switch(*++p) { + case 'y': + len += i2a(year, buf + len, 4); + break; + case 'm': + len += i2a(month, buf + len, 2); + break; + case 'd': + len += i2a(day, buf + len, 2); + break; + case 'H': + len += i2a(hour, buf + len, 2); + break; + case 'M': + len += i2a(min, buf + len, 2); + break; + case 'S': + len += i2a(sec, buf + len, 2); + break; + case 'z': + len += i2a(us/1000, buf + len, 3); + break; + case 'Z': + len += i2a(us, buf + len, 6); + break; + case 'l': + buf[len++] = *plevel; + break; + case 'L': + for (int i = 0; i < 5; ++i) { + buf[len++] = plevel[i]; + } + break; + case 's': + { + va_list ap; + va_start(ap, fmt); + len += vsnprintf(buf + len, bufsize - len, fmt, ap); + va_end(ap); + } + break; + case '%': + buf[len++] = '%'; + break; + default: break; + } + } else { + buf[len++] = *p; + } + ++p; + } + } else { + len += snprintf(buf + len, bufsize - len, "%04d-%02d-%02d %02d:%02d:%02d.%03d %s ", + year, month, day, hour, min, sec, us/1000, + plevel); + + va_list ap; + va_start(ap, fmt); + len += vsnprintf(buf + len, bufsize - len, fmt, ap); + va_end(ap); + } + + if (logger->enable_color) { + len += snprintf(buf + len, bufsize - len, "%s", CLR_CLR); + } + + if(lenhandler) { + logger->handler(level, buf, len); + } + else { + logfile_write(logger, buf, len); + } + + hmutex_unlock(&logger->mutex_); + return len; +} + +static logger_t* s_logger = NULL; +logger_t* hv_default_logger() { + if (s_logger == NULL) { + s_logger = logger_create(); + atexit(hv_destroy_default_logger); + } + return s_logger; +} +void hv_destroy_default_logger(void) { + if (s_logger) { + logger_fsync(s_logger); + logger_destroy(s_logger); + s_logger = NULL; + } +} + +void stdout_logger(int loglevel, const char* buf, int len) { + fprintf(stdout, "%.*s", len, buf); +} + +void stderr_logger(int loglevel, const char* buf, int len) { + fprintf(stderr, "%.*s", len, buf); +} + +void file_logger(int loglevel, const char* buf, int len) { + logfile_write(hv_default_logger(), buf, len); +} diff --git a/ww/libhv/base/hlog.h b/ww/libhv/base/hlog.h new file mode 100644 index 00000000..c7cfd555 --- /dev/null +++ b/ww/libhv/base/hlog.h @@ -0,0 +1,179 @@ +#ifndef HV_LOG_H_ +#define HV_LOG_H_ + +/* + * hlog is thread-safe + */ + +#include + +#ifdef _WIN32 +#define DIR_SEPARATOR '\\' +#define DIR_SEPARATOR_STR "\\" +#else +#define DIR_SEPARATOR '/' +#define DIR_SEPARATOR_STR "/" +#endif + +#ifndef __FILENAME__ +// #define __FILENAME__ (strrchr(__FILE__, DIR_SEPARATOR) ? strrchr(__FILE__, DIR_SEPARATOR) + 1 : __FILE__) +#define __FILENAME__ (strrchr(DIR_SEPARATOR_STR __FILE__, DIR_SEPARATOR) + 1) +#endif + +#include "hexport.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define CLR_CLR "\033[0m" /* 恢复颜色 */ +#define CLR_BLACK "\033[30m" /* 黑色字 */ +#define CLR_RED "\033[31m" /* 红色字 */ +#define CLR_GREEN "\033[32m" /* 绿色字 */ +#define CLR_YELLOW "\033[33m" /* 黄色字 */ +#define CLR_BLUE "\033[34m" /* 蓝色字 */ +#define CLR_PURPLE "\033[35m" /* 紫色字 */ +#define CLR_SKYBLUE "\033[36m" /* 天蓝字 */ +#define CLR_WHITE "\033[37m" /* 白色字 */ + +#define CLR_BLK_WHT "\033[40;37m" /* 黑底白字 */ +#define CLR_RED_WHT "\033[41;37m" /* 红底白字 */ +#define CLR_GREEN_WHT "\033[42;37m" /* 绿底白字 */ +#define CLR_YELLOW_WHT "\033[43;37m" /* 黄底白字 */ +#define CLR_BLUE_WHT "\033[44;37m" /* 蓝底白字 */ +#define CLR_PURPLE_WHT "\033[45;37m" /* 紫底白字 */ +#define CLR_SKYBLUE_WHT "\033[46;37m" /* 天蓝底白字 */ +#define CLR_WHT_BLK "\033[47;30m" /* 白底黑字 */ + +// XXX(id, str, clr) +#define LOG_LEVEL_MAP(XXX) \ + XXX(LOG_LEVEL_DEBUG, "DEBUG", CLR_WHITE) \ + XXX(LOG_LEVEL_INFO, "INFO ", CLR_GREEN) \ + XXX(LOG_LEVEL_WARN, "WARN ", CLR_YELLOW) \ + XXX(LOG_LEVEL_ERROR, "ERROR", CLR_RED) \ + XXX(LOG_LEVEL_FATAL, "FATAL", CLR_RED_WHT) + +typedef enum { + LOG_LEVEL_VERBOSE = 0, +#define XXX(id, str, clr) id, + LOG_LEVEL_MAP(XXX) +#undef XXX + LOG_LEVEL_SILENT +} log_level_e; + +#define DEFAULT_LOG_FILE "libhv" +#define DEFAULT_LOG_LEVEL LOG_LEVEL_INFO +#define DEFAULT_LOG_FORMAT "%y-%m-%d %H:%M:%S.%z %L %s" +#define DEFAULT_LOG_REMAIN_DAYS 1 +#define DEFAULT_LOG_MAX_BUFSIZE (1<<14) // 16k +#define DEFAULT_LOG_MAX_FILESIZE (1<<24) // 16M + +// logger: default file_logger +// network_logger() see event/nlog.h +typedef void (*logger_handler)(int loglevel, const char* buf, int len); + +HV_EXPORT void stdout_logger(int loglevel, const char* buf, int len); +HV_EXPORT void stderr_logger(int loglevel, const char* buf, int len); +HV_EXPORT void file_logger(int loglevel, const char* buf, int len); +// network_logger implement see event/nlog.h +// HV_EXPORT void network_logger(int loglevel, const char* buf, int len); + +typedef struct logger_s logger_t; +HV_EXPORT logger_t* logger_create(); +HV_EXPORT void logger_destroy(logger_t* logger); + +HV_EXPORT void logger_set_handler(logger_t* logger, logger_handler fn); +HV_EXPORT void logger_set_level(logger_t* logger, int level); +// level = [VERBOSE,DEBUG,INFO,WARN,ERROR,FATAL,SILENT] +HV_EXPORT void logger_set_level_by_str(logger_t* logger, const char* level); +HV_EXPORT int logger_will_write_level(logger_t* logger, log_level_e level); + +/* + * format = "%y-%m-%d %H:%M:%S.%z %L %s" + * message = "2020-01-02 03:04:05.067 DEBUG message" + * %y year + * %m month + * %d day + * %H hour + * %M min + * %S sec + * %z ms + * %Z us + * %l First character of level + * %L All characters of level + * %s message + * %% % + */ +HV_EXPORT void logger_set_format(logger_t* logger, const char* format); +HV_EXPORT void logger_set_max_bufsize(logger_t* logger, unsigned int bufsize); +HV_EXPORT void logger_enable_color(logger_t* logger, int on); +HV_EXPORT int logger_print(logger_t* logger, int level, const char* fmt, ...); + +// below for file logger +HV_EXPORT void logger_set_file(logger_t* logger, const char* filepath); +HV_EXPORT void logger_set_max_filesize(logger_t* logger, unsigned long long filesize); +// 16, 16M, 16MB +HV_EXPORT void logger_set_max_filesize_by_str(logger_t* logger, const char* filesize); +HV_EXPORT void logger_set_remain_days(logger_t* logger, int days); +HV_EXPORT void logger_enable_fsync(logger_t* logger, int on); +HV_EXPORT void logger_fsync(logger_t* logger); +HV_EXPORT const char* logger_get_cur_file(logger_t* logger); + +// hlog: default logger instance +HV_EXPORT logger_t* hv_default_logger(); +HV_EXPORT void hv_destroy_default_logger(void); + +// macro hlog* +#define hlog hv_default_logger() +#define hlog_destory() hv_destroy_default_logger() +#define hlog_disable() logger_set_level(hlog, LOG_LEVEL_SILENT) +#define hlog_set_file(filepath) logger_set_file(hlog, filepath) +#define hlog_set_level(level) logger_set_level(hlog, level) +#define hlog_set_level_by_str(level) logger_set_level_by_str(hlog, level) +#define hlog_set_handler(fn) logger_set_handler(hlog, fn) +#define hlog_set_format(format) logger_set_format(hlog, format) +#define hlog_set_max_filesize(filesize) logger_set_max_filesize(hlog, filesize) +#define hlog_set_max_filesize_by_str(filesize) logger_set_max_filesize_by_str(hlog, filesize) +#define hlog_set_remain_days(days) logger_set_remain_days(hlog, days) +#define hlog_enable_fsync() logger_enable_fsync(hlog, 1) +#define hlog_disable_fsync() logger_enable_fsync(hlog, 0) +#define hlog_fsync() logger_fsync(hlog) +#define hlog_get_cur_file() logger_get_cur_file(hlog) +#define hlog_will_write(level) logger_will_write_level(hlog,level) + +#define hlogd(fmt, ...) logger_print(hlog, LOG_LEVEL_DEBUG, fmt " [%s:%d:%s]", ## __VA_ARGS__, __FILENAME__, __LINE__, __FUNCTION__) +#define hlogi(fmt, ...) logger_print(hlog, LOG_LEVEL_INFO, fmt " [%s:%d:%s]", ## __VA_ARGS__, __FILENAME__, __LINE__, __FUNCTION__) +#define hlogw(fmt, ...) logger_print(hlog, LOG_LEVEL_WARN, fmt " [%s:%d:%s]", ## __VA_ARGS__, __FILENAME__, __LINE__, __FUNCTION__) +#define hloge(fmt, ...) logger_print(hlog, LOG_LEVEL_ERROR, fmt " [%s:%d:%s]", ## __VA_ARGS__, __FILENAME__, __LINE__, __FUNCTION__) +#define hlogf(fmt, ...) logger_print(hlog, LOG_LEVEL_FATAL, fmt " [%s:%d:%s]", ## __VA_ARGS__, __FILENAME__, __LINE__, __FUNCTION__) + +// below for android +#if defined(ANDROID) || defined(__ANDROID__) +#include +#define LOG_TAG "JNI" +#undef hlogd +#undef hlogi +#undef hlogw +#undef hloge +#undef hlogf +#define hlogd(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) +#define hlogi(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) +#define hlogw(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) +#define hloge(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) +#define hlogf(...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__) +#endif + +// macro alias +#if !defined(LOGD) && !defined(LOGI) && !defined(LOGW) && !defined(LOGE) && !defined(LOGF) +#define LOGD hlogd +#define LOGI hlogi +#define LOGW hlogw +#define LOGE hloge +#define LOGF hlogf +#endif + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // HV_LOG_H_ diff --git a/ww/libhv/base/hmain.c b/ww/libhv/base/hmain.c new file mode 100644 index 00000000..d0a0faef --- /dev/null +++ b/ww/libhv/base/hmain.c @@ -0,0 +1,684 @@ +#include "hmain.h" + +#include "hbase.h" +#include "hlog.h" +#include "herr.h" +#include "htime.h" +#include "hthread.h" + +#ifdef OS_DARWIN +#include +#define environ (*_NSGetEnviron()) +#endif + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-truncation" +#endif + + +main_ctx_t g_main_ctx; + +static void init_arg_kv(int maxsize) { + g_main_ctx.arg_kv_size = 0; + SAFE_ALLOC(g_main_ctx.arg_kv, sizeof(char*) * maxsize); +} + +static void save_arg_kv(const char* key, int key_len, const char* val, int val_len) { + if (key_len <= 0) key_len = strlen(key); + if (val_len <= 0) val_len = strlen(val); + char* arg = NULL; + SAFE_ALLOC(arg, key_len + val_len + 2); + memcpy(arg, key, key_len); + arg[key_len] = '='; + memcpy(arg + key_len + 1, val, val_len); + // printf("save_arg_kv: %s\n", arg); + g_main_ctx.arg_kv[g_main_ctx.arg_kv_size++] = arg; +} + +static void init_arg_list(int maxsize) { + g_main_ctx.arg_list_size = 0; + SAFE_ALLOC(g_main_ctx.arg_list, sizeof(char*) * maxsize); +} + +static void save_arg_list(const char* arg) { + // printf("save_arg_list: %s\n", arg); + g_main_ctx.arg_list[g_main_ctx.arg_list_size++] = strdup(arg); +} + +static const char* get_val(char** kvs, const char* key) { + if (kvs == NULL) return NULL; + int key_len = strlen(key); + char* kv = NULL; + int kv_len = 0; + for (int i = 0; kvs[i]; ++i) { + kv = kvs[i]; + kv_len = strlen(kv); + if (kv_len <= key_len) continue; + // key=val + if (memcmp(kv, key, key_len) == 0 && kv[key_len] == '=') { + return kv + key_len + 1; + } + } + return NULL; +} + +const char* get_arg(const char* key) { + return get_val(g_main_ctx.arg_kv, key); +} + +const char* get_env(const char* key) { + return get_val(g_main_ctx.save_envp, key); +} + +int main_ctx_init(int argc, char** argv) { + if (argc == 0 || argv == NULL) { + argc = 1; + SAFE_ALLOC(argv, 2 * sizeof(char*)); + SAFE_ALLOC(argv[0], MAX_PATH); + get_executable_path(argv[0], MAX_PATH); + } + + get_run_dir(g_main_ctx.run_dir, sizeof(g_main_ctx.run_dir)); + //printf("run_dir=%s\n", g_main_ctx.run_dir); + strncpy(g_main_ctx.program_name, hv_basename(argv[0]), sizeof(g_main_ctx.program_name)); +#ifdef OS_WIN + if (strcmp(g_main_ctx.program_name+strlen(g_main_ctx.program_name)-4, ".exe") == 0) { + *(g_main_ctx.program_name+strlen(g_main_ctx.program_name)-4) = '\0'; + } +#endif + //printf("program_name=%s\n", g_main_ctx.program_name); + char logdir[MAX_PATH] = {0}; + snprintf(logdir, sizeof(logdir), "%s/logs", g_main_ctx.run_dir); + hv_mkdir(logdir); + snprintf(g_main_ctx.confile, sizeof(g_main_ctx.confile), "%s/etc/%s.conf", g_main_ctx.run_dir, g_main_ctx.program_name); + snprintf(g_main_ctx.pidfile, sizeof(g_main_ctx.pidfile), "%s/logs/%s.pid", g_main_ctx.run_dir, g_main_ctx.program_name); + snprintf(g_main_ctx.logfile, sizeof(g_main_ctx.logfile), "%s/logs/%s.log", g_main_ctx.run_dir, g_main_ctx.program_name); + hlog_set_file(g_main_ctx.logfile); + + g_main_ctx.pid = getpid(); + g_main_ctx.oldpid = getpid_from_pidfile(); +#ifdef OS_UNIX + if (kill(g_main_ctx.oldpid, 0) == -1 && errno == ESRCH) { + g_main_ctx.oldpid = -1; + } +#else + HANDLE hproc = OpenProcess(PROCESS_TERMINATE, FALSE, g_main_ctx.oldpid); + if (hproc == NULL) { + g_main_ctx.oldpid = -1; + } + else { + CloseHandle(hproc); + } +#endif + + // save arg + int i = 0; + g_main_ctx.os_argv = argv; + g_main_ctx.argc = 0; + g_main_ctx.arg_len = 0; + for (i = 0; argv[i]; ++i) { + g_main_ctx.arg_len += strlen(argv[i]) + 1; + } + g_main_ctx.argc = i; + char* argp = NULL; + SAFE_ALLOC(argp, g_main_ctx.arg_len); + SAFE_ALLOC(g_main_ctx.save_argv, (g_main_ctx.argc + 1) * sizeof(char*)); + char* cmdline = NULL; + SAFE_ALLOC(cmdline, g_main_ctx.arg_len); + g_main_ctx.cmdline = cmdline; + for (i = 0; argv[i]; ++i) { + strcpy(argp, argv[i]); + g_main_ctx.save_argv[i] = argp; + argp += strlen(argv[i]) + 1; + + strcpy(cmdline, argv[i]); + cmdline += strlen(argv[i]); + *cmdline = ' '; + ++cmdline; + } + g_main_ctx.save_argv[g_main_ctx.argc] = NULL; + g_main_ctx.cmdline[g_main_ctx.arg_len-1] = '\0'; + +#if defined(OS_WIN) || defined(OS_LINUX) || defined(OS_DARWIN) + // save env + g_main_ctx.os_envp = environ; + g_main_ctx.envc = 0; + g_main_ctx.env_len = 0; + for (i = 0; environ[i]; ++i) { + g_main_ctx.env_len += strlen(environ[i]) + 1; + } + g_main_ctx.envc = i; + char* envp = NULL; + SAFE_ALLOC(envp, g_main_ctx.env_len); + SAFE_ALLOC(g_main_ctx.save_envp, (g_main_ctx.envc + 1) * sizeof(char*)); + for (i = 0; environ[i]; ++i) { + g_main_ctx.save_envp[i] = envp; + strcpy(g_main_ctx.save_envp[i], environ[i]); + envp += strlen(environ[i]) + 1; + } + g_main_ctx.save_envp[g_main_ctx.envc] = NULL; +#endif + + // signals + g_main_ctx.reload_fn = NULL; + g_main_ctx.reload_userdata = NULL; + + // master workers + g_main_ctx.worker_processes = 0; + g_main_ctx.worker_threads = 0; + g_main_ctx.worker_fn = 0; + g_main_ctx.worker_userdata = 0; + g_main_ctx.proc_ctxs = NULL; + + atexit(main_ctx_free); + return 0; +} + +void main_ctx_free(void) { + if (g_main_ctx.save_argv) { + SAFE_FREE(g_main_ctx.save_argv[0]); + SAFE_FREE(g_main_ctx.save_argv); + } + SAFE_FREE(g_main_ctx.cmdline); + if (g_main_ctx.save_envp) { + SAFE_FREE(g_main_ctx.save_envp[0]); + SAFE_FREE(g_main_ctx.save_envp); + } + if (g_main_ctx.arg_kv) { + for (int i = 0; i < g_main_ctx.arg_kv_size; ++i) { + SAFE_FREE(g_main_ctx.arg_kv[i]); + } + SAFE_FREE(g_main_ctx.arg_kv); + } + if (g_main_ctx.arg_list) { + for (int i = 0; i < g_main_ctx.arg_list_size; ++i) { + SAFE_FREE(g_main_ctx.arg_list[i]); + } + SAFE_FREE(g_main_ctx.arg_list); + } +} + +#define UNDEFINED_OPTION -1 +static int get_arg_type(int short_opt, const char* options) { + if (options == NULL) return UNDEFINED_OPTION; + const char* p = options; + while (*p && *p != short_opt) ++p; + if (*p == '\0') return UNDEFINED_OPTION; + if (*(p+1) == ':') return REQUIRED_ARGUMENT; + return NO_ARGUMENT; +} + +int parse_opt(int argc, char** argv, const char* options) { + if (argc < 1) return 0; + init_arg_kv(strlen(options) + 1); + init_arg_list(argc); + + for (int i = 1; argv[i]; ++i) { + char* p = argv[i]; + if (*p != '-') { + save_arg_list(argv[i]); + continue; + } + while (*++p) { + int arg_type = get_arg_type(*p, options); + if (arg_type == UNDEFINED_OPTION) { + printf("Invalid option '%c'\n", *p); + return -20; + } else if (arg_type == NO_ARGUMENT) { + save_arg_kv(p, 1, OPTION_ENABLE, 0); + continue; + } else if (arg_type == REQUIRED_ARGUMENT) { + if (*(p+1) != '\0') { + save_arg_kv(p, 1, p+1, 0); + break; + } else if (argv[i+1] != NULL) { + save_arg_kv(p, 1, argv[++i], 0); + break; + } else { + printf("Option '%c' requires param\n", *p); + return -30; + } + } + } + } + return 0; +} + +static const option_t* get_option(const char* opt, const option_t* long_options, int size) { + if (opt == NULL || long_options == NULL) return NULL; + int len = strlen(opt); + if (len == 0) return NULL; + if (len == 1) { + for (int i = 0; i < size; ++i) { + if (long_options[i].short_opt == *opt) { + return &long_options[i]; + } + } + } else { + for (int i = 0; i < size; ++i) { + if (strcmp(long_options[i].long_opt, opt) == 0) { + return &long_options[i]; + } + } + } + + return NULL; +} + +#define MAX_OPTION 32 +// opt type +#define NOPREFIX_OPTION 0 +#define SHORT_OPTION -1 +#define LONG_OPTION -2 +int parse_opt_long(int argc, char** argv, const option_t* long_options, int size) { + if (argc < 1) return 0; + init_arg_kv(size + 1); + init_arg_list(argc); + + char opt[MAX_OPTION+1] = {0}; + for (int i = 1; argv[i]; ++i) { + char* arg = argv[i]; + int opt_type = NOPREFIX_OPTION; + // prefix + if (*arg == OPTION_PREFIX) { + ++arg; + opt_type = SHORT_OPTION; + if (*arg == OPTION_PREFIX) { + ++arg; + opt_type = LONG_OPTION; + } + } + int arg_len = strlen(arg); + // delim + char* delim = strchr(arg, OPTION_DELIM); + if (delim) { + if (delim == arg || delim == arg+arg_len-1 || delim-arg > MAX_OPTION) { + printf("Invalid option '%s'\n", argv[i]); + return -10; + } + memcpy(opt, arg, delim-arg); + opt[delim-arg] = '\0'; + } else { + if (opt_type == SHORT_OPTION) { + *opt = *arg; + opt[1] = '\0'; + } else { + strncpy(opt, arg, MAX_OPTION); + } + } + // get_option + const option_t* pOption = get_option(opt, long_options, size); + if (pOption == NULL) { + if (delim == NULL && opt_type == NOPREFIX_OPTION) { + save_arg_list(arg); + continue; + } else { + printf("Invalid option: '%s'\n", argv[i]); + return -10; + } + } + const char* value = NULL; + if (pOption->arg_type == NO_ARGUMENT) { + // -h + value = OPTION_ENABLE; + } else if (pOption->arg_type == REQUIRED_ARGUMENT) { + if (delim) { + // --port=80 + value = delim+1; + } else { + if (opt_type == SHORT_OPTION && *(arg+1) != '\0') { + // p80 + value = arg+1; + } else if (argv[i+1] != NULL) { + // --port 80 + value = argv[++i]; + } else { + printf("Option '%s' requires parament\n", opt); + return -20; + } + } + } + // preferred to use short_opt as key + if (pOption->short_opt > 0) { + save_arg_kv(&pOption->short_opt, 1, value, 0); + } else if (pOption->long_opt) { + save_arg_kv(pOption->long_opt, 0, value, 0); + } + } + return 0; +} + +#if defined(OS_UNIX) && !HAVE_SETPROCTITLE +/* + * memory layout + * argv[0]\0argv[1]\0argv[n]\0env[0]\0env[1]\0env[n]\0 + */ +void setproctitle(const char* fmt, ...) { + char buf[256] = {0}; + va_list ap; + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf) - 1, fmt, ap); + va_end(ap); + + int len = g_main_ctx.arg_len + g_main_ctx.env_len; + if (g_main_ctx.os_argv && len) { + strncpy(g_main_ctx.os_argv[0], buf, len-1); + } +} +#endif + +int create_pidfile() { + FILE* fp = fopen(g_main_ctx.pidfile, "w"); + if (fp == NULL) { + hloge("fopen('%s') error: %d", g_main_ctx.pidfile, errno); + return -1; + } + + g_main_ctx.pid = hv_getpid(); + fprintf(fp, "%d\n", (int)g_main_ctx.pid); + fclose(fp); + hlogi("create_pidfile('%s') pid=%d", g_main_ctx.pidfile, g_main_ctx.pid); + atexit(delete_pidfile); + return 0; +} + +void delete_pidfile(void) { + hlogi("delete_pidfile('%s') pid=%d", g_main_ctx.pidfile, g_main_ctx.pid); + remove(g_main_ctx.pidfile); +} + +pid_t getpid_from_pidfile() { + FILE* fp = fopen(g_main_ctx.pidfile, "r"); + if (fp == NULL) { + // hloge("fopen('%s') error: %d", g_main_ctx.pidfile, errno); + return -1; + } + int pid = -1; + int _ = fscanf(fp, "%d", &pid); + fclose(fp); + return pid; +} + +#ifdef OS_UNIX +// unix use signal +#include + +void signal_handler(int signo) { + hlogi("pid=%d recv signo=%d", getpid(), signo); + switch (signo) { + case SIGINT: + case SIGNAL_TERMINATE: + hlogi("killall processes"); + signal(SIGCHLD, SIG_IGN); + // master send SIGKILL => workers + for (int i = 0; i < g_main_ctx.worker_processes; ++i) { + if (g_main_ctx.proc_ctxs[i].pid <= 0) break; + kill(g_main_ctx.proc_ctxs[i].pid, SIGKILL); + g_main_ctx.proc_ctxs[i].pid = -1; + } + exit(0); + break; + case SIGCHLD: + { + pid_t pid = 0; + int status = 0; + while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { + hlogw("proc stop/waiting, pid=%d status=%d", pid, status); + for (int i = 0; i < g_main_ctx.worker_processes; ++i) { + proc_ctx_t* ctx = g_main_ctx.proc_ctxs + i; + if (ctx->pid == pid) { + ctx->pid = -1; + // NOTE: avoid frequent crash and restart + time_t run_time = time(NULL) - ctx->start_time; + if (ctx->spawn_cnt < 3 || run_time > 3600) { + hproc_spawn(ctx); + } + else { + hloge("proc crash, pid=%d spawn_cnt=%d run_time=%us", + pid, ctx->spawn_cnt, (unsigned int)run_time); + + bool have_worker = false; + for (int i = 0; i < g_main_ctx.worker_processes; ++i) { + if (g_main_ctx.proc_ctxs[i].pid > 0) { + have_worker = true; + break; + } + } + if (!have_worker) { + hlogw("No alive worker process, exit master process!"); + exit(0); + } + } + break; + } + } + } + } + break; + case SIGNAL_RELOAD: + if (g_main_ctx.reload_fn) { + g_main_ctx.reload_fn(g_main_ctx.reload_userdata); + if (getpid_from_pidfile() == getpid()) { + // master send SIGNAL_RELOAD => workers + for (int i = 0; i < g_main_ctx.worker_processes; ++i) { + if (g_main_ctx.proc_ctxs[i].pid <= 0) break; + kill(g_main_ctx.proc_ctxs[i].pid, SIGNAL_RELOAD); + } + } + } + break; + default: + break; + } +} + +int signal_init(procedure_t reload_fn, void* reload_userdata) { + g_main_ctx.reload_fn = reload_fn; + g_main_ctx.reload_userdata = reload_userdata; + + signal(SIGINT, signal_handler); + signal(SIGCHLD, signal_handler); + signal(SIGNAL_TERMINATE, signal_handler); + signal(SIGNAL_RELOAD, signal_handler); + + return 0; +} + +#elif defined(OS_WIN) +#include // for timeSetEvent + +// win32 use Event +//static HANDLE s_hEventTerm = NULL; +static HANDLE s_hEventReload = NULL; + +static void WINAPI on_timer(UINT uTimerID, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2) { + DWORD ret; + /* + ret = WaitForSingleObject(s_hEventTerm, 0); + if (ret == WAIT_OBJECT_0) { + hlogi("pid=%d recv event [TERM]", getpid()); + if (getpid_from_pidfile() == getpid()) { + timeKillEvent(uTimerID); + exit(0); + } + } + */ + + ret = WaitForSingleObject(s_hEventReload, 0); + if (ret == WAIT_OBJECT_0) { + hlogi("pid=%d recv event [RELOAD]", getpid()); + if (g_main_ctx.reload_fn) { + g_main_ctx.reload_fn(g_main_ctx.reload_userdata); + } + } +} + +static void signal_cleanup(void) { + //CloseHandle(s_hEventTerm); + //s_hEventTerm = NULL; + CloseHandle(s_hEventReload); + s_hEventReload = NULL; +} + +int signal_init(procedure_t reload_fn, void* reload_userdata) { + g_main_ctx.reload_fn = reload_fn; + g_main_ctx.reload_userdata = reload_userdata; + + char eventname[MAX_PATH] = {0}; + //snprintf(eventname, sizeof(eventname), "%s_term_event", g_main_ctx.program_name); + //s_hEventTerm = CreateEvent(NULL, FALSE, FALSE, eventname); + //s_hEventTerm = OpenEvent(EVENT_ALL_ACCESS, FALSE, eventname); + snprintf(eventname, sizeof(eventname), "%s_reload_event", g_main_ctx.program_name); + s_hEventReload = CreateEvent(NULL, FALSE, FALSE, eventname); + + timeSetEvent(1000, 1000, on_timer, 0, TIME_PERIODIC); + + atexit(signal_cleanup); + return 0; +} +#endif + +static void kill_proc(int pid) { +#ifdef OS_UNIX + kill(pid, SIGNAL_TERMINATE); +#else + //SetEvent(s_hEventTerm); + //hv_sleep(1); + HANDLE hproc = OpenProcess(PROCESS_TERMINATE, FALSE, pid); + if (hproc) { + TerminateProcess(hproc, 0); + CloseHandle(hproc); + } +#endif +} + +void signal_handle(const char* signal) { + if (strcmp(signal, "start") == 0) { + if (g_main_ctx.oldpid > 0) { + printf("%s is already running, pid=%d\n", g_main_ctx.program_name, g_main_ctx.oldpid); + exit(0); + } + } else if (strcmp(signal, "stop") == 0) { + if (g_main_ctx.oldpid > 0) { + kill_proc(g_main_ctx.oldpid); + printf("%s stop/waiting\n", g_main_ctx.program_name); + } else { + printf("%s is already stopped\n", g_main_ctx.program_name); + } + exit(0); + } else if (strcmp(signal, "restart") == 0) { + if (g_main_ctx.oldpid > 0) { + kill_proc(g_main_ctx.oldpid); + printf("%s stop/waiting\n", g_main_ctx.program_name); + hv_sleep(1); + } + } else if (strcmp(signal, "status") == 0) { + if (g_main_ctx.oldpid > 0) { + printf("%s start/running, pid=%d\n", g_main_ctx.program_name, g_main_ctx.oldpid); + } else { + printf("%s stop/waiting\n", g_main_ctx.program_name); + } + exit(0); + } else if (strcmp(signal, "reload") == 0) { + if (g_main_ctx.oldpid > 0) { + printf("reload confile [%s]\n", g_main_ctx.confile); +#ifdef OS_UNIX + kill(g_main_ctx.oldpid, SIGNAL_RELOAD); +#else + SetEvent(s_hEventReload); +#endif + } + hv_sleep(1); + exit(0); + } else { + printf("Invalid signal: '%s'\n", signal); + exit(0); + } + printf("%s start/running\n", g_main_ctx.program_name); +} + +// master-workers processes +static HTHREAD_ROUTINE(worker_thread) { + hlogi("worker_thread pid=%ld tid=%ld", hv_getpid(), hv_gettid()); + if (g_main_ctx.worker_fn) { + g_main_ctx.worker_fn(g_main_ctx.worker_userdata); + } + return 0; +} + +static void worker_init(void* userdata) { +#ifdef OS_UNIX + setproctitle("%s: worker process", g_main_ctx.program_name); + signal(SIGNAL_RELOAD, signal_handler); +#endif +} + +static void worker_proc(void* userdata) { + for (int i = 1; i < g_main_ctx.worker_threads; ++i) { + hthread_create(worker_thread, NULL); + } + worker_thread(NULL); +} + +int master_workers_run(procedure_t worker_fn, void* worker_userdata, + int worker_processes, int worker_threads, bool wait) { +#ifdef OS_WIN + // NOTE: Windows not provide MultiProcesses + if (worker_threads == 0) { + // MultiProcesses => MultiThreads + worker_threads = worker_processes; + } + worker_processes = 0; +#endif + if (worker_threads == 0) worker_threads = 1; + + g_main_ctx.worker_threads = worker_threads; + g_main_ctx.worker_fn = worker_fn; + g_main_ctx.worker_userdata = worker_userdata; + + if (worker_processes == 0) { + // single process + if (wait) { + for (int i = 1; i < worker_threads; ++i) { + hthread_create(worker_thread, NULL); + } + worker_thread(NULL); + } + else { + for (int i = 0; i < worker_threads; ++i) { + hthread_create(worker_thread, NULL); + } + } + } + else { + if (g_main_ctx.worker_processes != 0) { + return ERR_OVER_LIMIT; + } + // master-workers processes +#ifdef OS_UNIX + setproctitle("%s: master process", g_main_ctx.program_name); + signal(SIGNAL_RELOAD, signal_handler); +#endif + g_main_ctx.worker_processes = worker_processes; + int bytes = g_main_ctx.worker_processes * sizeof(proc_ctx_t); + SAFE_ALLOC(g_main_ctx.proc_ctxs, bytes); + proc_ctx_t* ctx = g_main_ctx.proc_ctxs; + for (int i = 0; i < g_main_ctx.worker_processes; ++i, ++ctx) { + ctx->init = worker_init; + ctx->proc = worker_proc; + hproc_spawn(ctx); + hlogi("workers[%d] start/running, pid=%d", i, ctx->pid); + } + g_main_ctx.pid = getpid(); + hlogi("master start/running, pid=%d", g_main_ctx.pid); + if (wait) { + while (1) hv_sleep (1); + } + } + return 0; +} + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif diff --git a/ww/libhv/base/hmain.h b/ww/libhv/base/hmain.h new file mode 100644 index 00000000..3fe65989 --- /dev/null +++ b/ww/libhv/base/hmain.h @@ -0,0 +1,117 @@ +#ifndef HV_MAIN_H_ +#define HV_MAIN_H_ + +#include "hexport.h" +#include "hplatform.h" +#include "hdef.h" +#include "hproc.h" + +#ifdef _MSC_VER +#pragma comment(lib, "winmm.lib") // for timeSetEvent +#endif + +BEGIN_EXTERN_C + +typedef struct main_ctx_s { + char run_dir[MAX_PATH]; + char program_name[MAX_PATH]; + + char confile[MAX_PATH]; // default etc/${program}.conf + char pidfile[MAX_PATH]; // default logs/${program}.pid + char logfile[MAX_PATH]; // default logs/${program}.log + + pid_t pid; // getpid + pid_t oldpid; // getpid_from_pidfile + + // arg + int argc; + int arg_len; + char** os_argv; + char** save_argv; + char* cmdline; + // parsed arg + int arg_kv_size; + char** arg_kv; + int arg_list_size; + char** arg_list; + + // env + int envc; + int env_len; + char** os_envp; + char** save_envp; + + // signals + procedure_t reload_fn; + void* reload_userdata; + // master workers model + int worker_processes; + int worker_threads; + procedure_t worker_fn; + void* worker_userdata; + proc_ctx_t* proc_ctxs; +} main_ctx_t; + +// arg_type +#define NO_ARGUMENT 0 +#define REQUIRED_ARGUMENT 1 +#define OPTIONAL_ARGUMENT 2 +// option define +#define OPTION_PREFIX '-' +#define OPTION_DELIM '=' +#define OPTION_ENABLE "1" +#define OPTION_DISABLE "0" +typedef struct option_s { + char short_opt; + const char* long_opt; + int arg_type; +} option_t; + +HV_EXPORT int main_ctx_init(int argc, char** argv); +HV_EXPORT void main_ctx_free(void); + +// ls -a -l +// ls -al +// watch -n 10 ls +// watch -n10 ls +HV_EXPORT int parse_opt(int argc, char** argv, const char* opt); +// gcc -g -Wall -O3 -std=cpp main.c +HV_EXPORT int parse_opt_long(int argc, char** argv, const option_t* long_options, int size); +HV_EXPORT const char* get_arg(const char* key); +HV_EXPORT const char* get_env(const char* key); + +#if defined(OS_UNIX) && !HAVE_SETPROCTITLE +HV_EXPORT void setproctitle(const char* fmt, ...); +#endif + +// pidfile +HV_EXPORT int create_pidfile(); +HV_EXPORT void delete_pidfile(void); +HV_EXPORT pid_t getpid_from_pidfile(); + +// signal=[start,stop,restart,status,reload] +HV_EXPORT int signal_init(procedure_t reload_fn DEFAULT(NULL), void* reload_userdata DEFAULT(NULL)); +HV_EXPORT void signal_handle(const char* signal); +#ifdef OS_UNIX +// we use SIGTERM to quit process, SIGUSR1 to reload confile +#define SIGNAL_TERMINATE SIGTERM +#define SIGNAL_RELOAD SIGUSR1 +void signal_handler(int signo); +#endif + +// global var +#define DEFAULT_WORKER_PROCESSES 4 +#define MAXNUM_WORKER_PROCESSES 256 +HV_EXPORT extern main_ctx_t g_main_ctx; + +// master-workers processes +HV_EXPORT int master_workers_run( + procedure_t worker_fn, + void* worker_userdata DEFAULT(NULL), + int worker_processes DEFAULT(DEFAULT_WORKER_PROCESSES), + int worker_threads DEFAULT(0), + bool wait DEFAULT(true)); + +END_EXTERN_C + +#endif // HV_MAIN_H_ diff --git a/ww/libhv/base/hmath.h b/ww/libhv/base/hmath.h new file mode 100644 index 00000000..d29fb01f --- /dev/null +++ b/ww/libhv/base/hmath.h @@ -0,0 +1,68 @@ +#ifndef HV_MATH_H_ +#define HV_MATH_H_ + +#include + +static inline unsigned long floor2e(unsigned long num) { + unsigned long n = num; + int e = 0; + while (n>>=1) ++e; + unsigned long ret = 1; + while (e--) ret<<=1; + return ret; +} + +static inline unsigned long ceil2e(unsigned long num) { + // 2**0 = 1 + if (num == 0 || num == 1) return 1; + unsigned long n = num - 1; + int e = 1; + while (n>>=1) ++e; + unsigned long ret = 1; + while (e--) ret<<=1; + return ret; +} + +// varint little-endian +// MSB +static inline int varint_encode(long long value, unsigned char* buf) { + unsigned char ch; + unsigned char *p = buf; + int bytes = 0; + do { + ch = value & 0x7F; + value >>= 7; + *p++ = value == 0 ? ch : (ch | 0x80); + ++bytes; + } while (value); + return bytes; +} + +// @param[IN|OUT] len: in=>buflen, out=>varint bytesize +static inline long long varint_decode(const unsigned char* buf, int* len) { + long long ret = 0; + int bytes = 0, bits = 0; + const unsigned char *p = buf; + do { + if (len && *len && bytes == *len) { + // Not enough length + *len = 0; + return 0; + } + ret |= ((long long)(*p & 0x7F)) << bits; + ++bytes; + if ((*p & 0x80) == 0) { + // Found end + if (len) *len = bytes; + return ret; + } + ++p; + bits += 7; + } while(bytes < 10); + + // Not found end + if (len) *len = -1; + return ret; +} + +#endif // HV_MATH_H_ diff --git a/ww/libhv/base/hmutex.h b/ww/libhv/base/hmutex.h new file mode 100644 index 00000000..34960396 --- /dev/null +++ b/ww/libhv/base/hmutex.h @@ -0,0 +1,268 @@ +#ifndef HV_MUTEX_H_ +#define HV_MUTEX_H_ + +#include "hexport.h" +#include "hplatform.h" +#include "htime.h" + +BEGIN_EXTERN_C + +#ifdef OS_WIN +#define hmutex_t CRITICAL_SECTION +#define hmutex_init InitializeCriticalSection +#define hmutex_destroy DeleteCriticalSection +#define hmutex_lock EnterCriticalSection +#define hmutex_unlock LeaveCriticalSection + +#define hrecursive_mutex_t CRITICAL_SECTION +#define hrecursive_mutex_init InitializeCriticalSection +#define hrecursive_mutex_destroy DeleteCriticalSection +#define hrecursive_mutex_lock EnterCriticalSection +#define hrecursive_mutex_unlock LeaveCriticalSection + +#define HSPINLOCK_COUNT -1 +#define hspinlock_t CRITICAL_SECTION +#define hspinlock_init(pspin) InitializeCriticalSectionAndSpinCount(pspin, HSPINLOCK_COUNT) +#define hspinlock_destroy DeleteCriticalSection +#define hspinlock_lock EnterCriticalSection +#define hspinlock_unlock LeaveCriticalSection + +#define hrwlock_t SRWLOCK +#define hrwlock_init InitializeSRWLock +#define hrwlock_destroy(plock) +#define hrwlock_rdlock AcquireSRWLockShared +#define hrwlock_rdunlock ReleaseSRWLockShared +#define hrwlock_wrlock AcquireSRWLockExclusive +#define hrwlock_wrunlock ReleaseSRWLockExclusive + +#define htimed_mutex_t HANDLE +#define htimed_mutex_init(pmutex) *(pmutex) = CreateMutex(NULL, FALSE, NULL) +#define htimed_mutex_destroy(pmutex) CloseHandle(*(pmutex)) +#define htimed_mutex_lock(pmutex) WaitForSingleObject(*(pmutex), INFINITE) +#define htimed_mutex_unlock(pmutex) ReleaseMutex(*(pmutex)) +// true: WAIT_OBJECT_0 +// false: WAIT_OBJECT_TIMEOUT +#define htimed_mutex_lock_for(pmutex, ms) ( WaitForSingleObject(*(pmutex), ms) == WAIT_OBJECT_0 ) + +#define hcondvar_t CONDITION_VARIABLE +#define hcondvar_init InitializeConditionVariable +#define hcondvar_destroy(pcond) +#define hcondvar_wait(pcond, pmutex) SleepConditionVariableCS(pcond, pmutex, INFINITE) +#define hcondvar_wait_for(pcond, pmutex, ms) SleepConditionVariableCS(pcond, pmutex, ms) +#define hcondvar_signal WakeConditionVariable +#define hcondvar_broadcast WakeAllConditionVariable + +#define honce_t INIT_ONCE +#define HONCE_INIT INIT_ONCE_STATIC_INIT +typedef void (*honce_fn)(); +static inline BOOL WINAPI s_once_func(INIT_ONCE* once, PVOID arg, PVOID* _) { + honce_fn fn = (honce_fn)arg; + fn(); + return TRUE; +} +static inline void honce(honce_t* once, honce_fn fn) { + PVOID dummy = NULL; + InitOnceExecuteOnce(once, s_once_func, (PVOID)fn, &dummy); +} + +#define hsem_t HANDLE +#define hsem_init(psem, value) *(psem) = CreateSemaphore(NULL, value, value+100000, NULL) +#define hsem_destroy(psem) CloseHandle(*(psem)) +#define hsem_wait(psem) WaitForSingleObject(*(psem), INFINITE) +#define hsem_post(psem) ReleaseSemaphore(*(psem), 1, NULL) +// true: WAIT_OBJECT_0 +// false: WAIT_OBJECT_TIMEOUT +#define hsem_wait_for(psem, ms) ( WaitForSingleObject(*(psem), ms) == WAIT_OBJECT_0 ) + +#else +#define hmutex_t pthread_mutex_t +#define hmutex_init(pmutex) pthread_mutex_init(pmutex, NULL) +#define hmutex_destroy pthread_mutex_destroy +#define hmutex_lock pthread_mutex_lock +#define hmutex_unlock pthread_mutex_unlock + +#define hrecursive_mutex_t pthread_mutex_t +#define hrecursive_mutex_init(pmutex) \ + do {\ + pthread_mutexattr_t attr;\ + pthread_mutexattr_init(&attr);\ + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);\ + pthread_mutex_init(pmutex, &attr);\ + } while(0) +#define hrecursive_mutex_destroy pthread_mutex_destroy +#define hrecursive_mutex_lock pthread_mutex_lock +#define hrecursive_mutex_unlock pthread_mutex_unlock + +#if HAVE_PTHREAD_SPIN_LOCK +#define hspinlock_t pthread_spinlock_t +#define hspinlock_init(pspin) pthread_spin_init(pspin, PTHREAD_PROCESS_PRIVATE) +#define hspinlock_destroy pthread_spin_destroy +#define hspinlock_lock pthread_spin_lock +#define hspinlock_unlock pthread_spin_unlock +#else +#define hspinlock_t pthread_mutex_t +#define hspinlock_init(pmutex) pthread_mutex_init(pmutex, NULL) +#define hspinlock_destroy pthread_mutex_destroy +#define hspinlock_lock pthread_mutex_lock +#define hspinlock_unlock pthread_mutex_unlock +#endif + +#define hrwlock_t pthread_rwlock_t +#define hrwlock_init(prwlock) pthread_rwlock_init(prwlock, NULL) +#define hrwlock_destroy pthread_rwlock_destroy +#define hrwlock_rdlock pthread_rwlock_rdlock +#define hrwlock_rdunlock pthread_rwlock_unlock +#define hrwlock_wrlock pthread_rwlock_wrlock +#define hrwlock_wrunlock pthread_rwlock_unlock + +#define htimed_mutex_t pthread_mutex_t +#define htimed_mutex_init(pmutex) pthread_mutex_init(pmutex, NULL) +#define htimed_mutex_destroy pthread_mutex_destroy +#define htimed_mutex_lock pthread_mutex_lock +#define htimed_mutex_unlock pthread_mutex_unlock +static inline void timespec_after(struct timespec* ts, unsigned int ms) { + struct timeval tv; + gettimeofday(&tv, NULL); + ts->tv_sec = tv.tv_sec + ms / 1000; + ts->tv_nsec = tv.tv_usec * 1000 + ms % 1000 * 1000000; + if (ts->tv_nsec >= 1000000000) { + ts->tv_nsec -= 1000000000; + ts->tv_sec += 1; + } +} +// true: OK +// false: ETIMEDOUT +static inline int htimed_mutex_lock_for(htimed_mutex_t* mutex, unsigned int ms) { +#if HAVE_PTHREAD_MUTEX_TIMEDLOCK + struct timespec ts; + timespec_after(&ts, ms); + return pthread_mutex_timedlock(mutex, &ts) != ETIMEDOUT; +#else + int ret = 0; + unsigned int end = gettick_ms() + ms; + while ((ret = pthread_mutex_trylock(mutex)) != 0) { + if (gettick_ms() >= end) { + break; + } + hv_msleep(1); + } + return ret == 0; +#endif +} + +#define hcondvar_t pthread_cond_t +#define hcondvar_init(pcond) pthread_cond_init(pcond, NULL) +#define hcondvar_destroy pthread_cond_destroy +#define hcondvar_wait pthread_cond_wait +#define hcondvar_signal pthread_cond_signal +#define hcondvar_broadcast pthread_cond_broadcast +// true: OK +// false: ETIMEDOUT +static inline int hcondvar_wait_for(hcondvar_t* cond, hmutex_t* mutex, unsigned int ms) { + struct timespec ts; + timespec_after(&ts, ms); + return pthread_cond_timedwait(cond, mutex, &ts) != ETIMEDOUT; +} + +#define honce_t pthread_once_t +#define HONCE_INIT PTHREAD_ONCE_INIT +#define honce pthread_once + +#include +#define hsem_t sem_t +#define hsem_init(psem, value) sem_init(psem, 0, value) +#define hsem_destroy sem_destroy +#define hsem_wait sem_wait +#define hsem_post sem_post +// true: OK +// false: ETIMEDOUT +static inline int hsem_wait_for(hsem_t* sem, unsigned int ms) { +#if HAVE_SEM_TIMEDWAIT + struct timespec ts; + timespec_after(&ts, ms); + return sem_timedwait(sem, &ts) != ETIMEDOUT; +#else + int ret = 0; + unsigned int end = gettick_ms() + ms; + while ((ret = sem_trywait(sem)) != 0) { + if (gettick_ms() >= end) { + break; + } + hv_msleep(1); + } + return ret == 0; +#endif +} + +#endif + +END_EXTERN_C + +#ifdef __cplusplus +#include +#include +// using std::mutex; +// NOTE: test std::timed_mutex incorrect in some platforms, use htimed_mutex_t +// using std::timed_mutex; +using std::condition_variable; +using std::lock_guard; +using std::unique_lock; + +BEGIN_NAMESPACE_HV + +class MutexLock { +public: + MutexLock() { hmutex_init(&_mutex); } + ~MutexLock() { hmutex_destroy(&_mutex); } + + void lock() { hmutex_lock(&_mutex); } + void unlock() { hmutex_unlock(&_mutex); } +protected: + hmutex_t _mutex; +}; + +class SpinLock { +public: + SpinLock() { hspinlock_init(&_spin); } + ~SpinLock() { hspinlock_destroy(&_spin); } + + void lock() { hspinlock_lock(&_spin); } + void unlock() { hspinlock_unlock(&_spin); } +protected: + hspinlock_t _spin; +}; + +class RWLock { +public: + RWLock() { hrwlock_init(&_rwlock); } + ~RWLock() { hrwlock_destroy(&_rwlock); } + + void rdlock() { hrwlock_rdlock(&_rwlock); } + void rdunlock() { hrwlock_rdunlock(&_rwlock); } + + void wrlock() { hrwlock_wrlock(&_rwlock); } + void wrunlock() { hrwlock_wrunlock(&_rwlock); } + + void lock() { rdlock(); } + void unlock() { rdunlock(); } +protected: + hrwlock_t _rwlock; +}; + +template +class LockGuard { +public: + LockGuard(T& t) : _lock(t) { _lock.lock(); } + ~LockGuard() { _lock.unlock(); } +protected: + T& _lock; +}; + +END_NAMESPACE_HV + +// same as java synchronized(lock) { ... } +#define synchronized(lock) for (std::lock_guard _lock_(lock), *p = &_lock_; p != NULL; p = NULL) + +#endif // __cplusplus + +#endif // HV_MUTEX_H_ diff --git a/ww/libhv/base/hplatform.h b/ww/libhv/base/hplatform.h new file mode 100644 index 00000000..5e0a290c --- /dev/null +++ b/ww/libhv/base/hplatform.h @@ -0,0 +1,330 @@ +#ifndef HV_PLATFORM_H_ +#define HV_PLATFORM_H_ + +#include "hconfig.h" + +// OS +#if defined(WIN64) || defined(_WIN64) + #define OS_WIN64 + #define OS_WIN32 +#elif defined(WIN32)|| defined(_WIN32) + #define OS_WIN32 +#elif defined(ANDROID) || defined(__ANDROID__) + #define OS_ANDROID + #define OS_LINUX +#elif defined(linux) || defined(__linux) || defined(__linux__) + #define OS_LINUX +#elif defined(__APPLE__) && (defined(__GNUC__) || defined(__xlC__) || defined(__xlc__)) + #include + #if defined(TARGET_OS_MAC) && TARGET_OS_MAC + #define OS_MAC + #elif defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE + #define OS_IOS + #endif + #define OS_DARWIN +#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) + #define OS_FREEBSD + #define OS_BSD +#elif defined(__NetBSD__) + #define OS_NETBSD + #define OS_BSD +#elif defined(__OpenBSD__) + #define OS_OPENBSD + #define OS_BSD +#elif defined(sun) || defined(__sun) || defined(__sun__) + #define OS_SOLARIS +#else + #warning "Untested operating system platform!" +#endif + +#if defined(OS_WIN32) || defined(OS_WIN64) + #undef OS_UNIX + #define OS_WIN +#else + #define OS_UNIX +#endif + +// ARCH +#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(_M_X64) + #define ARCH_X64 + #define ARCH_X86_64 +#elif defined(__i386) || defined(__i386__) || defined(_M_IX86) + #define ARCH_X86 + #define ARCH_X86_32 +#elif defined(__aarch64__) || defined(__ARM64__) || defined(_M_ARM64) + #define ARCH_ARM64 +#elif defined(__arm__) || defined(_M_ARM) + #define ARCH_ARM +#elif defined(__mips64__) + #define ARCH_MIPS64 +#elif defined(__mips__) + #define ARCH_MIPS +#else + #warning "Untested hardware architecture!" +#endif + +// COMPILER +#if defined (_MSC_VER) +#define COMPILER_MSVC + +#if (_MSC_VER < 1200) // Visual C++ 6.0 +#define MSVS_VERSION 1998 +#define MSVC_VERSION 60 +#elif (_MSC_VER >= 1200) && (_MSC_VER < 1300) // Visual Studio 2002, MSVC++ 7.0 +#define MSVS_VERSION 2002 +#define MSVC_VERSION 70 +#elif (_MSC_VER >= 1300) && (_MSC_VER < 1400) // Visual Studio 2003, MSVC++ 7.1 +#define MSVS_VERSION 2003 +#define MSVC_VERSION 71 +#elif (_MSC_VER >= 1400) && (_MSC_VER < 1500) // Visual Studio 2005, MSVC++ 8.0 +#define MSVS_VERSION 2005 +#define MSVC_VERSION 80 +#elif (_MSC_VER >= 1500) && (_MSC_VER < 1600) // Visual Studio 2008, MSVC++ 9.0 +#define MSVS_VERSION 2008 +#define MSVC_VERSION 90 +#elif (_MSC_VER >= 1600) && (_MSC_VER < 1700) // Visual Studio 2010, MSVC++ 10.0 +#define MSVS_VERSION 2010 +#define MSVC_VERSION 100 +#elif (_MSC_VER >= 1700) && (_MSC_VER < 1800) // Visual Studio 2012, MSVC++ 11.0 +#define MSVS_VERSION 2012 +#define MSVC_VERSION 110 +#elif (_MSC_VER >= 1800) && (_MSC_VER < 1900) // Visual Studio 2013, MSVC++ 12.0 +#define MSVS_VERSION 2013 +#define MSVC_VERSION 120 +#elif (_MSC_VER >= 1900) && (_MSC_VER < 1910) // Visual Studio 2015, MSVC++ 14.0 +#define MSVS_VERSION 2015 +#define MSVC_VERSION 140 +#elif (_MSC_VER >= 1910) && (_MSC_VER < 1920) // Visual Studio 2017, MSVC++ 15.0 +#define MSVS_VERSION 2017 +#define MSVC_VERSION 150 +#elif (_MSC_VER >= 1920) && (_MSC_VER < 2000) // Visual Studio 2019, MSVC++ 16.0 +#define MSVS_VERSION 2019 +#define MSVC_VERSION 160 +#endif + +#undef HAVE_STDATOMIC_H +#define HAVE_STDATOMIC_H 0 +#undef HAVE_SYS_TIME_H +#define HAVE_SYS_TIME_H 0 +#undef HAVE_PTHREAD_H +#define HAVE_PTHREAD_H 0 + +#pragma warning (disable: 4018) // signed/unsigned comparison +#pragma warning (disable: 4100) // unused param +#pragma warning (disable: 4102) // unreferenced label +#pragma warning (disable: 4244) // conversion loss of data +#pragma warning (disable: 4267) // size_t => int +#pragma warning (disable: 4819) // Unicode +#pragma warning (disable: 4996) // _CRT_SECURE_NO_WARNINGS + +#elif defined(__GNUC__) +#define COMPILER_GCC + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE 1 +#endif + +#elif defined(__clang__) +#define COMPILER_CLANG + +#elif defined(__MINGW32__) || defined(__MINGW64__) +#define COMPILER_MINGW + +#elif defined(__MSYS__) +#define COMPILER_MSYS + +#elif defined(__CYGWIN__) +#define COMPILER_CYGWIN + +#else +#warning "Untested compiler!" +#endif + +// headers +#ifdef OS_WIN + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #ifndef _CRT_NONSTDC_NO_DEPRECATE + #define _CRT_NONSTDC_NO_DEPRECATE + #endif + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif + #ifndef _WINSOCK_DEPRECATED_NO_WARNINGS + #define _WINSOCK_DEPRECATED_NO_WARNINGS + #endif + #include + #include // for inet_pton,inet_ntop + #include + #include // for getpid,exec + #include // for mkdir,rmdir,chdir,getcwd + #include // for open,close,read,write,lseek,tell + + #define hv_sleep(s) Sleep((s) * 1000) + #define hv_msleep(ms) Sleep(ms) + #define hv_usleep(us) Sleep((us) / 1000) + #define hv_delay(ms) hv_msleep(ms) + #define hv_mkdir(dir) mkdir(dir) + + // access + #ifndef F_OK + #define F_OK 0 /* test for existence of file */ + #endif + #ifndef X_OK + #define X_OK (1<<0) /* test for execute or search permission */ + #endif + #ifndef W_OK + #define W_OK (1<<1) /* test for write permission */ + #endif + #ifndef R_OK + #define R_OK (1<<2) /* test for read permission */ + #endif + + // stat + #ifndef S_ISREG + #define S_ISREG(st_mode) (((st_mode) & S_IFMT) == S_IFREG) + #endif + #ifndef S_ISDIR + #define S_ISDIR(st_mode) (((st_mode) & S_IFMT) == S_IFDIR) + #endif +#else + #include + #include // for mkdir,rmdir,chdir,getcwd + + // socket + #include + #include + #include + #include + #include + #include + #include // for gethostbyname + + #define hv_sleep(s) sleep(s) + #define hv_msleep(ms) usleep((ms) * 1000) + #define hv_usleep(us) usleep(us) + #define hv_delay(ms) hv_msleep(ms) + #define hv_mkdir(dir) mkdir(dir, 0777) +#endif + +#ifdef _MSC_VER + typedef int pid_t; + typedef int gid_t; + typedef int uid_t; + #define strcasecmp stricmp + #define strncasecmp strnicmp +#else + typedef int BOOL; + typedef unsigned char BYTE; + typedef unsigned short WORD; + typedef void* HANDLE; + #include + #define stricmp strcasecmp + #define strnicmp strncasecmp +#endif + +// ENDIAN +#ifndef BIG_ENDIAN +#define BIG_ENDIAN 4321 +#endif +#ifndef LITTLE_ENDIAN +#define LITTLE_ENDIAN 1234 +#endif +#ifndef NET_ENDIAN +#define NET_ENDIAN BIG_ENDIAN +#endif + +// BYTE_ORDER +#ifndef BYTE_ORDER +#if defined(__BYTE_ORDER) + #define BYTE_ORDER __BYTE_ORDER +#elif defined(__BYTE_ORDER__) + #define BYTE_ORDER __BYTE_ORDER__ +#elif defined(ARCH_X86) || defined(ARCH_X86_64) || \ + defined(__ARMEL__) || defined(__AARCH64EL__) || \ + defined(__MIPSEL) || defined(__MIPS64EL) + #define BYTE_ORDER LITTLE_ENDIAN +#elif defined(__ARMEB__) || defined(__AARCH64EB__) || \ + defined(__MIPSEB) || defined(__MIPS64EB) + #define BYTE_ORDER BIG_ENDIAN +#elif defined(OS_WIN) + #define BYTE_ORDER LITTLE_ENDIAN +#else + #warning "Unknown byte order!" +#endif +#endif + +// ANSI C +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef __cplusplus +#if HAVE_STDBOOL_H +#include +#else + #ifndef bool + #define bool char + #endif + + #ifndef true + #define true 1 + #endif + + #ifndef false + #define false 0 + #endif +#endif +#endif + +#if HAVE_STDINT_H +#include +#elif defined(_MSC_VER) && _MSC_VER < 1700 +typedef __int8 int8_t; +typedef __int16 int16_t; +typedef __int32 int32_t; +typedef __int64 int64_t; +typedef unsigned __int8 uint8_t; +typedef unsigned __int16 uint16_t; +typedef unsigned __int32 uint32_t; +typedef unsigned __int64 uint64_t; +#endif + +typedef float float32_t; +typedef double float64_t; + +typedef int (*method_t)(void* userdata); +typedef void (*procedure_t)(void* userdata); + +#if HAVE_SYS_TYPES_H +#include +#endif + +#if HAVE_SYS_STAT_H +#include +#endif + +#if HAVE_SYS_TIME_H +#include // for gettimeofday +#endif + +#if HAVE_FCNTL_H +#include +#endif + +#if HAVE_PTHREAD_H +#include +#endif + +#endif // HV_PLATFORM_H_ diff --git a/ww/libhv/base/hproc.h b/ww/libhv/base/hproc.h new file mode 100644 index 00000000..b7835626 --- /dev/null +++ b/ww/libhv/base/hproc.h @@ -0,0 +1,69 @@ +#ifndef HV_PROC_H_ +#define HV_PROC_H_ + +#include "hplatform.h" + +typedef struct proc_ctx_s { + pid_t pid; // tid in Windows + time_t start_time; + int spawn_cnt; + procedure_t init; + void* init_userdata; + procedure_t proc; + void* proc_userdata; + procedure_t exit; + void* exit_userdata; +} proc_ctx_t; + +static inline void hproc_run(proc_ctx_t* ctx) { + if (ctx->init) { + ctx->init(ctx->init_userdata); + } + if (ctx->proc) { + ctx->proc(ctx->proc_userdata); + } + if (ctx->exit) { + ctx->exit(ctx->exit_userdata); + } +} + +#ifdef OS_UNIX +// unix use multi-processes +static inline int hproc_spawn(proc_ctx_t* ctx) { + ++ctx->spawn_cnt; + ctx->start_time = time(NULL); + pid_t pid = fork(); + if (pid < 0) { + perror("fork"); + return -1; + } else if (pid == 0) { + // child process + ctx->pid = getpid(); + hproc_run(ctx); + exit(0); + } else if (pid > 0) { + // parent process + ctx->pid = pid; + } + return pid; +} +#elif defined(OS_WIN) +// win32 use multi-threads +static void win_thread(void* userdata) { + proc_ctx_t* ctx = (proc_ctx_t*)userdata; + ctx->pid = GetCurrentThreadId(); // tid in Windows + hproc_run(ctx); +} +static inline int hproc_spawn(proc_ctx_t* ctx) { + ++ctx->spawn_cnt; + ctx->start_time = time(NULL); + HANDLE h = (HANDLE)_beginthread(win_thread, 0, ctx); + if (h == NULL) { + return -1; + } + ctx->pid = GetThreadId(h); // tid in Windows + return ctx->pid; +} +#endif + +#endif // HV_PROC_H_ diff --git a/ww/libhv/base/hsocket.c b/ww/libhv/base/hsocket.c new file mode 100644 index 00000000..da491b79 --- /dev/null +++ b/ww/libhv/base/hsocket.c @@ -0,0 +1,408 @@ +#include "hsocket.h" + +#include "hdef.h" + +#ifdef OS_WIN +#include "hatomic.h" +static hatomic_flag_t s_wsa_initialized = HATOMIC_FLAG_INIT; +void WSAInit() { + if (!hatomic_flag_test_and_set(&s_wsa_initialized)) { + WSADATA wsadata; + WSAStartup(MAKEWORD(2, 2), &wsadata); + } +} + +void WSADeinit() { + if (hatomic_flag_test_and_set(&s_wsa_initialized)) { + hatomic_flag_clear(&s_wsa_initialized); + WSACleanup(); + } +} +#endif + +static inline int socket_errno_negative(int sockfd) { + int err = socket_errno(); + if (sockfd >= 0) closesocket(sockfd); + return err > 0 ? -err : -1; +} + +const char* socket_strerror(int err) { +#ifdef OS_WIN + static char buffer[128]; + + FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS | + FORMAT_MESSAGE_MAX_WIDTH_MASK, + 0, ABS(err), 0, buffer, sizeof(buffer), NULL); + + return buffer; +#else + return strerror(ABS(err)); +#endif +} + +bool is_ipv4(const char* host) { + struct sockaddr_in sin; + return inet_pton(AF_INET, host, &sin) == 1; +} + +bool is_ipv6(const char* host) { + struct sockaddr_in6 sin6; + return inet_pton(AF_INET6, host, &sin6) == 1; +} + +int ResolveAddr(const char* host, sockaddr_u* addr) { +#ifdef OS_WIN + WSAInit(); +#endif + if (inet_pton(AF_INET, host, &addr->sin.sin_addr) == 1) { + addr->sa.sa_family = AF_INET; // host is ipv4, so easy ;) + return 0; + } + + if (inet_pton(AF_INET6, host, &addr->sin6.sin6_addr) == 1) { + addr->sa.sa_family = AF_INET6; // host is ipv6 + } + + struct addrinfo* ais = NULL; + int ret = getaddrinfo(host, NULL, NULL, &ais); + if (ret != 0 || ais == NULL || ais->ai_addr == NULL || ais->ai_addrlen == 0) { + printd("unknown host: %s err:%d:%s\n", host, ret, gai_strerror(ret)); + return ret; + } + struct addrinfo* pai = ais; + while (pai != NULL) { + if (pai->ai_family == AF_INET) break; + pai = pai->ai_next; + } + if (pai == NULL) pai = ais; + memcpy(addr, pai->ai_addr, pai->ai_addrlen); + freeaddrinfo(ais); + return 0; +} + +const char* sockaddr_ip(sockaddr_u* addr, char *ip, int len) { + if (addr->sa.sa_family == AF_INET) { + return inet_ntop(AF_INET, &addr->sin.sin_addr, ip, len); + } + else if (addr->sa.sa_family == AF_INET6) { + return inet_ntop(AF_INET6, &addr->sin6.sin6_addr, ip, len); + } + return ip; +} + +uint16_t sockaddr_port(sockaddr_u* addr) { + uint16_t port = 0; + if (addr->sa.sa_family == AF_INET) { + port = ntohs(addr->sin.sin_port); + } + else if (addr->sa.sa_family == AF_INET6) { + port = ntohs(addr->sin6.sin6_port); + } + return port; +} + +int sockaddr_set_ip(sockaddr_u* addr, const char* host) { + if (!host || *host == '\0') { + addr->sin.sin_family = AF_INET; + addr->sin.sin_addr.s_addr = htonl(INADDR_ANY); + return 0; + } + return ResolveAddr(host, addr); +} + +void sockaddr_set_port(sockaddr_u* addr, int port) { + if (addr->sa.sa_family == AF_INET) { + addr->sin.sin_port = htons(port); + } + else if (addr->sa.sa_family == AF_INET6) { + addr->sin6.sin6_port = htons(port); + } +} + +int sockaddr_set_ipport(sockaddr_u* addr, const char* host, int port) { +#ifdef ENABLE_UDS + if (port < 0) { + sockaddr_set_path(addr, host); + return 0; + } +#endif + int ret = sockaddr_set_ip(addr, host); + if (ret != 0) return ret; + sockaddr_set_port(addr, port); + // SOCKADDR_PRINT(addr); + return 0; +} + +socklen_t sockaddr_len(sockaddr_u* addr) { + if (addr->sa.sa_family == AF_INET) { + return sizeof(struct sockaddr_in); + } + else if (addr->sa.sa_family == AF_INET6) { + return sizeof(struct sockaddr_in6); + } +#ifdef ENABLE_UDS + else if (addr->sa.sa_family == AF_UNIX) { + return sizeof(struct sockaddr_un); + } +#endif + return sizeof(sockaddr_u); +} + +const char* sockaddr_str(sockaddr_u* addr, char* buf, int len) { + char ip[SOCKADDR_STRLEN] = {0}; + uint16_t port = 0; + if (addr->sa.sa_family == AF_INET) { + inet_ntop(AF_INET, &addr->sin.sin_addr, ip, len); + port = ntohs(addr->sin.sin_port); + snprintf(buf, len, "%s:%d", ip, port); + } + else if (addr->sa.sa_family == AF_INET6) { + inet_ntop(AF_INET6, &addr->sin6.sin6_addr, ip, len); + port = ntohs(addr->sin6.sin6_port); + snprintf(buf, len, "[%s]:%d", ip, port); + } +#ifdef ENABLE_UDS + else if (addr->sa.sa_family == AF_UNIX) { + snprintf(buf, len, "%s", addr->sun.sun_path); + } +#endif + return buf; +} + +static int sockaddr_bind(sockaddr_u* localaddr, int type) { + // socket -> setsockopt -> bind +#ifdef SOCK_CLOEXEC + type |= SOCK_CLOEXEC; +#endif + int sockfd = socket(localaddr->sa.sa_family, type, 0); + if (sockfd < 0) { + perror("socket"); + goto error; + } + +#ifdef OS_UNIX + so_reuseaddr(sockfd, 1); + // so_reuseport(sockfd, 1); +#endif + + if (localaddr->sa.sa_family == AF_INET6) { + ip_v6only(sockfd, 0); + } + + if (bind(sockfd, &localaddr->sa, sockaddr_len(localaddr)) < 0) { + perror("bind"); + goto error; + } + + return sockfd; +error: + return socket_errno_negative(sockfd); +} + +static int sockaddr_connect(sockaddr_u* peeraddr, int nonblock) { + // socket -> nonblocking -> connect + int ret = 0; + int connfd = socket(peeraddr->sa.sa_family, SOCK_STREAM, 0); + if (connfd < 0) { + perror("socket"); + goto error; + } + + if (nonblock) { + nonblocking(connfd); + } + + ret = connect(connfd, &peeraddr->sa, sockaddr_len(peeraddr)); +#ifdef OS_WIN + if (ret < 0 && socket_errno() != WSAEWOULDBLOCK) { +#else + if (ret < 0 && socket_errno() != EINPROGRESS) { +#endif + // perror("connect"); + goto error; + } + + return connfd; +error: + return socket_errno_negative(connfd); +} + +static int ListenFD(int sockfd) { + if (sockfd < 0) return sockfd; + if (listen(sockfd, SOMAXCONN) < 0) { + perror("listen"); + return socket_errno_negative(sockfd); + } + return sockfd; +} + +static int ConnectFDTimeout(int connfd, int ms) { + int err = 0; + socklen_t optlen = sizeof(err); + struct timeval tv = { ms / 1000, (ms % 1000) * 1000 }; + fd_set writefds; + FD_ZERO(&writefds); + FD_SET(connfd, &writefds); + int ret = select(connfd+1, 0, &writefds, 0, &tv); + if (ret < 0) { + perror("select"); + goto error; + } + if (ret == 0) { + errno = ETIMEDOUT; + goto error; + } + if (getsockopt(connfd, SOL_SOCKET, SO_ERROR, (char*)&err, &optlen) < 0 || err != 0) { + if (err != 0) errno = err; + goto error; + } + blocking(connfd); + return connfd; +error: + return socket_errno_negative(connfd); +} + +int Bind(int port, const char* host, int type) { +#ifdef OS_WIN + WSAInit(); +#endif + sockaddr_u localaddr; + memset(&localaddr, 0, sizeof(localaddr)); + int ret = sockaddr_set_ipport(&localaddr, host, port); + if (ret != 0) { + return NABS(ret); + } + return sockaddr_bind(&localaddr, type); +} + +int Listen(int port, const char* host) { + int sockfd = Bind(port, host, SOCK_STREAM); + if (sockfd < 0) return sockfd; + return ListenFD(sockfd); +} + +int Connect(const char* host, int port, int nonblock) { +#ifdef OS_WIN + WSAInit(); +#endif + sockaddr_u peeraddr; + memset(&peeraddr, 0, sizeof(peeraddr)); + int ret = sockaddr_set_ipport(&peeraddr, host, port); + if (ret != 0) { + return NABS(ret); + } + return sockaddr_connect(&peeraddr, nonblock); +} + +int ConnectNonblock(const char* host, int port) { + return Connect(host, port, 1); +} + +int ConnectTimeout(const char* host, int port, int ms) { + int connfd = Connect(host, port, 1); + if (connfd < 0) return connfd; + return ConnectFDTimeout(connfd, ms); +} + +#ifdef ENABLE_UDS +int BindUnix(const char* path, int type) { + sockaddr_u localaddr; + memset(&localaddr, 0, sizeof(localaddr)); + sockaddr_set_path(&localaddr, path); + return sockaddr_bind(&localaddr, type); +} + +int ListenUnix(const char* path) { + int sockfd = BindUnix(path, SOCK_STREAM); + if (sockfd < 0) return sockfd; + return ListenFD(sockfd); +} + +int ConnectUnix(const char* path, int nonblock) { + sockaddr_u peeraddr; + memset(&peeraddr, 0, sizeof(peeraddr)); + sockaddr_set_path(&peeraddr, path); + return sockaddr_connect(&peeraddr, nonblock); +} + +int ConnectUnixNonblock(const char* path) { + return ConnectUnix(path, 1); +} + +int ConnectUnixTimeout(const char* path, int ms) { + int connfd = ConnectUnix(path, 1); + if (connfd < 0) return connfd; + return ConnectFDTimeout(connfd, ms); +} +#endif + +int Socketpair(int family, int type, int protocol, int sv[2]) { +#if defined(OS_UNIX) && HAVE_SOCKETPAIR + return socketpair(AF_LOCAL, type, protocol, sv); +#endif + if (family != AF_INET || type != SOCK_STREAM) { + return -1; + } +#ifdef OS_WIN + WSAInit(); +#endif + int listenfd, connfd, acceptfd; + listenfd = connfd = acceptfd = -1; + struct sockaddr_in localaddr; + socklen_t addrlen = sizeof(localaddr); + memset(&localaddr, 0, addrlen); + localaddr.sin_family = AF_INET; + localaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + localaddr.sin_port = 0; + // listener + listenfd = socket(AF_INET, SOCK_STREAM, 0); + if (listenfd < 0) { + perror("socket"); + goto error; + } + if (bind(listenfd, (struct sockaddr*)&localaddr, addrlen) < 0) { + perror("bind"); + goto error; + } + if (listen(listenfd, 1) < 0) { + perror("listen"); + goto error; + } + if (getsockname(listenfd, (struct sockaddr*)&localaddr, &addrlen) < 0) { + perror("getsockname"); + goto error; + } + // connector + connfd = socket(AF_INET, SOCK_STREAM, 0); + if (connfd < 0) { + perror("socket"); + goto error; + } + if (connect(connfd, (struct sockaddr*)&localaddr, addrlen) < 0) { + perror("connect"); + goto error; + } + // acceptor + acceptfd = accept(listenfd, (struct sockaddr*)&localaddr, &addrlen); + if (acceptfd < 0) { + perror("accept"); + goto error; + } + + closesocket(listenfd); + sv[0] = connfd; + sv[1] = acceptfd; + return 0; +error: + if (listenfd != -1) { + closesocket(listenfd); + } + if (connfd != -1) { + closesocket(connfd); + } + if (acceptfd != -1) { + closesocket(acceptfd); + } + return -1; +} diff --git a/ww/libhv/base/hsocket.h b/ww/libhv/base/hsocket.h new file mode 100644 index 00000000..8459a7b0 --- /dev/null +++ b/ww/libhv/base/hsocket.h @@ -0,0 +1,285 @@ +#ifndef HV_SOCKET_H_ +#define HV_SOCKET_H_ + +#include "hexport.h" +#include "hplatform.h" + +#ifdef ENABLE_UDS +#ifdef OS_WIN + #include // import struct sockaddr_un +#else + #include // import struct sockaddr_un +#endif +#endif + +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") +#endif + +#define LOCALHOST "127.0.0.1" +#define ANYADDR "0.0.0.0" + +BEGIN_EXTERN_C + +HV_INLINE int socket_errno() { +#ifdef OS_WIN + return WSAGetLastError(); +#else + return errno; +#endif +} +HV_EXPORT const char* socket_strerror(int err); + +#ifdef OS_WIN + +typedef SOCKET hsocket_t; +typedef int socklen_t; + +void WSAInit(); +void WSADeinit(); + +HV_INLINE int blocking(int sockfd) { + unsigned long nb = 0; + return ioctlsocket(sockfd, FIONBIO, &nb); +} +HV_INLINE int nonblocking(int sockfd) { + unsigned long nb = 1; + return ioctlsocket(sockfd, FIONBIO, &nb); +} + +#undef EAGAIN +#define EAGAIN WSAEWOULDBLOCK + +#undef EINPROGRESS +#define EINPROGRESS WSAEINPROGRESS + +#undef EINTR +#define EINTR WSAEINTR + +#undef ENOTSOCK +#define ENOTSOCK WSAENOTSOCK + +#undef EMSGSIZE +#define EMSGSIZE WSAEMSGSIZE + +#else + +typedef int hsocket_t; + +#ifndef SOCKET +#define SOCKET int +#endif + +#ifndef INVALID_SOCKET +#define INVALID_SOCKET -1 +#endif + +HV_INLINE int blocking(int s) { + return fcntl(s, F_SETFL, fcntl(s, F_GETFL) & ~O_NONBLOCK); +} + +HV_INLINE int nonblocking(int s) { + return fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK); +} + +HV_INLINE int closesocket(int sockfd) { + return close(sockfd); +} + +#endif + +#ifndef SAFE_CLOSESOCKET +#define SAFE_CLOSESOCKET(fd) do {if ((fd) >= 0) {closesocket(fd); (fd) = -1;}} while(0) +#endif + +//-----------------------------sockaddr_u---------------------------------------------- +typedef union { + struct sockaddr sa; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; +#ifdef ENABLE_UDS + struct sockaddr_un sun; +#endif +} sockaddr_u; + +HV_EXPORT bool is_ipv4(const char* host); +HV_EXPORT bool is_ipv6(const char* host); +HV_INLINE bool is_ipaddr(const char* host) { + return is_ipv4(host) || is_ipv6(host); +} + +// @param host: domain or ip +// @retval 0:succeed +HV_EXPORT int ResolveAddr(const char* host, sockaddr_u* addr); + +HV_EXPORT const char* sockaddr_ip(sockaddr_u* addr, char *ip, int len); +HV_EXPORT uint16_t sockaddr_port(sockaddr_u* addr); +HV_EXPORT int sockaddr_set_ip(sockaddr_u* addr, const char* host); +HV_EXPORT void sockaddr_set_port(sockaddr_u* addr, int port); +HV_EXPORT int sockaddr_set_ipport(sockaddr_u* addr, const char* host, int port); +HV_EXPORT socklen_t sockaddr_len(sockaddr_u* addr); +HV_EXPORT const char* sockaddr_str(sockaddr_u* addr, char* buf, int len); + +//#define INET_ADDRSTRLEN 16 +//#define INET6_ADDRSTRLEN 46 +#ifdef ENABLE_UDS +#define SOCKADDR_STRLEN sizeof(((struct sockaddr_un*)(NULL))->sun_path) +HV_INLINE void sockaddr_set_path(sockaddr_u* addr, const char* path) { + addr->sa.sa_family = AF_UNIX; + strncpy(addr->sun.sun_path, path, sizeof(addr->sun.sun_path)); +} +#else +#define SOCKADDR_STRLEN 64 // ipv4:port | [ipv6]:port +#endif + +HV_INLINE void sockaddr_print(sockaddr_u* addr) { + char buf[SOCKADDR_STRLEN] = {0}; + sockaddr_str(addr, buf, sizeof(buf)); + puts(buf); +} + +#define SOCKADDR_LEN(addr) sockaddr_len((sockaddr_u*)addr) +#define SOCKADDR_STR(addr, buf) sockaddr_str((sockaddr_u*)addr, buf, sizeof(buf)) +#define SOCKADDR_PRINT(addr) sockaddr_print((sockaddr_u*)addr) +//===================================================================================== + +// socket -> setsockopt -> bind +// @param type: SOCK_STREAM(tcp) SOCK_DGRAM(udp) +// @return sockfd +HV_EXPORT int Bind(int port, const char* host DEFAULT(ANYADDR), int type DEFAULT(SOCK_STREAM)); + +// Bind -> listen +// @return listenfd +HV_EXPORT int Listen(int port, const char* host DEFAULT(ANYADDR)); + +// @return connfd +// ResolveAddr -> socket -> nonblocking -> connect +HV_EXPORT int Connect(const char* host, int port, int nonblock DEFAULT(0)); +// Connect(host, port, 1) +HV_EXPORT int ConnectNonblock(const char* host, int port); +// Connect(host, port, 1) -> select -> blocking +#define DEFAULT_CONNECT_TIMEOUT 10000 // ms +HV_EXPORT int ConnectTimeout(const char* host, int port, int ms DEFAULT(DEFAULT_CONNECT_TIMEOUT)); + +#ifdef ENABLE_UDS +HV_EXPORT int BindUnix(const char* path, int type DEFAULT(SOCK_STREAM)); +HV_EXPORT int ListenUnix(const char* path); +HV_EXPORT int ConnectUnix(const char* path, int nonblock DEFAULT(0)); +HV_EXPORT int ConnectUnixNonblock(const char* path); +HV_EXPORT int ConnectUnixTimeout(const char* path, int ms DEFAULT(DEFAULT_CONNECT_TIMEOUT)); +#endif + +// Just implement Socketpair(AF_INET, SOCK_STREAM, 0, sv); +HV_EXPORT int Socketpair(int family, int type, int protocol, int sv[2]); + +HV_INLINE int tcp_nodelay(int sockfd, int on DEFAULT(1)) { + return setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (const char*)&on, sizeof(int)); +} + +HV_INLINE int tcp_nopush(int sockfd, int on DEFAULT(1)) { +#ifdef TCP_NOPUSH + return setsockopt(sockfd, IPPROTO_TCP, TCP_NOPUSH, (const char*)&on, sizeof(int)); +#elif defined(TCP_CORK) + return setsockopt(sockfd, IPPROTO_TCP, TCP_CORK, (const char*)&on, sizeof(int)); +#else + return 0; +#endif +} + +HV_INLINE int tcp_keepalive(int sockfd, int on DEFAULT(1), int delay DEFAULT(60)) { + if (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, (const char*)&on, sizeof(int)) != 0) { + return socket_errno(); + } + +#ifdef TCP_KEEPALIVE + return setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE, (const char*)&delay, sizeof(int)); +#elif defined(TCP_KEEPIDLE) + // TCP_KEEPIDLE => tcp_keepalive_time + // TCP_KEEPCNT => tcp_keepalive_probes + // TCP_KEEPINTVL => tcp_keepalive_intvl + return setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, (const char*)&delay, sizeof(int)); +#else + return 0; +#endif +} + +HV_INLINE int udp_broadcast(int sockfd, int on DEFAULT(1)) { + return setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, (const char*)&on, sizeof(int)); +} + +HV_INLINE int ip_v6only(int sockfd, int on DEFAULT(1)) { +#ifdef IPV6_V6ONLY + return setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&on, sizeof(int)); +#else + return 0; +#endif +} + +// send timeout +HV_INLINE int so_sndtimeo(int sockfd, int timeout) { +#ifdef OS_WIN + return setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&timeout, sizeof(int)); +#else + struct timeval tv = {timeout/1000, (timeout%1000)*1000}; + return setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); +#endif +} + +// recv timeout +HV_INLINE int so_rcvtimeo(int sockfd, int timeout) { +#ifdef OS_WIN + return setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(int)); +#else + struct timeval tv = {timeout/1000, (timeout%1000)*1000}; + return setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); +#endif +} + +// send buffer size +HV_INLINE int so_sndbuf(int sockfd, int len) { + return setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (const char*)&len, sizeof(int)); +} + +// recv buffer size +HV_INLINE int so_rcvbuf(int sockfd, int len) { + return setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, (const char*)&len, sizeof(int)); +} + +HV_INLINE int so_reuseaddr(int sockfd, int on DEFAULT(1)) { +#ifdef SO_REUSEADDR + // NOTE: SO_REUSEADDR allow to reuse sockaddr of TIME_WAIT status + return setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(int)); +#else + return 0; +#endif +} + +HV_INLINE int so_reuseport(int sockfd, int on DEFAULT(1)) { +#ifdef SO_REUSEPORT + // NOTE: SO_REUSEPORT allow multiple sockets to bind same port + return setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, (const char*)&on, sizeof(int)); +#else + return 0; +#endif +} + +HV_INLINE int so_linger(int sockfd, int timeout DEFAULT(1)) { +#ifdef SO_LINGER + struct linger linger; + if (timeout >= 0) { + linger.l_onoff = 1; + linger.l_linger = timeout; + } else { + linger.l_onoff = 0; + linger.l_linger = 0; + } + // NOTE: SO_LINGER change the default behavior of close, send RST, avoid TIME_WAIT + return setsockopt(sockfd, SOL_SOCKET, SO_LINGER, (const char*)&linger, sizeof(linger)); +#else + return 0; +#endif +} + +END_EXTERN_C + +#endif // HV_SOCKET_H_ diff --git a/ww/libhv/base/hsysinfo.h b/ww/libhv/base/hsysinfo.h new file mode 100644 index 00000000..5366aa5a --- /dev/null +++ b/ww/libhv/base/hsysinfo.h @@ -0,0 +1,68 @@ +#ifndef HV_SYS_INFO_H_ +#define HV_SYS_INFO_H_ + +#include "hplatform.h" + +#ifdef OS_LINUX +#include +#endif + +#ifdef OS_DARWIN +#include +#include +#endif + +static inline int get_ncpu() { +#ifdef OS_WIN + SYSTEM_INFO si; + GetSystemInfo(&si); + return si.dwNumberOfProcessors; +#else + //return get_nprocs(); + //return get_nprocs_conf(); + //return sysconf(_SC_NPROCESSORS_ONLN); // processors available + return sysconf(_SC_NPROCESSORS_CONF); // processors configured +#endif +} + +typedef struct meminfo_s { + unsigned long total; // KB + unsigned long free; // KB +} meminfo_t; + +static inline int get_meminfo(meminfo_t* mem) { +#ifdef OS_WIN + MEMORYSTATUSEX memstat; + memset(&memstat, 0, sizeof(memstat)); + memstat.dwLength = sizeof(memstat); + GlobalMemoryStatusEx(&memstat); + mem->total = (unsigned long)(memstat.ullTotalPhys >> 10); + mem->free = (unsigned long)(memstat.ullAvailPhys >> 10); + return 0; +#elif defined(OS_LINUX) + struct sysinfo info; + if (sysinfo(&info) < 0) { + return errno; + } + mem->total = info.totalram * info.mem_unit >> 10; + mem->free = info.freeram * info.mem_unit >> 10; + return 0; +#elif defined(OS_DARWIN) + uint64_t memsize = 0; + size_t size = sizeof(memsize); + int which[2] = {CTL_HW, HW_MEMSIZE}; + sysctl(which, 2, &memsize, &size, NULL, 0); + mem->total = memsize >> 10; + + vm_statistics_data_t info; + mach_msg_type_number_t count = sizeof(info) / sizeof(integer_t); + host_statistics(mach_host_self(), HOST_VM_INFO, (host_info_t)&info, &count); + mem->free = ((uint64_t)info.free_count * sysconf(_SC_PAGESIZE)) >> 10; + return 0; +#else + (void)(mem); + return -10; +#endif +} + +#endif // HV_SYS_INFO_H_ diff --git a/ww/libhv/base/hthread.h b/ww/libhv/base/hthread.h new file mode 100644 index 00000000..b43e0c64 --- /dev/null +++ b/ww/libhv/base/hthread.h @@ -0,0 +1,217 @@ +#ifndef HV_THREAD_H_ +#define HV_THREAD_H_ + +#include "hplatform.h" + +#ifdef OS_WIN +#define hv_getpid (long)GetCurrentProcessId +#else +#define hv_getpid (long)getpid +#endif + +#ifdef OS_WIN +#define hv_gettid (long)GetCurrentThreadId +#elif HAVE_GETTID || defined(OS_ANDROID) +#define hv_gettid (long)gettid +#elif defined(OS_LINUX) +#include +#define hv_gettid() (long)syscall(SYS_gettid) +#elif defined(OS_DARWIN) +static inline long hv_gettid() { + uint64_t tid = 0; + pthread_threadid_np(NULL, &tid); + return tid; +} +#elif HAVE_PTHREAD_H +#define hv_gettid (long)pthread_self +#else +#define hv_gettid hv_getpid +#endif + +/* +#include "hthread.h" + +HTHREAD_ROUTINE(thread_demo) { + printf("thread[%ld] start\n", hv_gettid()); + hv_delay(3000); + printf("thread[%ld] end\n", hv_gettid()); + return 0; +} + +int main() { + hthread_t th = hthread_create(thread_demo, NULL); + hthread_join(th); + return 0; +} + */ + +#ifdef OS_WIN +typedef HANDLE hthread_t; +typedef DWORD (WINAPI *hthread_routine)(void*); +#define HTHREAD_RETTYPE DWORD +#define HTHREAD_ROUTINE(fname) DWORD WINAPI fname(void* userdata) +static inline hthread_t hthread_create(hthread_routine fn, void* userdata) { + return CreateThread(NULL, 0, fn, userdata, 0, NULL); +} + +static inline int hthread_join(hthread_t th) { + WaitForSingleObject(th, INFINITE); + CloseHandle(th); + return 0; +} + +#else + +typedef pthread_t hthread_t; +typedef void* (*hthread_routine)(void*); +#define HTHREAD_RETTYPE void* +#define HTHREAD_ROUTINE(fname) void* fname(void* userdata) +static inline hthread_t hthread_create(hthread_routine fn, void* userdata) { + pthread_t th; + pthread_create(&th, NULL, fn, userdata); + return th; +} + +static inline int hthread_join(hthread_t th) { + return pthread_join(th, NULL); +} + +#endif + +#ifdef __cplusplus +/************************************************ + * HThread + * Status: STOP,RUNNING,PAUSE + * Control: start,stop,pause,resume + * first-level virtual: doTask + * second-level virtual: run +************************************************/ +#include +#include +#include + +class HThread { +public: + enum Status { + STOP, + RUNNING, + PAUSE, + }; + + enum SleepPolicy { + YIELD, + SLEEP_FOR, + SLEEP_UNTIL, + NO_SLEEP, + }; + + HThread() { + status = STOP; + status_changed = false; + dotask_cnt = 0; + sleep_policy = YIELD; + sleep_ms = 0; + } + + virtual ~HThread() {} + + void setStatus(Status stat) { + status_changed = true; + status = stat; + } + + void setSleepPolicy(SleepPolicy policy, uint32_t ms = 0) { + sleep_policy = policy; + sleep_ms = ms; + setStatus(status); + } + + virtual int start() { + if (status == STOP) { + thread = std::thread([this] { + if (!doPrepare()) return; + setStatus(RUNNING); + run(); + setStatus(STOP); + if (!doFinish()) return; + }); + } + return 0; + } + + virtual int stop() { + if (status != STOP) { + setStatus(STOP); + } + if (thread.joinable()) { + thread.join(); // wait thread exit + } + return 0; + } + + virtual int pause() { + if (status == RUNNING) { + setStatus(PAUSE); + } + return 0; + } + + virtual int resume() { + if (status == PAUSE) { + setStatus(RUNNING); + } + return 0; + } + + virtual void run() { + while (status != STOP) { + while (status == PAUSE) { + std::this_thread::yield(); + } + + doTask(); + ++dotask_cnt; + + HThread::sleep(); + } + } + + virtual bool doPrepare() {return true;} + virtual void doTask() {} + virtual bool doFinish() {return true;} + + std::thread thread; + std::atomic status; + uint32_t dotask_cnt; +protected: + void sleep() { + switch (sleep_policy) { + case YIELD: + std::this_thread::yield(); + break; + case SLEEP_FOR: + std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms)); + break; + case SLEEP_UNTIL: { + if (status_changed) { + status_changed = false; + base_tp = std::chrono::steady_clock::now(); + } + base_tp += std::chrono::milliseconds(sleep_ms); + std::this_thread::sleep_until(base_tp); + } + break; + default: // donothing, go all out. + break; + } + } + + SleepPolicy sleep_policy; + uint32_t sleep_ms; + // for SLEEP_UNTIL + std::atomic status_changed; + std::chrono::steady_clock::time_point base_tp; +}; +#endif + +#endif // HV_THREAD_H_ diff --git a/ww/libhv/base/htime.c b/ww/libhv/base/htime.c new file mode 100644 index 00000000..6f1542ea --- /dev/null +++ b/ww/libhv/base/htime.c @@ -0,0 +1,290 @@ +#include "htime.h" + +static const char* s_weekdays[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; + +static const char* s_months[] = {"January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December"}; + +static const uint8_t s_days[] = \ +// 1 3 5 7 8 10 12 + {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + +unsigned int gettick_ms() { +#ifdef OS_WIN + return GetTickCount(); +#elif HAVE_CLOCK_GETTIME + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec * 1000 + ts.tv_nsec / 1000000; +#else + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec * 1000 + tv.tv_usec / 1000; +#endif +} + +unsigned long long gethrtime_us() { +#ifdef OS_WIN + static LONGLONG s_freq = 0; + if (s_freq == 0) { + LARGE_INTEGER freq; + QueryPerformanceFrequency(&freq); + s_freq = freq.QuadPart; + } + if (s_freq != 0) { + LARGE_INTEGER count; + QueryPerformanceCounter(&count); + return (unsigned long long)(count.QuadPart / (double)s_freq * 1000000); + } + return 0; +#elif defined(OS_SOLARIS) + return gethrtime() / 1000; +#elif HAVE_CLOCK_GETTIME + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec*(unsigned long long)1000000 + ts.tv_nsec / 1000; +#else + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec*(unsigned long long)1000000 + tv.tv_usec; +#endif +} + +datetime_t datetime_now() { +#ifdef OS_WIN + SYSTEMTIME tm; + GetLocalTime(&tm); + datetime_t dt; + dt.year = tm.wYear; + dt.month = tm.wMonth; + dt.day = tm.wDay; + dt.hour = tm.wHour; + dt.min = tm.wMinute; + dt.sec = tm.wSecond; + dt.ms = tm.wMilliseconds; + return dt; +#else + struct timeval tv; + gettimeofday(&tv, NULL); + datetime_t dt = datetime_localtime(tv.tv_sec); + dt.ms = tv.tv_usec / 1000; + return dt; +#endif +} + +datetime_t datetime_localtime(time_t seconds) { + struct tm* tm = localtime(&seconds); + datetime_t dt; + dt.year = tm->tm_year + 1900; + dt.month = tm->tm_mon + 1; + dt.day = tm->tm_mday; + dt.hour = tm->tm_hour; + dt.min = tm->tm_min; + dt.sec = tm->tm_sec; + return dt; +} + +time_t datetime_mktime(datetime_t* dt) { + struct tm tm; + time_t ts; + time(&ts); + struct tm* ptm = localtime(&ts); + memcpy(&tm, ptm, sizeof(struct tm)); + tm.tm_year = dt->year - 1900; + tm.tm_mon = dt->month - 1; + tm.tm_mday = dt->day; + tm.tm_hour = dt->hour; + tm.tm_min = dt->min; + tm.tm_sec = dt->sec; + return mktime(&tm); +} + +int days_of_month(int month, int year) { + if (month < 1 || month > 12) { + return 0; + } + int days = s_days[month-1]; + return (month == 2 && IS_LEAP_YEAR(year)) ? ++days : days; +} + +datetime_t* datetime_past(datetime_t* dt, int days) { + assert(days >= 0); + int sub = days; + while (sub) { + if (dt->day > sub) { + dt->day -= sub; + break; + } + sub -= dt->day; + if (--dt->month == 0) { + dt->month = 12; + --dt->year; + } + dt->day = days_of_month(dt->month, dt->year); + } + return dt; +} + +datetime_t* datetime_future(datetime_t* dt, int days) { + assert(days >= 0); + int sub = days; + int mdays; + while (sub) { + mdays = days_of_month(dt->month, dt->year); + if (dt->day + sub <= mdays) { + dt->day += sub; + break; + } + sub -= (mdays - dt->day + 1); + if (++dt->month > 12) { + dt->month = 1; + ++dt->year; + } + dt->day = 1; + } + return dt; +} + +char* duration_fmt(int sec, char* buf) { + int h, m, s; + m = sec / 60; + s = sec % 60; + h = m / 60; + m = m % 60; + sprintf(buf, TIME_FMT, h, m, s); + return buf; +} + +char* datetime_fmt(datetime_t* dt, char* buf) { + sprintf(buf, DATETIME_FMT, + dt->year, dt->month, dt->day, + dt->hour, dt->min, dt->sec); + return buf; +} + +char* datetime_fmt_iso(datetime_t* dt, char* buf) { + sprintf(buf, DATETIME_FMT_ISO, + dt->year, dt->month, dt->day, + dt->hour, dt->min, dt->sec, + dt->ms); + return buf; +} + +char* gmtime_fmt(time_t time, char* buf) { + struct tm* tm = gmtime(&time); + //strftime(buf, GMTIME_FMT_BUFLEN, "%a, %d %b %Y %H:%M:%S GMT", tm); + sprintf(buf, GMTIME_FMT, + s_weekdays[tm->tm_wday], + tm->tm_mday, s_months[tm->tm_mon], tm->tm_year + 1900, + tm->tm_hour, tm->tm_min, tm->tm_sec); + return buf; +} + +int month_atoi(const char* month) { + for (size_t i = 0; i < 12; ++i) { + if (strnicmp(month, s_months[i], strlen(month)) == 0) + return i+1; + } + return 0; +} + +const char* month_itoa(int month) { + assert(month >= 1 && month <= 12); + return s_months[month-1]; +} + +int weekday_atoi(const char* weekday) { + for (size_t i = 0; i < 7; ++i) { + if (strnicmp(weekday, s_weekdays[i], strlen(weekday)) == 0) + return i; + } + return 0; +} + +const char* weekday_itoa(int weekday) { + assert(weekday >= 0 && weekday <= 7); + if (weekday == 7) weekday = 0; + return s_weekdays[weekday]; +} + +datetime_t hv_compile_datetime() { + datetime_t dt; + char month[32]; + sscanf(__DATE__, "%s %d %d", month, &dt.day, &dt.year); + sscanf(__TIME__, "%d:%d:%d", &dt.hour, &dt.min, &dt.sec); + dt.month = month_atoi(month); + return dt; +} + +time_t cron_next_timeout(int minute, int hour, int day, int week, int month) { + enum { + MINUTELY, + HOURLY, + DAILY, + WEEKLY, + MONTHLY, + YEARLY, + } period_type = MINUTELY; + struct tm tm; + time_t tt; + time(&tt); + tm = *localtime(&tt); + time_t tt_round = 0; + + tm.tm_sec = 0; + if (minute >= 0) { + period_type = HOURLY; + tm.tm_min = minute; + } + if (hour >= 0) { + period_type = DAILY; + tm.tm_hour = hour; + } + if (week >= 0) { + period_type = WEEKLY; + } + else if (day > 0) { + period_type = MONTHLY; + tm.tm_mday = day; + if (month > 0) { + period_type = YEARLY; + tm.tm_mon = month - 1; + } + } + + tt_round = mktime(&tm); + if (week >= 0) { + tt_round += (week-tm.tm_wday)*SECONDS_PER_DAY; + } + if (tt_round > tt) { + return tt_round; + } + + switch(period_type) { + case MINUTELY: + tt_round += SECONDS_PER_MINUTE; + return tt_round; + case HOURLY: + tt_round += SECONDS_PER_HOUR; + return tt_round; + case DAILY: + tt_round += SECONDS_PER_DAY; + return tt_round; + case WEEKLY: + tt_round += SECONDS_PER_WEEK; + return tt_round; + case MONTHLY: + if (++tm.tm_mon == 12) { + tm.tm_mon = 0; + ++tm.tm_year; + } + break; + case YEARLY: + ++tm.tm_year; + break; + default: + return -1; + } + + return mktime(&tm); +} diff --git a/ww/libhv/base/htime.h b/ww/libhv/base/htime.h new file mode 100644 index 00000000..4d6dd016 --- /dev/null +++ b/ww/libhv/base/htime.h @@ -0,0 +1,114 @@ +#ifndef HV_TIME_H_ +#define HV_TIME_H_ + +#include "hexport.h" +#include "hplatform.h" + +BEGIN_EXTERN_C + +#define SECONDS_PER_MINUTE 60 +#define SECONDS_PER_HOUR 3600 +#define SECONDS_PER_DAY 86400 // 24*3600 +#define SECONDS_PER_WEEK 604800 // 7*24*3600 + +#define IS_LEAP_YEAR(year) (((year)%4 == 0 && (year)%100 != 0) || (year)%400 == 0) + +typedef struct datetime_s { + int year; + int month; + int day; + int hour; + int min; + int sec; + int ms; +} datetime_t; + +#ifdef _MSC_VER +/* @see winsock2.h +// Structure used in select() call, taken from the BSD file sys/time.h +struct timeval { + long tv_sec; + long tv_usec; +}; +*/ + +struct timezone { + int tz_minuteswest; /* of Greenwich */ + int tz_dsttime; /* type of dst correction to apply */ +}; + +#include +HV_INLINE int gettimeofday(struct timeval *tv, struct timezone *tz) { + struct _timeb tb; + _ftime(&tb); + if (tv) { + tv->tv_sec = (long)tb.time; + tv->tv_usec = tb.millitm * 1000; + } + if (tz) { + tz->tz_minuteswest = tb.timezone; + tz->tz_dsttime = tb.dstflag; + } + return 0; +} +#endif + +HV_EXPORT unsigned int gettick_ms(); +HV_INLINE unsigned long long gettimeofday_ms() { + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec * (unsigned long long)1000 + tv.tv_usec/1000; +} +HV_INLINE unsigned long long gettimeofday_us() { + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec * (unsigned long long)1000000 + tv.tv_usec; +} +HV_EXPORT unsigned long long gethrtime_us(); + +HV_EXPORT datetime_t datetime_now(); +HV_EXPORT datetime_t datetime_localtime(time_t seconds); +HV_EXPORT time_t datetime_mktime(datetime_t* dt); + +HV_EXPORT datetime_t* datetime_past(datetime_t* dt, int days DEFAULT(1)); +HV_EXPORT datetime_t* datetime_future(datetime_t* dt, int days DEFAULT(1)); + +#define TIME_FMT "%02d:%02d:%02d" +#define TIME_FMT_BUFLEN 12 +HV_EXPORT char* duration_fmt(int sec, char* buf); + +#define DATETIME_FMT "%04d-%02d-%02d %02d:%02d:%02d" +#define DATETIME_FMT_ISO "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ" +#define DATETIME_FMT_BUFLEN 30 +HV_EXPORT char* datetime_fmt(datetime_t* dt, char* buf); +HV_EXPORT char* datetime_fmt_iso(datetime_t* dt, char* buf); + +#define GMTIME_FMT "%.3s, %02d %.3s %04d %02d:%02d:%02d GMT" +#define GMTIME_FMT_BUFLEN 30 +HV_EXPORT char* gmtime_fmt(time_t time, char* buf); + +HV_EXPORT int days_of_month(int month, int year); + +HV_EXPORT int month_atoi(const char* month); +HV_EXPORT const char* month_itoa(int month); + +HV_EXPORT int weekday_atoi(const char* weekday); +HV_EXPORT const char* weekday_itoa(int weekday); + +HV_EXPORT datetime_t hv_compile_datetime(); + +/* + * minute hour day week month action + * 0~59 0~23 1~31 0~6 1~12 + * -1 -1 -1 -1 -1 cron.minutely + * 30 -1 -1 -1 -1 cron.hourly + * 30 1 -1 -1 -1 cron.daily + * 30 1 15 -1 -1 cron.monthly + * 30 1 -1 0 -1 cron.weekly + * 30 1 1 -1 10 cron.yearly + */ +HV_EXPORT time_t cron_next_timeout(int minute, int hour, int day, int week, int month); + +END_EXTERN_C + +#endif // HV_TIME_H_ diff --git a/ww/libhv/base/hversion.c b/ww/libhv/base/hversion.c new file mode 100644 index 00000000..31a97d97 --- /dev/null +++ b/ww/libhv/base/hversion.c @@ -0,0 +1,48 @@ +#include "hversion.h" + +#include "htime.h" + +const char* hv_compile_version() { + static char s_version[16] = {0}; + datetime_t dt = hv_compile_datetime(); + snprintf(s_version, sizeof(s_version), "%d.%d.%d.%d", + HV_VERSION_MAJOR, dt.year%100, dt.month, dt.day); + return s_version; +} + +int version_atoi(const char* str) { + int hex = 0; + + // trim v1.2.3.4 + const char* pv = strchr(str, 'v'); + const char* pdot = pv ? pv+1 : str; + + while (1) { + hex = (hex << 8) | atoi(pdot); + pdot = strchr(pdot, '.'); + if (pdot == NULL) break; + ++pdot; + } + + return hex; +} + +void version_itoa(int num, char* str) { + char* ch = (char*)# + sprintf(str, "%d.%d.%d.%d", ch[3], ch[2], ch[1], ch[0]); + + // trim 0.1.2.3 + const char* p = str; + while (1) { + if (p[0] == '0' && p[1] == '.') { + p += 2; + } + else { + break; + } + } + + if (p != str) { + strcpy(str, p); + } +} diff --git a/ww/libhv/base/hversion.h b/ww/libhv/base/hversion.h new file mode 100644 index 00000000..d88dd42b --- /dev/null +++ b/ww/libhv/base/hversion.h @@ -0,0 +1,34 @@ +#ifndef HV_VERSION_H_ +#define HV_VERSION_H_ + +#include "hexport.h" +#include "hdef.h" + +BEGIN_EXTERN_C + +#define HV_VERSION_MAJOR 1 +#define HV_VERSION_MINOR 3 +#define HV_VERSION_PATCH 2 + +#define HV_VERSION_STRING STRINGIFY(HV_VERSION_MAJOR) "." \ + STRINGIFY(HV_VERSION_MINOR) "." \ + STRINGIFY(HV_VERSION_PATCH) + +#define HV_VERSION_NUMBER ((HV_VERSION_MAJOR << 16) | (HV_VERSION_MINOR << 8) | HV_VERSION_PATCH) + + +HV_INLINE const char* hv_version() { + return HV_VERSION_STRING; +} + +HV_EXPORT const char* hv_compile_version(); + +// 1.2.3.4 => 0x01020304 +HV_EXPORT int version_atoi(const char* str); + +// 0x01020304 => 1.2.3.4 +HV_EXPORT void version_itoa(int hex, char* str); + +END_EXTERN_C + +#endif // HV_VERSION_H_ diff --git a/ww/libhv/base/list.h b/ww/libhv/base/list.h new file mode 100644 index 00000000..97a7d7af --- /dev/null +++ b/ww/libhv/base/list.h @@ -0,0 +1,733 @@ +#ifndef _LINUX_LIST_H +#define _LINUX_LIST_H + +/* + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when + * manipulating whole lists rather than single entries, as + * sometimes we already know the next/prev entries and we can + * generate better code by using them directly rather than + * using the generic single-entry routines. + */ + +#include + +#ifndef prefetch +#ifdef __GNUC__ + #define prefetch(x) __builtin_prefetch(x) +#else + #define prefetch(x) (void)0 +#endif +#endif + +struct list_head { + struct list_head *next, *prev; +}; +#define list_node list_head + +struct hlist_head { + struct hlist_node *first; +}; + +struct hlist_node { + struct hlist_node *next, **pprev; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } +// TODO: defined LIST_HEAD +#ifndef LIST_HEAD +#define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name) +#endif +#define INIT_LIST_HEAD list_init +static inline void list_init(struct list_head *list) +{ + list->next = list; + list->prev = list; +} + +/* + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_add(struct list_head *n, + struct list_head *prev, + struct list_head *next) +{ + next->prev = n; + n->next = next; + n->prev = prev; + prev->next = n; +} + +/** + * list_add - add a new entry + * @new: new entry to be added + * @head: list head to add it after + * + * Insert a new entry after the specified head. + * This is good for implementing stacks. + */ +static inline void list_add(struct list_head *n, struct list_head *head) +{ + __list_add(n, head, head->next); +} + + +/** + * list_add_tail - add a new entry + * @new: new entry to be added + * @head: list head to add it before + * + * Insert a new entry before the specified head. + * This is useful for implementing queues. + */ +static inline void list_add_tail(struct list_head *n, struct list_head *head) +{ + __list_add(n, head->prev, head); +} + +/* + * Delete a list entry by making the prev/next entries + * point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_del(struct list_head * prev, struct list_head * next) +{ + next->prev = prev; + prev->next = next; +} + +/** + * list_del - deletes entry from list. + * @entry: the element to delete from the list. + * Note: list_empty() on entry does not return true after this, the entry is + * in an undefined state. + */ +static inline void __list_del_entry(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); +} + +static inline void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + //entry->next = NULL; + //entry->prev = NULL; +} + +/** + * list_replace - replace old entry by new one + * @old : the element to be replaced + * @new : the new element to insert + * + * If @old was empty, it will be overwritten. + */ +static inline void list_replace(struct list_head *old, + struct list_head *n) +{ + n->next = old->next; + n->next->prev = n; + n->prev = old->prev; + n->prev->next = n; +} + +static inline void list_replace_init(struct list_head *old, + struct list_head *n) +{ + list_replace(old, n); + INIT_LIST_HEAD(old); +} + +/** + * list_del_init - deletes entry from list and reinitialize it. + * @entry: the element to delete from the list. + */ +static inline void list_del_init(struct list_head *entry) +{ + __list_del_entry(entry); + INIT_LIST_HEAD(entry); +} + +/** + * list_move - delete from one list and add as another's head + * @list: the entry to move + * @head: the head that will precede our entry + */ +static inline void list_move(struct list_head *list, struct list_head *head) +{ + __list_del_entry(list); + list_add(list, head); +} + +/** + * list_move_tail - delete from one list and add as another's tail + * @list: the entry to move + * @head: the head that will follow our entry + */ +static inline void list_move_tail(struct list_head *list, + struct list_head *head) +{ + __list_del_entry(list); + list_add_tail(list, head); +} + +/** + * list_is_last - tests whether @list is the last entry in list @head + * @list: the entry to test + * @head: the head of the list + */ +static inline int list_is_last(const struct list_head *list, + const struct list_head *head) +{ + return list->next == head; +} + +/** + * list_empty - tests whether a list is empty + * @head: the list to test. + */ +static inline int list_empty(const struct list_head *head) +{ + return head->next == head; +} + +/** + * list_empty_careful - tests whether a list is empty and not being modified + * @head: the list to test + * + * Description: + * tests whether a list is empty _and_ checks that no other CPU might be + * in the process of modifying either member (next or prev) + * + * NOTE: using list_empty_careful() without synchronization + * can only be safe if the only activity that can happen + * to the list entry is list_del_init(). Eg. it cannot be used + * if another CPU could re-list_add() it. + */ +static inline int list_empty_careful(const struct list_head *head) +{ + struct list_head *next = head->next; + return (next == head) && (next == head->prev); +} + +/** + * list_rotate_left - rotate the list to the left + * @head: the head of the list + */ +static inline void list_rotate_left(struct list_head *head) +{ + struct list_head *first; + + if (!list_empty(head)) { + first = head->next; + list_move_tail(first, head); + } +} + +/** + * list_is_singular - tests whether a list has just one entry. + * @head: the list to test. + */ +static inline int list_is_singular(const struct list_head *head) +{ + return !list_empty(head) && (head->next == head->prev); +} + +static inline void __list_cut_position(struct list_head *list, + struct list_head *head, struct list_head *entry) +{ + struct list_head *new_first = entry->next; + list->next = head->next; + list->next->prev = list; + list->prev = entry; + entry->next = list; + head->next = new_first; + new_first->prev = head; +} + +/** + * list_cut_position - cut a list into two + * @list: a new list to add all removed entries + * @head: a list with entries + * @entry: an entry within head, could be the head itself + * and if so we won't cut the list + * + * This helper moves the initial part of @head, up to and + * including @entry, from @head to @list. You should + * pass on @entry an element you know is on @head. @list + * should be an empty list or a list you do not care about + * losing its data. + * + */ +static inline void list_cut_position(struct list_head *list, + struct list_head *head, struct list_head *entry) +{ + if (list_empty(head)) + return; + if (list_is_singular(head) && + (head->next != entry && head != entry)) + return; + if (entry == head) + INIT_LIST_HEAD(list); + else + __list_cut_position(list, head, entry); +} + +static inline void __list_splice(const struct list_head *list, + struct list_head *prev, + struct list_head *next) +{ + struct list_head *first = list->next; + struct list_head *last = list->prev; + + first->prev = prev; + prev->next = first; + + last->next = next; + next->prev = last; +} + +/** + * list_splice - join two lists, this is designed for stacks + * @list: the new list to add. + * @head: the place to add it in the first list. + */ +static inline void list_splice(const struct list_head *list, + struct list_head *head) +{ + if (!list_empty(list)) + __list_splice(list, head, head->next); +} + +/** + * list_splice_tail - join two lists, each list being a queue + * @list: the new list to add. + * @head: the place to add it in the first list. + */ +static inline void list_splice_tail(struct list_head *list, + struct list_head *head) +{ + if (!list_empty(list)) + __list_splice(list, head->prev, head); +} + +/** + * list_splice_init - join two lists and reinitialise the emptied list. + * @list: the new list to add. + * @head: the place to add it in the first list. + * + * The list at @list is reinitialised + */ +static inline void list_splice_init(struct list_head *list, + struct list_head *head) +{ + if (!list_empty(list)) { + __list_splice(list, head, head->next); + INIT_LIST_HEAD(list); + } +} + +/** + * list_splice_tail_init - join two lists and reinitialise the emptied list + * @list: the new list to add. + * @head: the place to add it in the first list. + * + * Each of the lists is a queue. + * The list at @list is reinitialised + */ +static inline void list_splice_tail_init(struct list_head *list, + struct list_head *head) +{ + if (!list_empty(list)) { + __list_splice(list, head->prev, head); + INIT_LIST_HEAD(list); + } +} + +/** + * list_entry - get the struct for this entry + * @ptr: the &struct list_head pointer. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + */ +#define list_entry(ptr, type, member) \ + container_of(ptr, type, member) + +/** + * list_first_entry - get the first element from a list + * @ptr: the list head to take the element from. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + * + * Note, that list is expected to be not empty. + */ +#define list_first_entry(ptr, type, member) \ + list_entry((ptr)->next, type, member) + +/** + * list_for_each - iterate over a list + * @pos: the &struct list_head to use as a loop cursor. + * @head: the head for your list. + */ +#define list_for_each(pos, head) \ + for (pos = (head)->next; prefetch(pos->next), pos != (head); \ + pos = pos->next) + +/** + * __list_for_each - iterate over a list + * @pos: the &struct list_head to use as a loop cursor. + * @head: the head for your list. + * + * This variant differs from list_for_each() in that it's the + * simplest possible list iteration code, no prefetching is done. + * Use this for code that knows the list to be very short (empty + * or 1 entry) most of the time. + */ +#define __list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); pos = pos->next) + +/** + * list_for_each_prev - iterate over a list backwards + * @pos: the &struct list_head to use as a loop cursor. + * @head: the head for your list. + */ +#define list_for_each_prev(pos, head) \ + for (pos = (head)->prev; prefetch(pos->prev), pos != (head); \ + pos = pos->prev) + +/** + * list_for_each_safe - iterate over a list safe against removal of list entry + * @pos: the &struct list_head to use as a loop cursor. + * @n: another &struct list_head to use as temporary storage + * @head: the head for your list. + */ +#define list_for_each_safe(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, n = pos->next) + +/** + * list_for_each_prev_safe - iterate over a list backwards safe against removal of list entry + * @pos: the &struct list_head to use as a loop cursor. + * @n: another &struct list_head to use as temporary storage + * @head: the head for your list. + */ +#define list_for_each_prev_safe(pos, n, head) \ + for (pos = (head)->prev, n = pos->prev; \ + prefetch(pos->prev), pos != (head); \ + pos = n, n = pos->prev) + +/** + * list_for_each_entry - iterate over list of given type + * @pos: the type * to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry(pos, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member); \ + prefetch(pos->member.next), &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + +/** + * list_for_each_entry_reverse - iterate backwards over list of given type. + * @pos: the type * to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_reverse(pos, head, member) \ + for (pos = list_entry((head)->prev, typeof(*pos), member); \ + prefetch(pos->member.prev), &pos->member != (head); \ + pos = list_entry(pos->member.prev, typeof(*pos), member)) + +/** + * list_prepare_entry - prepare a pos entry for use in list_for_each_entry_continue() + * @pos: the type * to use as a start point + * @head: the head of the list + * @member: the name of the list_struct within the struct. + * + * Prepares a pos entry for use as a start point in list_for_each_entry_continue(). + */ +#define list_prepare_entry(pos, head, member) \ + ((pos) ? : list_entry(head, typeof(*pos), member)) + +/** + * list_for_each_entry_continue - continue iteration over list of given type + * @pos: the type * to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + * + * Continue to iterate over list of given type, continuing after + * the current position. + */ +#define list_for_each_entry_continue(pos, head, member) \ + for (pos = list_entry(pos->member.next, typeof(*pos), member); \ + prefetch(pos->member.next), &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + +/** + * list_for_each_entry_continue_reverse - iterate backwards from the given point + * @pos: the type * to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + * + * Start to iterate over list of given type backwards, continuing after + * the current position. + */ +#define list_for_each_entry_continue_reverse(pos, head, member) \ + for (pos = list_entry(pos->member.prev, typeof(*pos), member); \ + prefetch(pos->member.prev), &pos->member != (head); \ + pos = list_entry(pos->member.prev, typeof(*pos), member)) + +/** + * list_for_each_entry_from - iterate over list of given type from the current point + * @pos: the type * to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + * + * Iterate over list of given type, continuing from current position. + */ +#define list_for_each_entry_from(pos, head, member) \ + for (; prefetch(pos->member.next), &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + +/** + * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry + * @pos: the type * to use as a loop cursor. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_safe(pos, n, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member), \ + n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) + +/** + * list_for_each_entry_safe_continue - continue list iteration safe against removal + * @pos: the type * to use as a loop cursor. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + * + * Iterate over list of given type, continuing after current point, + * safe against removal of list entry. + */ +#define list_for_each_entry_safe_continue(pos, n, head, member) \ + for (pos = list_entry(pos->member.next, typeof(*pos), member), \ + n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) + +/** + * list_for_each_entry_safe_from - iterate over list from current point safe against removal + * @pos: the type * to use as a loop cursor. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + * + * Iterate over list of given type from current point, safe against + * removal of list entry. + */ +#define list_for_each_entry_safe_from(pos, n, head, member) \ + for (n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) + +/** + * list_for_each_entry_safe_reverse - iterate backwards over list safe against removal + * @pos: the type * to use as a loop cursor. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + * + * Iterate backwards over list of given type, safe against removal + * of list entry. + */ +#define list_for_each_entry_safe_reverse(pos, n, head, member) \ + for (pos = list_entry((head)->prev, typeof(*pos), member), \ + n = list_entry(pos->member.prev, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.prev, typeof(*n), member)) + +/** + * list_safe_reset_next - reset a stale list_for_each_entry_safe loop + * @pos: the loop cursor used in the list_for_each_entry_safe loop + * @n: temporary storage used in list_for_each_entry_safe + * @member: the name of the list_struct within the struct. + * + * list_safe_reset_next is not safe to use in general if the list may be + * modified concurrently (eg. the lock is dropped in the loop body). An + * exception to this is if the cursor element (pos) is pinned in the list, + * and list_safe_reset_next is called after re-taking the lock and before + * completing the current iteration of the loop body. + */ +#define list_safe_reset_next(pos, n, member) \ + n = list_entry(pos->member.next, typeof(*pos), member) + +/* + * Double linked lists with a single pointer list head. + * Mostly useful for hash tables where the two pointer list head is + * too wasteful. + * You lose the ability to access the tail in O(1). + */ + +#define HLIST_HEAD_INIT { .first = NULL } +#define HLIST_HEAD(name) struct hlist_head name = { .first = NULL } +#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL) +#define INIT_HLIST_NODE hlist_init +static inline void hlist_init(struct hlist_node *h) +{ + h->next = NULL; + h->pprev = NULL; +} + +static inline int hlist_unhashed(const struct hlist_node *h) +{ + return !h->pprev; +} + +static inline int hlist_empty(const struct hlist_head *h) +{ + return !h->first; +} + +static inline void __hlist_del(struct hlist_node *n) +{ + struct hlist_node *next = n->next; + struct hlist_node **pprev = n->pprev; + *pprev = next; + if (next) + next->pprev = pprev; +} + +static inline void hlist_del(struct hlist_node *n) +{ + __hlist_del(n); + //n->next = NULL; + //n->pprev = NULL; +} + +static inline void hlist_del_init(struct hlist_node *n) +{ + if (!hlist_unhashed(n)) { + __hlist_del(n); + INIT_HLIST_NODE(n); + } +} + +static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h) +{ + struct hlist_node *first = h->first; + n->next = first; + if (first) + first->pprev = &n->next; + h->first = n; + n->pprev = &h->first; +} + +/* next must be != NULL */ +static inline void hlist_add_before(struct hlist_node *n, + struct hlist_node *next) +{ + n->pprev = next->pprev; + n->next = next; + next->pprev = &n->next; + *(n->pprev) = n; +} + +static inline void hlist_add_after(struct hlist_node *n, + struct hlist_node *next) +{ + next->next = n->next; + n->next = next; + next->pprev = &n->next; + + if(next->next) + next->next->pprev = &next->next; +} + +/* after that we'll appear to be on some hlist and hlist_del will work */ +static inline void hlist_add_fake(struct hlist_node *n) +{ + n->pprev = &n->next; +} + +/* + * Move a list from one list head to another. Fixup the pprev + * reference of the first entry if it exists. + */ +static inline void hlist_move_list(struct hlist_head *old, + struct hlist_head *n) +{ + n->first = old->first; + if (n->first) + n->first->pprev = &n->first; + old->first = NULL; +} + +#define hlist_entry(ptr, type, member) container_of(ptr,type,member) + +#define hlist_for_each(pos, head) \ + for (pos = (head)->first; pos && ({ prefetch(pos->next); 1; }); \ + pos = pos->next) + +#define hlist_for_each_safe(pos, n, head) \ + for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \ + pos = n) + +/** + * hlist_for_each_entry - iterate over list of given type + * @tpos: the type * to use as a loop cursor. + * @pos: the &struct hlist_node to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the hlist_node within the struct. + */ +#define hlist_for_each_entry(tpos, pos, head, member) \ + for (pos = (head)->first; \ + pos && ({ prefetch(pos->next); 1;}) && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +/** + * hlist_for_each_entry_continue - iterate over a hlist continuing after current point + * @tpos: the type * to use as a loop cursor. + * @pos: the &struct hlist_node to use as a loop cursor. + * @member: the name of the hlist_node within the struct. + */ +#define hlist_for_each_entry_continue(tpos, pos, member) \ + for (pos = (pos)->next; \ + pos && ({ prefetch(pos->next); 1;}) && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +/** + * hlist_for_each_entry_from - iterate over a hlist continuing from current point + * @tpos: the type * to use as a loop cursor. + * @pos: the &struct hlist_node to use as a loop cursor. + * @member: the name of the hlist_node within the struct. + */ +#define hlist_for_each_entry_from(tpos, pos, member) \ + for (; pos && ({ prefetch(pos->next); 1;}) && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +/** + * hlist_for_each_entry_safe - iterate over list of given type safe against removal of list entry + * @tpos: the type * to use as a loop cursor. + * @pos: the &struct hlist_node to use as a loop cursor. + * @n: another &struct hlist_node to use as temporary storage + * @head: the head for your list. + * @member: the name of the hlist_node within the struct. + */ +#define hlist_for_each_entry_safe(tpos, pos, n, head, member) \ + for (pos = (head)->first; \ + pos && ({ n = pos->next; 1; }) && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = n) + +#endif diff --git a/ww/libhv/base/netinet.h b/ww/libhv/base/netinet.h new file mode 100644 index 00000000..4969639e --- /dev/null +++ b/ww/libhv/base/netinet.h @@ -0,0 +1,194 @@ +#ifndef HV_NETINET_H_ +#define HV_NETINET_H_ + +#include "hplatform.h" + +/* +#ifdef OS_UNIX +#include +#include +#include +#include +#include +typedef struct iphdr iphdr_t; +typedef struct udphdr udphdr_t; +typedef struct tcphdr tcphdr_t; + +typedef struct icmphdr icmphdr_t; +typedef struct icmp icmp_t; +#else +*/ +// sizeof(iphdr_t) = 20 +typedef struct iphdr_s { +#if BYTE_ORDER == LITTLE_ENDIAN + uint8_t ihl:4; // ip header length + uint8_t version:4; +#elif BYTE_ORDER == BIG_ENDIAN + uint8_t version:4; + uint8_t ihl:4; +#else +#error "BYTE_ORDER undefined!" +#endif + uint8_t tos; // type of service + uint16_t tot_len; // total length + uint16_t id; + uint16_t frag_off; // fragment offset + uint8_t ttl; // Time To Live + uint8_t protocol; + uint16_t check; // checksum + uint32_t saddr; // srcaddr + uint32_t daddr; // dstaddr + /*The options start here.*/ +} iphdr_t; + +// sizeof(udphdr_t) = 8 +typedef struct udphdr_s { + uint16_t source; // source port + uint16_t dest; // dest port + uint16_t len; // udp length + uint16_t check; // checksum +} udphdr_t; + +// sizeof(tcphdr_t) = 20 +typedef struct tcphdr_s { + uint16_t source; // source port + uint16_t dest; // dest port + uint32_t seq; // sequence + uint32_t ack_seq; +#if BYTE_ORDER == LITTLE_ENDIAN + uint16_t res1:4; + uint16_t doff:4; + uint16_t fin:1; + uint16_t syn:1; + uint16_t rst:1; + uint16_t psh:1; + uint16_t ack:1; + uint16_t urg:1; + uint16_t res2:2; +#elif BYTE_ORDER == BIG_ENDIAN + uint16_t doff:4; + uint16_t res1:4; + uint16_t res2:2; + uint16_t urg:1; + uint16_t ack:1; + uint16_t psh:1; + uint16_t rst:1; + uint16_t syn:1; + uint16_t fin:1; +#else +#error "BYTE_ORDER undefined!" +#endif + uint16_t window; + uint16_t check; // checksum + uint16_t urg_ptr; // urgent pointer +} tcphdr_t; + +//----------------------icmp---------------------------------- +#define ICMP_ECHOREPLY 0 /* Echo Reply */ +#define ICMP_DEST_UNREACH 3 /* Destination Unreachable */ +#define ICMP_SOURCE_QUENCH 4 /* Source Quench */ +#define ICMP_REDIRECT 5 /* Redirect (change route) */ +#define ICMP_ECHO 8 /* Echo Request */ +#define ICMP_TIME_EXCEEDED 11 /* Time Exceeded */ +#define ICMP_PARAMETERPROB 12 /* Parameter Problem */ +#define ICMP_TIMESTAMP 13 /* Timestamp Request */ +#define ICMP_TIMESTAMPREPLY 14 /* Timestamp Reply */ +#define ICMP_INFO_REQUEST 15 /* Information Request */ +#define ICMP_INFO_REPLY 16 /* Information Reply */ +#define ICMP_ADDRESS 17 /* Address Mask Request */ +#define ICMP_ADDRESSREPLY 18 /* Address Mask Reply */ + +// sizeof(icmphdr_t) = 8 +typedef struct icmphdr_s { + uint8_t type; // message type + uint8_t code; // type sub-code + uint16_t checksum; + union { + struct { + uint16_t id; + uint16_t sequence; + } echo; + uint32_t gateway; + struct { + uint16_t reserved; + uint16_t mtu; + } frag; + } un; +} icmphdr_t; + +typedef struct icmp_s { + uint8_t icmp_type; + uint8_t icmp_code; + uint16_t icmp_cksum; + union { + uint8_t ih_pptr; + struct in_addr ih_gwaddr; + struct ih_idseq { + uint16_t icd_id; + uint16_t icd_seq; + } ih_idseq; + uint32_t ih_void; + + struct ih_pmtu { + uint16_t ipm_void; + uint16_t ipm_nextmtu; + } ih_pmtu; + + struct ih_rtradv { + uint8_t irt_num_addrs; + uint8_t irt_wpa; + uint16_t irt_lifetime; + } ih_rtradv; + } icmp_hun; +#define icmp_pptr icmp_hun.ih_pptr +#define icmp_gwaddr icmp_hun.ih_gwaddr +#define icmp_id icmp_hun.ih_idseq.icd_id +#define icmp_seq icmp_hun.ih_idseq.icd_seq +#define icmp_void icmp_hun.ih_void +#define icmp_pmvoid icmp_hun.ih_pmtu.ipm_void +#define icmp_nextmtu icmp_hun.ih_pmtu.ipm_nextmtu +#define icmp_num_addrs icmp_hun.ih_rtradv.irt_num_addrs +#define icmp_wpa icmp_hun.ih_rtradv.irt_wpa +#define icmp_lifetime icmp_hun.ih_rtradv.irt_lifetime + + union { + struct { + uint32_t its_otime; + uint32_t its_rtime; + uint32_t its_ttime; + } id_ts; + /* + struct { + struct ip idi_ip; + } id_ip; + struct icmp_ra_addr id_radv; + */ + uint32_t id_mask; + uint8_t id_data[1]; + } icmp_dun; +#define icmp_otime icmp_dun.id_ts.its_otime +#define icmp_rtime icmp_dun.id_ts.its_rtime +#define icmp_ttime icmp_dun.id_ts.its_ttime +#define icmp_ip icmp_dun.id_ip.idi_ip +#define icmp_radv icmp_dun.id_radv +#define icmp_mask icmp_dun.id_mask +#define icmp_data icmp_dun.id_data +} icmp_t; +//#endif + +static inline uint16_t checksum(uint8_t* buf, int len) { + unsigned int sum = 0; + uint16_t* ptr = (uint16_t*)buf; + while(len > 1) { + sum += *ptr++; + len -= 2; + } + if(len) { + sum += *(uint8_t*)ptr; + } + sum = (sum >> 16) + (sum & 0xffff); + sum += (sum >> 16); + return (uint16_t)(~sum); +}; + +#endif // HV_NETINET_H_ diff --git a/ww/libhv/base/queue.h b/ww/libhv/base/queue.h new file mode 100644 index 00000000..17140a9e --- /dev/null +++ b/ww/libhv/base/queue.h @@ -0,0 +1,105 @@ +#ifndef HV_QUEUE_H_ +#define HV_QUEUE_H_ + +/* + * queue + * FIFO: push_back,pop_front + * stack + * LIFO: push_back,pop_back + */ + +#include // for assert +#include // for NULL +#include // for malloc,realloc,free +#include // for memset,memmove + +#include "hbase.h" // for HV_ALLOC, HV_FREE + +#define QUEUE_INIT_SIZE 16 + +// #include +// typedef std::deque qtype; +#define QUEUE_DECL(type, qtype) \ +struct qtype { \ + type* ptr; \ + size_t size; \ + size_t maxsize;\ + size_t _offset;\ +}; \ +typedef struct qtype qtype;\ +\ +static inline type* qtype##_data(qtype* p) {\ + return p->ptr + p->_offset;\ +}\ +\ +static inline int qtype##_size(qtype* p) {\ + return p->size;\ +}\ +\ +static inline int qtype##_maxsize(qtype* p) {\ + return p->maxsize;\ +}\ +\ +static inline int qtype##_empty(qtype* p) {\ + return p->size == 0;\ +}\ +\ +static inline type* qtype##_front(qtype* p) {\ + return p->size == 0 ? NULL : p->ptr + p->_offset;\ +}\ +\ +static inline type* qtype##_back(qtype* p) {\ + return p->size == 0 ? NULL : p->ptr + p->_offset + p->size - 1;\ +}\ +\ +static inline void qtype##_init(qtype* p, int maxsize) {\ + p->_offset = 0;\ + p->size = 0;\ + p->maxsize = maxsize;\ + HV_ALLOC(p->ptr, sizeof(type) * maxsize);\ +}\ +\ +static inline void qtype##_clear(qtype* p) {\ + p->_offset = 0;\ + p->size = 0;\ + memset(p->ptr, 0, sizeof(type) * p->maxsize);\ +}\ +\ +static inline void qtype##_cleanup(qtype* p) {\ + HV_FREE(p->ptr);\ + p->_offset = p->size = p->maxsize = 0;\ +}\ +\ +static inline void qtype##_resize(qtype* p, int maxsize) {\ + if (maxsize == 0) maxsize = QUEUE_INIT_SIZE;\ + p->ptr = (type*)hv_realloc(p->ptr, sizeof(type) * maxsize, sizeof(type) * p->maxsize);\ + p->maxsize = maxsize;\ +}\ +\ +static inline void qtype##_double_resize(qtype* p) {\ + qtype##_resize(p, p->maxsize * 2);\ +}\ +\ +static inline void qtype##_push_back(qtype* p, type* elem) {\ + if (p->size == p->maxsize) {\ + qtype##_double_resize(p);\ + }\ + else if (p->_offset + p->size == p->maxsize) {\ + memmove(p->ptr, p->ptr + p->_offset, sizeof(type) * p->size);\ + p->_offset = 0;\ + }\ + p->ptr[p->_offset + p->size] = *elem;\ + p->size++;\ +}\ +static inline void qtype##_pop_front(qtype* p) {\ + assert(p->size > 0);\ + p->size--;\ + if (++p->_offset == p->maxsize) p->_offset = 0;\ +}\ +\ +static inline void qtype##_pop_back(qtype* p) {\ + assert(p->size > 0);\ + p->size--;\ +}\ + +#endif // HV_QUEUE_H_ diff --git a/ww/libhv/base/rbtree.c b/ww/libhv/base/rbtree.c new file mode 100644 index 00000000..9998f230 --- /dev/null +++ b/ww/libhv/base/rbtree.c @@ -0,0 +1,386 @@ +/* + Red Black Trees + (C) 1999 Andrea Arcangeli + (C) 2002 David Woodhouse + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + linux/lib/rbtree.c +*/ + +#include "rbtree.h" + +static void __rb_rotate_left(struct rb_node *node, struct rb_root *root) +{ + struct rb_node *right = node->rb_right; + + if ((node->rb_right = right->rb_left)) + right->rb_left->rb_parent = node; + right->rb_left = node; + + if ((right->rb_parent = node->rb_parent)) + { + if (node == node->rb_parent->rb_left) + node->rb_parent->rb_left = right; + else + node->rb_parent->rb_right = right; + } + else + root->rb_node = right; + node->rb_parent = right; +} + +static void __rb_rotate_right(struct rb_node *node, struct rb_root *root) +{ + struct rb_node *left = node->rb_left; + + if ((node->rb_left = left->rb_right)) + left->rb_right->rb_parent = node; + left->rb_right = node; + + if ((left->rb_parent = node->rb_parent)) + { + if (node == node->rb_parent->rb_right) + node->rb_parent->rb_right = left; + else + node->rb_parent->rb_left = left; + } + else + root->rb_node = left; + node->rb_parent = left; +} + +void rb_insert_color(struct rb_node *node, struct rb_root *root) +{ + struct rb_node *parent, *gparent; + + while ((parent = node->rb_parent) && parent->rb_color == RB_RED) + { + gparent = parent->rb_parent; + + if (parent == gparent->rb_left) + { + { + register struct rb_node *uncle = gparent->rb_right; + if (uncle && uncle->rb_color == RB_RED) + { + uncle->rb_color = RB_BLACK; + parent->rb_color = RB_BLACK; + gparent->rb_color = RB_RED; + node = gparent; + continue; + } + } + + if (parent->rb_right == node) + { + register struct rb_node *tmp; + __rb_rotate_left(parent, root); + tmp = parent; + parent = node; + node = tmp; + } + + parent->rb_color = RB_BLACK; + gparent->rb_color = RB_RED; + __rb_rotate_right(gparent, root); + } else { + { + register struct rb_node *uncle = gparent->rb_left; + if (uncle && uncle->rb_color == RB_RED) + { + uncle->rb_color = RB_BLACK; + parent->rb_color = RB_BLACK; + gparent->rb_color = RB_RED; + node = gparent; + continue; + } + } + + if (parent->rb_left == node) + { + register struct rb_node *tmp; + __rb_rotate_right(parent, root); + tmp = parent; + parent = node; + node = tmp; + } + + parent->rb_color = RB_BLACK; + gparent->rb_color = RB_RED; + __rb_rotate_left(gparent, root); + } + } + + root->rb_node->rb_color = RB_BLACK; +} + +static void __rb_erase_color(struct rb_node *node, struct rb_node *parent, + struct rb_root *root) +{ + struct rb_node *other; + + while ((!node || node->rb_color == RB_BLACK) && node != root->rb_node) + { + if (parent->rb_left == node) + { + other = parent->rb_right; + if (other->rb_color == RB_RED) + { + other->rb_color = RB_BLACK; + parent->rb_color = RB_RED; + __rb_rotate_left(parent, root); + other = parent->rb_right; + } + if ((!other->rb_left || + other->rb_left->rb_color == RB_BLACK) + && (!other->rb_right || + other->rb_right->rb_color == RB_BLACK)) + { + other->rb_color = RB_RED; + node = parent; + parent = node->rb_parent; + } + else + { + if (!other->rb_right || + other->rb_right->rb_color == RB_BLACK) + { + register struct rb_node *o_left; + if ((o_left = other->rb_left)) + o_left->rb_color = RB_BLACK; + other->rb_color = RB_RED; + __rb_rotate_right(other, root); + other = parent->rb_right; + } + other->rb_color = parent->rb_color; + parent->rb_color = RB_BLACK; + if (other->rb_right) + other->rb_right->rb_color = RB_BLACK; + __rb_rotate_left(parent, root); + node = root->rb_node; + break; + } + } + else + { + other = parent->rb_left; + if (other->rb_color == RB_RED) + { + other->rb_color = RB_BLACK; + parent->rb_color = RB_RED; + __rb_rotate_right(parent, root); + other = parent->rb_left; + } + if ((!other->rb_left || + other->rb_left->rb_color == RB_BLACK) + && (!other->rb_right || + other->rb_right->rb_color == RB_BLACK)) + { + other->rb_color = RB_RED; + node = parent; + parent = node->rb_parent; + } + else + { + if (!other->rb_left || + other->rb_left->rb_color == RB_BLACK) + { + register struct rb_node *o_right; + if ((o_right = other->rb_right)) + o_right->rb_color = RB_BLACK; + other->rb_color = RB_RED; + __rb_rotate_left(other, root); + other = parent->rb_left; + } + other->rb_color = parent->rb_color; + parent->rb_color = RB_BLACK; + if (other->rb_left) + other->rb_left->rb_color = RB_BLACK; + __rb_rotate_right(parent, root); + node = root->rb_node; + break; + } + } + } + if (node) + node->rb_color = RB_BLACK; +} + +void rb_erase(struct rb_node *node, struct rb_root *root) +{ + struct rb_node *child, *parent; + int color; + + if (!node->rb_left) + child = node->rb_right; + else if (!node->rb_right) + child = node->rb_left; + else + { + struct rb_node *old = node, *left; + + node = node->rb_right; + while ((left = node->rb_left)) + node = left; + child = node->rb_right; + parent = node->rb_parent; + color = node->rb_color; + + if (child) + child->rb_parent = parent; + if (parent) + { + if (parent->rb_left == node) + parent->rb_left = child; + else + parent->rb_right = child; + } + else + root->rb_node = child; + + if (node->rb_parent == old) + parent = node; + node->rb_parent = old->rb_parent; + node->rb_color = old->rb_color; + node->rb_right = old->rb_right; + node->rb_left = old->rb_left; + + if (old->rb_parent) + { + if (old->rb_parent->rb_left == old) + old->rb_parent->rb_left = node; + else + old->rb_parent->rb_right = node; + } else + root->rb_node = node; + + old->rb_left->rb_parent = node; + if (old->rb_right) + old->rb_right->rb_parent = node; + goto color; + } + + parent = node->rb_parent; + color = node->rb_color; + + if (child) + child->rb_parent = parent; + if (parent) + { + if (parent->rb_left == node) + parent->rb_left = child; + else + parent->rb_right = child; + } + else + root->rb_node = child; + + color: + if (color == RB_BLACK) + __rb_erase_color(child, parent, root); +} + +/* + * This function returns the first node (in sort order) of the tree. + */ +struct rb_node *rb_first(struct rb_root *root) +{ + struct rb_node *n; + + n = root->rb_node; + if (!n) + return (struct rb_node *)0; + while (n->rb_left) + n = n->rb_left; + return n; +} + +struct rb_node *rb_last(struct rb_root *root) +{ + struct rb_node *n; + + n = root->rb_node; + if (!n) + return (struct rb_node *)0; + while (n->rb_right) + n = n->rb_right; + return n; +} + +struct rb_node *rb_next(struct rb_node *node) +{ + /* If we have a right-hand child, go down and then left as far + as we can. */ + if (node->rb_right) { + node = node->rb_right; + while (node->rb_left) + node = node->rb_left; + return node; + } + + /* No right-hand children. Everything down and left is + smaller than us, so any 'next' node must be in the general + direction of our parent. Go up the tree; any time the + ancestor is a right-hand child of its parent, keep going + up. First time it's a left-hand child of its parent, said + parent is our 'next' node. */ + while (node->rb_parent && node == node->rb_parent->rb_right) + node = node->rb_parent; + + return node->rb_parent; +} + +struct rb_node *rb_prev(struct rb_node *node) +{ + /* If we have a left-hand child, go down and then right as far + as we can. */ + if (node->rb_left) { + node = node->rb_left; + while (node->rb_right) + node = node->rb_right; + return node; + } + + /* No left-hand children. Go up till we find an ancestor which + is a right-hand child of its parent */ + while (node->rb_parent && node == node->rb_parent->rb_left) + node = node->rb_parent; + + return node->rb_parent; +} + +void rb_replace_node(struct rb_node *victim, struct rb_node *newnode, + struct rb_root *root) +{ + struct rb_node *parent = victim->rb_parent; + + /* Set the surrounding nodes to point to the replacement */ + if (parent) { + if (victim == parent->rb_left) + parent->rb_left = newnode; + else + parent->rb_right = newnode; + } else { + root->rb_node = newnode; + } + if (victim->rb_left) + victim->rb_left->rb_parent = newnode; + if (victim->rb_right) + victim->rb_right->rb_parent = newnode; + + /* Copy the pointers/colour from the victim to the replacement */ + *newnode = *victim; +} diff --git a/ww/libhv/base/rbtree.h b/ww/libhv/base/rbtree.h new file mode 100644 index 00000000..602390a2 --- /dev/null +++ b/ww/libhv/base/rbtree.h @@ -0,0 +1,147 @@ +/* + Red Black Trees + (C) 1999 Andrea Arcangeli + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + linux/include/linux/rbtree.h + + To use rbtrees you'll have to implement your own insert and search cores. + This will avoid us to use callbacks and to drop drammatically performances. + I know it's not the cleaner way, but in C (not in C++) to get + performances and genericity... + + Some example of insert and search follows here. The search is a plain + normal search over an ordered tree. The insert instead must be implemented + in two steps: First, the code must insert the element in order as a red leaf + in the tree, and then the support library function rb_insert_color() must + be called. Such function will do the not trivial work to rebalance the + rbtree, if necessary. + +----------------------------------------------------------------------- +static inline struct page * rb_search_page_cache(struct inode * inode, + unsigned long offset) +{ + struct rb_node * n = inode->i_rb_page_cache.rb_node; + struct page * page; + + while (n) + { + page = rb_entry(n, struct page, rb_page_cache); + + if (offset < page->offset) + n = n->rb_left; + else if (offset > page->offset) + n = n->rb_right; + else + return page; + } + return NULL; +} + +static inline struct page * __rb_insert_page_cache(struct inode * inode, + unsigned long offset, + struct rb_node * node) +{ + struct rb_node ** p = &inode->i_rb_page_cache.rb_node; + struct rb_node * parent = NULL; + struct page * page; + + while (*p) + { + parent = *p; + page = rb_entry(parent, struct page, rb_page_cache); + + if (offset < page->offset) + p = &(*p)->rb_left; + else if (offset > page->offset) + p = &(*p)->rb_right; + else + return page; + } + + rb_link_node(node, parent, p); + + return NULL; +} + +static inline struct page * rb_insert_page_cache(struct inode * inode, + unsigned long offset, + struct rb_node * node) +{ + struct page * ret; + if ((ret = __rb_insert_page_cache(inode, offset, node))) + goto out; + rb_insert_color(node, &inode->i_rb_page_cache); + out: + return ret; +} +----------------------------------------------------------------------- +*/ + +#ifndef _LINUX_RBTREE_H +#define _LINUX_RBTREE_H + +struct rb_node +{ + struct rb_node *rb_parent; + struct rb_node *rb_right; + struct rb_node *rb_left; + char rb_color; +#define RB_RED 0 +#define RB_BLACK 1 +}; + +struct rb_root +{ + struct rb_node *rb_node; +}; + +#define RB_ROOT (struct rb_root){ (struct rb_node *)0, } +#define rb_entry(ptr, type, member) \ + ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) + +#ifdef __cplusplus +extern "C" +{ +#endif + +void rb_insert_color(struct rb_node *node, struct rb_root *root); +void rb_erase(struct rb_node *node, struct rb_root *root); + +/* Find logical next and previous nodes in a tree */ +struct rb_node *rb_next(struct rb_node *); +struct rb_node *rb_prev(struct rb_node *); +struct rb_node *rb_first(struct rb_root *); +struct rb_node *rb_last(struct rb_root *); + +/* Fast replacement of a single node without remove/rebalance/add/rebalance */ +void rb_replace_node(struct rb_node *victim, struct rb_node *newnode, + struct rb_root *root); + +#ifdef __cplusplus +} +#endif + +static inline void rb_link_node(struct rb_node *node, struct rb_node *parent, + struct rb_node **link) +{ + node->rb_parent = parent; + node->rb_color = RB_RED; + node->rb_left = node->rb_right = (struct rb_node *)0; + *link = node; +} + +#endif /* _LINUX_RBTREE_H */ diff --git a/ww/libhv/cmake/ios.toolchain.cmake b/ww/libhv/cmake/ios.toolchain.cmake new file mode 100644 index 00000000..8f3d814e --- /dev/null +++ b/ww/libhv/cmake/ios.toolchain.cmake @@ -0,0 +1,1028 @@ +# This file is part of the ios-cmake project. It was retrieved from +# https://github.com/leetal/ios-cmake.git, which is a fork of +# https://github.com/gerstrong/ios-cmake.git, which is a fork of +# https://github.com/cristeab/ios-cmake.git, which is a fork of +# https://code.google.com/p/ios-cmake/. Which in turn is based off of +# the Platform/Darwin.cmake and Platform/UnixPaths.cmake files which +# are included with CMake 2.8.4 +# +# The ios-cmake project is licensed under the new BSD license. +# +# Copyright (c) 2014, Bogdan Cristea and LTE Engineering Software, +# Kitware, Inc., Insight Software Consortium. All rights reserved. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# This file is based on the Platform/Darwin.cmake and +# Platform/UnixPaths.cmake files which are included with CMake 2.8.4 +# It has been altered for iOS development. +# +# Updated by Alex Stewart (alexs.mac@gmail.com) +# +# ***************************************************************************** +# Now maintained by Alexander Widerberg (widerbergaren [at] gmail.com) +# under the BSD-3-Clause license +# https://github.com/leetal/ios-cmake +# ***************************************************************************** +# +# INFORMATION / HELP +# +############################################################################### +# OPTIONS # +############################################################################### +# +# PLATFORM: (default "OS64") +# OS = Build for iPhoneOS. +# OS64 = Build for arm64 iphoneOS. +# OS64COMBINED = Build for arm64 x86_64 iphoneOS + iphoneOS Simulator. Combined into FAT STATIC lib (only supported on 3.14+ of CMake with "-G Xcode" argument in combination with the "cmake --install" CMake build step) +# SIMULATOR = Build for x86 i386 iphoneOS Simulator. +# SIMULATOR64 = Build for x86_64 iphoneOS Simulator. +# SIMULATORARM64 = Build for arm64 iphoneOS Simulator. +# TVOS = Build for arm64 tvOS. +# TVOSCOMBINED = Build for arm64 x86_64 tvOS + tvOS Simulator. Combined into FAT STATIC lib (only supported on 3.14+ of CMake with "-G Xcode" argument in combination with the "cmake --install" CMake build step) +# SIMULATOR_TVOS = Build for x86_64 tvOS Simulator. +# WATCHOS = Build for armv7k arm64_32 for watchOS. +# WATCHOSCOMBINED = Build for armv7k arm64_32 x86_64 watchOS + watchOS Simulator. Combined into FAT STATIC lib (only supported on 3.14+ of CMake with "-G Xcode" argument in combination with the "cmake --install" CMake build step) +# SIMULATOR_WATCHOS = Build for x86_64 for watchOS Simulator. +# MAC = Build for x86_64 macOS. +# MAC_ARM64 = Build for Apple Silicon macOS. +# MAC_UNIVERSAL = Combined build for x86_64 and Apple Silicon on macOS. +# MAC_CATALYST = Build for x86_64 macOS with Catalyst support (iOS toolchain on macOS). +# Note: The build argument "MACOSX_DEPLOYMENT_TARGET" can be used to control min-version of macOS +# MAC_CATALYST_ARM64 = Build for Apple Silicon macOS with Catalyst support (iOS toolchain on macOS). +# Note: The build argument "MACOSX_DEPLOYMENT_TARGET" can be used to control min-version of macOS +# +# CMAKE_OSX_SYSROOT: Path to the SDK to use. By default this is +# automatically determined from PLATFORM and xcodebuild, but +# can also be manually specified (although this should not be required). +# +# CMAKE_DEVELOPER_ROOT: Path to the Developer directory for the platform +# being compiled for. By default, this is automatically determined from +# CMAKE_OSX_SYSROOT, but can also be manually specified (although this should +# not be required). +# +# DEPLOYMENT_TARGET: Minimum SDK version to target. Default 2.0 on watchOS and 9.0 on tvOS+iOS +# +# NAMED_LANGUAGE_SUPPORT: +# ON (default) = Will require "enable_language(OBJC) and/or enable_language(OBJCXX)" for full OBJC|OBJCXX support +# OFF = Will embed the OBJC and OBJCXX flags into the CMAKE_C_FLAGS and CMAKE_CXX_FLAGS (legacy behavior, CMake version < 3.16) +# +# ENABLE_BITCODE: (ON|OFF) Enables or disables bitcode support. Default ON +# +# ENABLE_ARC: (ON|OFF) Enables or disables ARC support. Default ON (ARC enabled by default) +# +# ENABLE_VISIBILITY: (ON|OFF) Enables or disables symbol visibility support. Default OFF (visibility hidden by default) +# +# ENABLE_STRICT_TRY_COMPILE: (ON|OFF) Enables or disables strict try_compile() on all Check* directives (will run linker +# to actually check if linking is possible). Default OFF (will set CMAKE_TRY_COMPILE_TARGET_TYPE to STATIC_LIBRARY) +# +# ARCHS: (armv7 armv7s armv7k arm64 arm64_32 i386 x86_64) If specified, will override the default architectures for the given PLATFORM +# OS = armv7 armv7s arm64 (if applicable) +# OS64 = arm64 (if applicable) +# SIMULATOR = i386 +# SIMULATOR64 = x86_64 +# SIMULATORARM64 = arm64 +# TVOS = arm64 +# SIMULATOR_TVOS = x86_64 (i386 has since long been deprecated) +# WATCHOS = armv7k arm64_32 (if applicable) +# SIMULATOR_WATCHOS = x86_64 (i386 has since long been deprecated) +# MAC = x86_64 +# MAC_ARM64 = arm64 +# MAC_UNIVERSAL = x86_64 arm64 +# MAC_CATALYST = x86_64 +# MAC_CATALYST_ARM64 = arm64 +# +# NOTE: When manually specifying ARCHS, put a semi-colon between the entries. E.g., -DARCHS="armv7;arm64" +# +############################################################################### +# END OPTIONS # +############################################################################### +# +# This toolchain defines the following properties (available via get_property()) for use externally: +# +# PLATFORM: The currently targeted platform. +# XCODE_VERSION: Version number (not including Build version) of Xcode detected. +# SDK_VERSION: Version of SDK being used. +# OSX_ARCHITECTURES: Architectures being compiled for (generated from PLATFORM). +# APPLE_TARGET_TRIPLE: Used by autoconf build systems. NOTE: If "ARCHS" is overridden, this will *NOT* be set! +# +# This toolchain defines the following macros for use externally: +# +# set_xcode_property (TARGET XCODE_PROPERTY XCODE_VALUE XCODE_VARIANT) +# A convenience macro for setting xcode specific properties on targets. +# Available variants are: All, Release, RelWithDebInfo, Debug, MinSizeRel +# example: set_xcode_property (myioslib IPHONEOS_DEPLOYMENT_TARGET "3.1" "all"). +# +# find_host_package (PROGRAM ARGS) +# A macro used to find executable programs on the host system, not within the +# environment. Thanks to the android-cmake project for providing the +# command. +# + +cmake_minimum_required(VERSION 3.8.0) + +# CMake invokes the toolchain file twice during the first build, but only once during subsequent rebuilds. +if(DEFINED ENV{_IOS_TOOLCHAIN_HAS_RUN}) + return() +endif() +set(ENV{_IOS_TOOLCHAIN_HAS_RUN} true) + +# List of supported platform values +list(APPEND _supported_platforms + "OS" "OS64" "OS64COMBINED" "SIMULATOR" "SIMULATOR64" "SIMULATORARM64" + "TVOS" "TVOSCOMBINED" "SIMULATOR_TVOS" + "WATCHOS" "WATCHOSCOMBINED" "SIMULATOR_WATCHOS" + "MAC" "MAC_ARM64" "MAC_UNIVERSAL" + "MAC_CATALYST" "MAC_CATALYST_ARM64") + +# Cache what generator is used +set(USED_CMAKE_GENERATOR "${CMAKE_GENERATOR}") + +# Check if using a CMake version capable of building combined FAT builds (simulator and target slices combined in one static lib) +if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.14") + set(MODERN_CMAKE YES) +endif() + +# Get the Xcode version being used. +# Problem: CMake runs toolchain files multiple times, but can't read cache variables on some runs. +# Workaround: On the first run (in which cache variables are always accessible), set an intermediary environment variable. +# +# NOTE: This pattern is used in many places in this toolchain to speed up checks of all sorts +if(DEFINED XCODE_VERSION_INT) + # Environment variables are always preserved. + set(ENV{_XCODE_VERSION_INT} "${XCODE_VERSION_INT}") +elseif(DEFINED ENV{_XCODE_VERSION_INT}) + set(XCODE_VERSION_INT "$ENV{_XCODE_VERSION_INT}") +elseif(NOT DEFINED XCODE_VERSION_INT) + find_program(XCODEBUILD_EXECUTABLE xcodebuild) + if(NOT XCODEBUILD_EXECUTABLE) + message(FATAL_ERROR "xcodebuild not found. Please install either the standalone commandline tools or Xcode.") + endif() + execute_process(COMMAND ${XCODEBUILD_EXECUTABLE} -version + OUTPUT_VARIABLE XCODE_VERSION_INT + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + string(REGEX MATCH "Xcode [0-9\\.]+" XCODE_VERSION_INT "${XCODE_VERSION_INT}") + string(REGEX REPLACE "Xcode ([0-9\\.]+)" "\\1" XCODE_VERSION_INT "${XCODE_VERSION_INT}") + set(XCODE_VERSION_INT "${XCODE_VERSION_INT}" CACHE INTERNAL "") +endif() + +# Assuming that xcode 12.0 is installed you most probably have ios sdk 14.0 or later installed (tested on Big Sur) +# if you don't set a deployment target it will be set the way you only get 64-bit builds +if(NOT DEFINED DEPLOYMENT_TARGET AND XCODE_VERSION_INT VERSION_GREATER 12.0) + # Temporarily fix the arm64 issues in CMake install-combined by excluding arm64 for simulator builds (needed for Apple Silicon...) + set(CMAKE_XCODE_ATTRIBUTE_EXCLUDED_ARCHS[sdk=iphonesimulator*] "arm64") +endif() + +# Check if the platform variable is set +if(DEFINED PLATFORM) + # Environment variables are always preserved. + set(ENV{_PLATFORM} "${PLATFORM}") +elseif(DEFINED ENV{_PLATFORM}) + set(PLATFORM "$ENV{_PLATFORM}") +elseif(NOT DEFINED PLATFORM) + message(FATAL_ERROR "PLATFORM argument not set. Bailing configure since I don't know what target you want to build for!") +endif () + +if(PLATFORM MATCHES ".*COMBINED" AND NOT CMAKE_GENERATOR MATCHES "Xcode") + message(FATAL_ERROR "The combined builds support requires Xcode to be used as a generator via '-G Xcode' command-line argument in CMake") +endif() + +# Safeguard that the platform value is set and is one of the supported values +list(FIND _supported_platforms ${PLATFORM} contains_PLATFORM) +if("${contains_PLATFORM}" EQUAL "-1") + string(REPLACE ";" "\n * " _supported_platforms_formatted "${_supported_platforms}") + message(FATAL_ERROR " Invalid PLATFORM specified! Current value: ${PLATFORM}.\n" + " Supported PLATFORM values: \n * ${_supported_platforms_formatted}") +endif() + +# Check if Apple Silicon is supported +if(PLATFORM MATCHES "^(MAC_ARM64)$|^(MAC_CATALYST_ARM64)$|^(MAC_UNIVERSAL)$" AND ${CMAKE_VERSION} VERSION_LESS "3.19.5") + message(FATAL_ERROR "Apple Silicon builds requires a minimum of CMake 3.19.5") +endif() + +# Touch the toolchain variable to suppress the "unused variable" warning. +# This happens if CMake is invoked with the same command line the second time. +if(CMAKE_TOOLCHAIN_FILE) +endif() + +# Fix for PThread library not in path +set(CMAKE_THREAD_LIBS_INIT "-lpthread") +set(CMAKE_HAVE_THREADS_LIBRARY 1) +set(CMAKE_USE_WIN32_THREADS_INIT 0) +set(CMAKE_USE_PTHREADS_INIT 1) + +# Specify named language support defaults. +if(NOT DEFINED NAMED_LANGUAGE_SUPPORT AND ${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.16") + set(NAMED_LANGUAGE_SUPPORT ON) + message(STATUS "[DEFAULTS] Using explicit named language support! E.g., enable_language(CXX) is needed in the project files.") +elseif(NOT DEFINED NAMED_LANGUAGE_SUPPORT AND ${CMAKE_VERSION} VERSION_LESS "3.16") + set(NAMED_LANGUAGE_SUPPORT OFF) + message(STATUS "[DEFAULTS] Disabling explicit named language support. Falling back to legacy behavior.") +elseif(DEFINED NAMED_LANGUAGE_SUPPORT AND ${CMAKE_VERSION} VERSION_LESS "3.16") + message(FATAL_ERROR "CMake named language support for OBJC and OBJCXX was added in CMake 3.16.") +endif() +set(NAMED_LANGUAGE_SUPPORT_INT ${NAMED_LANGUAGE_SUPPORT} CACHE BOOL + "Whether or not to enable explicit named language support" FORCE) + +# Specify the minimum version of the deployment target. +if(NOT DEFINED DEPLOYMENT_TARGET) + if (PLATFORM MATCHES "WATCHOS") + # Unless specified, SDK version 4.0 is used by default as minimum target version (watchOS). + set(DEPLOYMENT_TARGET "4.0") + elseif(PLATFORM STREQUAL "MAC") + # Unless specified, SDK version 10.13 (High Sierra) is used by default as the minimum target version (macos). + set(DEPLOYMENT_TARGET "10.13") + elseif(PLATFORM STREQUAL "MAC_ARM64") + # Unless specified, SDK version 11.0 (Big Sur) is used by default as the minimum target version (macOS on arm). + set(DEPLOYMENT_TARGET "11.0") + elseif(PLATFORM STREQUAL "MAC_UNIVERSAL") + # Unless specified, SDK version 11.0 (Big Sur) is used by default as minimum target version for universal builds. + set(DEPLOYMENT_TARGET "11.0") + elseif(PLATFORM STREQUAL "MAC_CATALYST" OR PLATFORM STREQUAL "MAC_CATALYST_ARM64") + # Unless specified, SDK version 13.0 is used by default as the minimum target version (mac catalyst minimum requirement). + set(DEPLOYMENT_TARGET "13.1") + else() + # Unless specified, SDK version 11.0 is used by default as the minimum target version (iOS, tvOS). + set(DEPLOYMENT_TARGET "11.0") + endif() + message(STATUS "[DEFAULTS] Using the default min-version since DEPLOYMENT_TARGET not provided!") +elseif(DEFINED DEPLOYMENT_TARGET AND PLATFORM MATCHES "^MAC_CATALYST" AND ${DEPLOYMENT_TARGET} VERSION_LESS "13.1") + message(FATAL_ERROR "Mac Catalyst builds requires a minimum deployment target of 13.1!") +endif() + +# Store the DEPLOYMENT_TARGET in the cache +set(DEPLOYMENT_TARGET "${DEPLOYMENT_TARGET}" CACHE INTERNAL "") + +# Handle the case where we are targeting iOS and a version above 10.3.4 (32-bit support dropped officially) +if(PLATFORM STREQUAL "OS" AND DEPLOYMENT_TARGET VERSION_GREATER_EQUAL 10.3.4) + set(PLATFORM "OS64") + message(STATUS "Targeting minimum SDK version ${DEPLOYMENT_TARGET}. Dropping 32-bit support.") +elseif(PLATFORM STREQUAL "SIMULATOR" AND DEPLOYMENT_TARGET VERSION_GREATER_EQUAL 10.3.4) + set(PLATFORM "SIMULATOR64") + message(STATUS "Targeting minimum SDK version ${DEPLOYMENT_TARGET}. Dropping 32-bit support.") +endif() + +set(PLATFORM_INT "${PLATFORM}") + +if(DEFINED ARCHS) + string(REPLACE ";" "-" ARCHS_SPLIT "${ARCHS}") +endif() + +# Determine the platform name and architectures for use in xcodebuild commands +# from the specified PLATFORM_INT name. +if(PLATFORM_INT STREQUAL "OS") + set(SDK_NAME iphoneos) + if(NOT ARCHS) + set(ARCHS armv7 armv7s arm64) + set(APPLE_TARGET_TRIPLE_INT arm-apple-ios${DEPLOYMENT_TARGET}) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET}) + endif() +elseif(PLATFORM_INT STREQUAL "OS64") + set(SDK_NAME iphoneos) + if(NOT ARCHS) + if (XCODE_VERSION_INT VERSION_GREATER 10.0) + set(ARCHS arm64) # FIXME: Add arm64e when Apple has fixed the integration issues with it, libarclite_iphoneos.a is currently missing bitcode markers for example + else() + set(ARCHS arm64) + endif() + set(APPLE_TARGET_TRIPLE_INT aarch64-apple-ios${DEPLOYMENT_TARGET}) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET}) + endif() +elseif(PLATFORM_INT STREQUAL "OS64COMBINED") + set(SDK_NAME iphoneos) + if(MODERN_CMAKE) + if(NOT ARCHS) + if (XCODE_VERSION_INT VERSION_GREATER 10.0) + set(ARCHS arm64 x86_64) # FIXME: Add arm64e when Apple has fixed the integration issues with it, libarclite_iphoneos.a is currently missing bitcode markers for example + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=iphoneos*] "arm64") + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=iphonesimulator*] "x86_64") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphoneos*] "arm64") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphonesimulator*] "x86_64") + else() + set(ARCHS arm64 x86_64) + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=iphoneos*] "arm64") + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=iphonesimulator*] "x86_64") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphoneos*] "arm64") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphonesimulator*] "x86_64") + endif() + set(APPLE_TARGET_TRIPLE_INT aarch64-x86_64-apple-ios${DEPLOYMENT_TARGET}) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET}) + endif() + else() + message(FATAL_ERROR "Please make sure that you are running CMake 3.14+ to make the OS64COMBINED setting work") + endif() +elseif(PLATFORM_INT STREQUAL "SIMULATOR") + set(SDK_NAME iphonesimulator) + if(NOT ARCHS) + set(ARCHS i386) + set(APPLE_TARGET_TRIPLE_INT i386-apple-ios${DEPLOYMENT_TARGET}-simulator) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET}-simulator) + endif() + message(DEPRECATION "SIMULATOR IS DEPRECATED. Consider using SIMULATOR64 instead.") +elseif(PLATFORM_INT STREQUAL "SIMULATOR64") + set(SDK_NAME iphonesimulator) + if(NOT ARCHS) + set(ARCHS x86_64) + set(APPLE_TARGET_TRIPLE_INT x86_64-apple-ios${DEPLOYMENT_TARGET}-simulator) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET}-simulator) + endif() +elseif(PLATFORM_INT STREQUAL "SIMULATORARM64") + set(SDK_NAME iphonesimulator) + if(NOT ARCHS) + set(ARCHS arm64) + set(APPLE_TARGET_TRIPLE_INT aarch64-apple-ios${DEPLOYMENT_TARGET}-simulator) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET}-simulator) + endif() +elseif(PLATFORM_INT STREQUAL "TVOS") + set(SDK_NAME appletvos) + if(NOT ARCHS) + set(ARCHS arm64) + set(APPLE_TARGET_TRIPLE_INT aarch64-apple-tvos${DEPLOYMENT_TARGET}) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-tvos${DEPLOYMENT_TARGET}) + endif() +elseif (PLATFORM_INT STREQUAL "TVOSCOMBINED") + set(SDK_NAME appletvos) + if(MODERN_CMAKE) + if(NOT ARCHS) + set(ARCHS arm64 x86_64) + set(APPLE_TARGET_TRIPLE_INT aarch64-x86_64-apple-tvos${DEPLOYMENT_TARGET}) + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=appletvos*] "arm64") + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=appletvsimulator*] "x86_64") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=appletvos*] "arm64") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=appletvsimulator*] "x86_64") + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-tvos${DEPLOYMENT_TARGET}) + endif() + else() + message(FATAL_ERROR "Please make sure that you are running CMake 3.14+ to make the TVOSCOMBINED setting work") + endif() +elseif(PLATFORM_INT STREQUAL "SIMULATOR_TVOS") + set(SDK_NAME appletvsimulator) + if(NOT ARCHS) + set(ARCHS x86_64) + set(APPLE_TARGET_TRIPLE_INT x86_64-apple-tvos${DEPLOYMENT_TARGET}-simulator) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-tvos${DEPLOYMENT_TARGET}-simulator) + endif() +elseif(PLATFORM_INT STREQUAL "WATCHOS") + set(SDK_NAME watchos) + if(NOT ARCHS) + if (XCODE_VERSION_INT VERSION_GREATER 10.0) + set(ARCHS armv7k arm64_32) + set(APPLE_TARGET_TRIPLE_INT aarch64_32-apple-watchos${DEPLOYMENT_TARGET}) + else() + set(ARCHS armv7k) + set(APPLE_TARGET_TRIPLE_INT arm-apple-watchos${DEPLOYMENT_TARGET}) + endif() + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-watchos${DEPLOYMENT_TARGET}) + endif() +elseif(PLATFORM_INT STREQUAL "WATCHOSCOMBINED") + set(SDK_NAME watchos) + if(MODERN_CMAKE) + if(NOT ARCHS) + if (XCODE_VERSION_INT VERSION_GREATER 10.0) + set(ARCHS armv7k arm64_32 i386) + set(APPLE_TARGET_TRIPLE_INT aarch64_32-i386-apple-watchos${DEPLOYMENT_TARGET}) + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=watchos*] "armv7k arm64_32") + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=watchsimulator*] "i386") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=watchos*] "armv7k arm64_32") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=watchsimulator*] "i386") + else() + set(ARCHS armv7k i386) + set(APPLE_TARGET_TRIPLE_INT arm-i386-apple-watchos${DEPLOYMENT_TARGET}) + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=watchos*] "armv7k") + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=watchsimulator*] "i386") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=watchos*] "armv7k") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=watchsimulator*] "i386") + endif() + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-watchos${DEPLOYMENT_TARGET}) + endif() + else() + message(FATAL_ERROR "Please make sure that you are running CMake 3.14+ to make the WATCHOSCOMBINED setting work") + endif() +elseif(PLATFORM_INT STREQUAL "SIMULATOR_WATCHOS") + set(SDK_NAME watchsimulator) + if(NOT ARCHS) + set(ARCHS i386) + set(APPLE_TARGET_TRIPLE_INT i386-apple-watchos${DEPLOYMENT_TARGET}-simulator) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-watchos${DEPLOYMENT_TARGET}-simulator) + endif() +elseif(PLATFORM_INT STREQUAL "MAC" OR PLATFORM_INT STREQUAL "MAC_CATALYST") + set(SDK_NAME macosx) + if(NOT ARCHS) + set(ARCHS x86_64) + endif() + string(REPLACE ";" "-" ARCHS_SPLIT "${ARCHS}") + if(PLATFORM_INT STREQUAL "MAC") + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-macosx${DEPLOYMENT_TARGET}) + elseif(PLATFORM_INT STREQUAL "MAC_CATALYST") + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET}-macabi) + endif() +elseif(PLATFORM_INT MATCHES "^(MAC_ARM64)$|^(MAC_CATALYST_ARM64)$") + set(SDK_NAME macosx) + if(NOT ARCHS) + set(ARCHS arm64) + endif() + string(REPLACE ";" "-" ARCHS_SPLIT "${ARCHS}") + if(PLATFORM_INT STREQUAL "MAC_ARM64") + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-macosx${DEPLOYMENT_TARGET}) + elseif(PLATFORM_INT STREQUAL "MAC_CATALYST_ARM64") + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET}-macabi) + endif() +elseif(PLATFORM_INT STREQUAL "MAC_UNIVERSAL") + set(SDK_NAME macosx) + if(NOT ARCHS) + set(ARCHS "x86_64;arm64") + endif() + string(REPLACE ";" "-" ARCHS_SPLIT "${ARCHS}") + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-macosx${DEPLOYMENT_TARGET}) +else() + message(FATAL_ERROR "Invalid PLATFORM: ${PLATFORM_INT}") +endif() + +string(REPLACE ";" " " ARCHS_SPACED "${ARCHS}") + +if(MODERN_CMAKE AND PLATFORM_INT MATCHES ".*COMBINED" AND NOT CMAKE_GENERATOR MATCHES "Xcode") + message(FATAL_ERROR "The COMBINED options only work with Xcode generator, -G Xcode") +endif() + +if(CMAKE_GENERATOR MATCHES "Xcode" AND PLATFORM_INT MATCHES "^MAC_CATALYST") + set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++") + set(CMAKE_XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS "macosx") + set(CMAKE_XCODE_EFFECTIVE_PLATFORMS "-maccatalyst") + if(NOT DEFINED MACOSX_DEPLOYMENT_TARGET) + set(CMAKE_XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET "10.15") + else() + set(CMAKE_XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET "${MACOSX_DEPLOYMENT_TARGET}") + endif() +elseif(CMAKE_GENERATOR MATCHES "Xcode") + set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++") + set(CMAKE_XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET "${DEPLOYMENT_TARGET}") + if(NOT PLATFORM_INT MATCHES ".*COMBINED") + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=${SDK_NAME}*] "${ARCHS_SPACED}") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=${SDK_NAME}*] "${ARCHS_SPACED}") + endif() +endif() + +# If the user did not specify the SDK root to use, then query xcodebuild for it. +if(DEFINED CMAKE_OSX_SYSROOT_INT) + # Environment variables are always preserved. + set(ENV{_CMAKE_OSX_SYSROOT_INT} "${CMAKE_OSX_SYSROOT_INT}") +elseif(DEFINED ENV{_CMAKE_OSX_SYSROOT_INT}) + set(CMAKE_OSX_SYSROOT_INT "$ENV{_CMAKE_OSX_SYSROOT_INT}") +elseif(NOT DEFINED CMAKE_OSX_SYSROOT_INT) + execute_process(COMMAND ${XCODEBUILD_EXECUTABLE} -version -sdk ${SDK_NAME} Path + OUTPUT_VARIABLE CMAKE_OSX_SYSROOT_INT + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) +endif() + +if (NOT DEFINED CMAKE_OSX_SYSROOT_INT AND NOT DEFINED CMAKE_OSX_SYSROOT) + message(SEND_ERROR "Please make sure that Xcode is installed and that the toolchain" + "is pointing to the correct path. Please run:" + "sudo xcode-select -s /Applications/Xcode.app/Contents/Developer" + "and see if that fixes the problem for you.") + message(FATAL_ERROR "Invalid CMAKE_OSX_SYSROOT: ${CMAKE_OSX_SYSROOT} " + "does not exist.") +elseif(DEFINED CMAKE_OSX_SYSROOT_INT) + set(CMAKE_OSX_SYSROOT_INT "${CMAKE_OSX_SYSROOT_INT}" CACHE INTERNAL "") + # Specify the location or name of the platform SDK to be used in CMAKE_OSX_SYSROOT. + set(CMAKE_OSX_SYSROOT "${CMAKE_OSX_SYSROOT_INT}" CACHE INTERNAL "") +endif() + +# Use bitcode or not +if(NOT DEFINED ENABLE_BITCODE AND NOT ARCHS MATCHES "((^|;|, )(i386|x86_64))+") + # Unless specified, enable bitcode support by default + message(STATUS "[DEFAULTS] Enabling bitcode support by default. ENABLE_BITCODE not provided!") + set(ENABLE_BITCODE ON) +elseif(NOT DEFINED ENABLE_BITCODE) + message(STATUS "[DEFAULTS] Disabling bitcode support by default on simulators. ENABLE_BITCODE not provided for override!") + set(ENABLE_BITCODE OFF) +endif() +set(ENABLE_BITCODE_INT ${ENABLE_BITCODE} CACHE BOOL + "Whether or not to enable bitcode" FORCE) +# Use ARC or not +if(NOT DEFINED ENABLE_ARC) + # Unless specified, enable ARC support by default + set(ENABLE_ARC ON) + message(STATUS "[DEFAULTS] Enabling ARC support by default. ENABLE_ARC not provided!") +endif() +set(ENABLE_ARC_INT ${ENABLE_ARC} CACHE BOOL "Whether or not to enable ARC" FORCE) +# Use hidden visibility or not +if(NOT DEFINED ENABLE_VISIBILITY) + # Unless specified, disable symbols visibility by default + set(ENABLE_VISIBILITY OFF) + message(STATUS "[DEFAULTS] Hiding symbols visibility by default. ENABLE_VISIBILITY not provided!") +endif() +set(ENABLE_VISIBILITY_INT ${ENABLE_VISIBILITY} CACHE BOOL "Whether or not to hide symbols from the dynamic linker (-fvisibility=hidden)" FORCE) +# Set strict compiler checks or not +if(NOT DEFINED ENABLE_STRICT_TRY_COMPILE) + # Unless specified, disable strict try_compile() + set(ENABLE_STRICT_TRY_COMPILE OFF) + message(STATUS "[DEFAULTS] Using NON-strict compiler checks by default. ENABLE_STRICT_TRY_COMPILE not provided!") +endif() +set(ENABLE_STRICT_TRY_COMPILE_INT ${ENABLE_STRICT_TRY_COMPILE} CACHE BOOL + "Whether or not to use strict compiler checks" FORCE) + +# Get the SDK version information. +if(DEFINED SDK_VERSION) + # Environment variables are always preserved. + set(ENV{_SDK_VERSION} "${SDK_VERSION}") +elseif(DEFINED ENV{_SDK_VERSION}) + set(SDK_VERSION "$ENV{_SDK_VERSION}") +elseif(NOT DEFINED SDK_VERSION) + execute_process(COMMAND ${XCODEBUILD_EXECUTABLE} -sdk ${CMAKE_OSX_SYSROOT_INT} -version SDKVersion + OUTPUT_VARIABLE SDK_VERSION + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) +endif() + +# Find the Developer root for the specific iOS platform being compiled for +# from CMAKE_OSX_SYSROOT. Should be ../../ from SDK specified in +# CMAKE_OSX_SYSROOT. There does not appear to be a direct way to obtain +# this information from xcrun or xcodebuild. +if (NOT DEFINED CMAKE_DEVELOPER_ROOT AND NOT CMAKE_GENERATOR MATCHES "Xcode") + get_filename_component(PLATFORM_SDK_DIR ${CMAKE_OSX_SYSROOT_INT} PATH) + get_filename_component(CMAKE_DEVELOPER_ROOT ${PLATFORM_SDK_DIR} PATH) + if (NOT EXISTS "${CMAKE_DEVELOPER_ROOT}") + message(FATAL_ERROR "Invalid CMAKE_DEVELOPER_ROOT: ${CMAKE_DEVELOPER_ROOT} does not exist.") + endif() +endif() + +# Find the C & C++ compilers for the specified SDK. +if(DEFINED CMAKE_C_COMPILER) + # Environment variables are always preserved. + set(ENV{_CMAKE_C_COMPILER} "${CMAKE_C_COMPILER}") +elseif(DEFINED ENV{_CMAKE_C_COMPILER}) + set(CMAKE_C_COMPILER "$ENV{_CMAKE_C_COMPILER}") + set(CMAKE_ASM_COMPILER ${CMAKE_C_COMPILER}) +elseif(NOT DEFINED CMAKE_C_COMPILER) + execute_process(COMMAND xcrun -sdk ${CMAKE_OSX_SYSROOT_INT} -find clang + OUTPUT_VARIABLE CMAKE_C_COMPILER + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + set(CMAKE_ASM_COMPILER ${CMAKE_C_COMPILER}) +endif() +if(DEFINED CMAKE_CXX_COMPILER) + # Environment variables are always preserved. + set(ENV{_CMAKE_CXX_COMPILER} "${CMAKE_CXX_COMPILER}") +elseif(DEFINED ENV{_CMAKE_CXX_COMPILER}) + set(CMAKE_CXX_COMPILER "$ENV{_CMAKE_CXX_COMPILER}") +elseif(NOT DEFINED CMAKE_CXX_COMPILER) + execute_process(COMMAND xcrun -sdk ${CMAKE_OSX_SYSROOT_INT} -find clang++ + OUTPUT_VARIABLE CMAKE_CXX_COMPILER + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) +endif() +# Find (Apple's) libtool. +if(DEFINED BUILD_LIBTOOL) + # Environment variables are always preserved. + set(ENV{_BUILD_LIBTOOL} "${BUILD_LIBTOOL}") +elseif(DEFINED ENV{_BUILD_LIBTOOL}) + set(BUILD_LIBTOOL "$ENV{_BUILD_LIBTOOL}") +elseif(NOT DEFINED BUILD_LIBTOOL) + execute_process(COMMAND xcrun -sdk ${CMAKE_OSX_SYSROOT_INT} -find libtool + OUTPUT_VARIABLE BUILD_LIBTOOL + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) +endif() +# Find the toolchain's provided install_name_tool if none is found on the host +if(DEFINED CMAKE_INSTALL_NAME_TOOL) + # Environment variables are always preserved. + set(ENV{_CMAKE_INSTALL_NAME_TOOL} "${CMAKE_INSTALL_NAME_TOOL}") +elseif(DEFINED ENV{_CMAKE_INSTALL_NAME_TOOL}) + set(CMAKE_INSTALL_NAME_TOOL "$ENV{_CMAKE_INSTALL_NAME_TOOL}") +elseif(NOT DEFINED CMAKE_INSTALL_NAME_TOOL) + execute_process(COMMAND xcrun -sdk ${CMAKE_OSX_SYSROOT_INT} -find install_name_tool + OUTPUT_VARIABLE CMAKE_INSTALL_NAME_TOOL_INT + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + set(CMAKE_INSTALL_NAME_TOOL ${CMAKE_INSTALL_NAME_TOOL_INT} CACHE INTERNAL "") +endif() + +# Configure libtool to be used instead of ar + ranlib to build static libraries. +# This is required on Xcode 7+, but should also work on previous versions of +# Xcode. +get_property(languages GLOBAL PROPERTY ENABLED_LANGUAGES) +foreach(lang ${languages}) + set(CMAKE_${lang}_CREATE_STATIC_LIBRARY "${BUILD_LIBTOOL} -static -o " CACHE INTERNAL "") +endforeach() + +# CMake 3.14+ support building for iOS, watchOS, and tvOS out of the box. +if(MODERN_CMAKE) + if(SDK_NAME MATCHES "iphone") + set(CMAKE_SYSTEM_NAME iOS) + elseif(SDK_NAME MATCHES "macosx") + set(CMAKE_SYSTEM_NAME Darwin) + elseif(SDK_NAME MATCHES "appletv") + set(CMAKE_SYSTEM_NAME tvOS) + elseif(SDK_NAME MATCHES "watch") + set(CMAKE_SYSTEM_NAME watchOS) + endif() + # Provide flags for a combined FAT library build on newer CMake versions + if(PLATFORM_INT MATCHES ".*COMBINED") + set(CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH "NO") + set(CMAKE_IOS_INSTALL_COMBINED YES) + endif() +elseif(NOT DEFINED CMAKE_SYSTEM_NAME AND ${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.10") + # Legacy code path prior to CMake 3.14 or fallback if no CMAKE_SYSTEM_NAME specified + set(CMAKE_SYSTEM_NAME iOS) +elseif(NOT DEFINED CMAKE_SYSTEM_NAME) + # Legacy code path before CMake 3.14 or fallback if no CMAKE_SYSTEM_NAME specified + set(CMAKE_SYSTEM_NAME Darwin) +endif() +# Standard settings. +set(CMAKE_SYSTEM_VERSION ${SDK_VERSION} CACHE INTERNAL "") +set(UNIX ON CACHE BOOL "") +set(APPLE ON CACHE BOOL "") +if(PLATFORM STREQUAL "MAC" OR PLATFORM STREQUAL "MAC_ARM64" OR PLATFORM STREQUAL "MAC_UNIVERSAL") + set(IOS OFF CACHE BOOL "") + set(MACOS ON CACHE BOOL "") +elseif(PLATFORM STREQUAL "MAC_CATALYST" OR PLATFORM STREQUAL "MAC_CATALYST_ARM64") + set(IOS ON CACHE BOOL "") + set(MACOS ON CACHE BOOL "") +else() + set(IOS ON CACHE BOOL "") +endif() +set(CMAKE_AR ar CACHE FILEPATH "" FORCE) +set(CMAKE_RANLIB ranlib CACHE FILEPATH "" FORCE) +set(CMAKE_STRIP strip CACHE FILEPATH "" FORCE) +# Set the architectures for which to build. +set(CMAKE_OSX_ARCHITECTURES ${ARCHS} CACHE INTERNAL "") +# Change the type of target generated for try_compile() so it'll work when cross-compiling, weak compiler checks +if(NOT ENABLE_STRICT_TRY_COMPILE_INT) + set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) +endif() +# All iOS/Darwin specific settings - some may be redundant. +if (NOT DEFINED CMAKE_MACOSX_BUNDLE) + set(CMAKE_MACOSX_BUNDLE YES) +endif() +set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO") +set(CMAKE_SHARED_LIBRARY_PREFIX "lib") +set(CMAKE_SHARED_LIBRARY_SUFFIX ".dylib") +set(CMAKE_SHARED_MODULE_PREFIX "lib") +set(CMAKE_SHARED_MODULE_SUFFIX ".so") +set(CMAKE_C_COMPILER_ABI ELF) +set(CMAKE_CXX_COMPILER_ABI ELF) +set(CMAKE_C_HAS_ISYSROOT 1) +set(CMAKE_CXX_HAS_ISYSROOT 1) +set(CMAKE_MODULE_EXISTS 1) +set(CMAKE_DL_LIBS "") +set(CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG "-compatibility_version ") +set(CMAKE_C_OSX_CURRENT_VERSION_FLAG "-current_version ") +set(CMAKE_CXX_OSX_COMPATIBILITY_VERSION_FLAG "${CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG}") +set(CMAKE_CXX_OSX_CURRENT_VERSION_FLAG "${CMAKE_C_OSX_CURRENT_VERSION_FLAG}") + +if(ARCHS MATCHES "((^|;|, )(arm64|arm64e|x86_64))+") + set(CMAKE_C_SIZEOF_DATA_PTR 8) + set(CMAKE_CXX_SIZEOF_DATA_PTR 8) + if(ARCHS MATCHES "((^|;|, )(arm64|arm64e))+") + set(CMAKE_SYSTEM_PROCESSOR "aarch64") + else() + set(CMAKE_SYSTEM_PROCESSOR "x86_64") + endif() +else() + set(CMAKE_C_SIZEOF_DATA_PTR 4) + set(CMAKE_CXX_SIZEOF_DATA_PTR 4) + set(CMAKE_SYSTEM_PROCESSOR "arm") +endif() + +# Note that only Xcode 7+ supports the newer more specific: +# -m${SDK_NAME}-version-min flags, older versions of Xcode use: +# -m(ios/ios-simulator)-version-min instead. +if(${CMAKE_VERSION} VERSION_LESS "3.11") + if(PLATFORM_INT STREQUAL "OS" OR PLATFORM_INT STREQUAL "OS64") + if(XCODE_VERSION_INT VERSION_LESS 7.0) + set(SDK_NAME_VERSION_FLAGS + "-mios-version-min=${DEPLOYMENT_TARGET}") + else() + # Xcode 7.0+ uses flags we can build directly from SDK_NAME. + set(SDK_NAME_VERSION_FLAGS + "-m${SDK_NAME}-version-min=${DEPLOYMENT_TARGET}") + endif() + elseif(PLATFORM_INT STREQUAL "TVOS") + set(SDK_NAME_VERSION_FLAGS + "-mtvos-version-min=${DEPLOYMENT_TARGET}") + elseif(PLATFORM_INT STREQUAL "SIMULATOR_TVOS") + set(SDK_NAME_VERSION_FLAGS + "-mtvos-simulator-version-min=${DEPLOYMENT_TARGET}") + elseif(PLATFORM_INT STREQUAL "WATCHOS") + set(SDK_NAME_VERSION_FLAGS + "-mwatchos-version-min=${DEPLOYMENT_TARGET}") + elseif(PLATFORM_INT STREQUAL "SIMULATOR_WATCHOS") + set(SDK_NAME_VERSION_FLAGS + "-mwatchos-simulator-version-min=${DEPLOYMENT_TARGET}") + elseif(PLATFORM_INT STREQUAL "MAC") + set(SDK_NAME_VERSION_FLAGS + "-mmacosx-version-min=${DEPLOYMENT_TARGET}") + else() + # SIMULATOR or SIMULATOR64 both use -mios-simulator-version-min. + set(SDK_NAME_VERSION_FLAGS + "-mios-simulator-version-min=${DEPLOYMENT_TARGET}") + endif() +elseif(NOT PLATFORM_INT MATCHES "^MAC_CATALYST") + # Newer versions of CMake sets the version min flags correctly, skip this for Mac Catalyst targets + set(CMAKE_OSX_DEPLOYMENT_TARGET ${DEPLOYMENT_TARGET}) +endif() + +if(DEFINED APPLE_TARGET_TRIPLE_INT) + set(APPLE_TARGET_TRIPLE ${APPLE_TARGET_TRIPLE_INT} CACHE INTERNAL "") + set(CMAKE_C_COMPILER_TARGET ${APPLE_TARGET_TRIPLE}) + set(CMAKE_CXX_COMPILER_TARGET ${APPLE_TARGET_TRIPLE}) + set(CMAKE_ASM_COMPILER_TARGET ${APPLE_TARGET_TRIPLE}) +endif() + +if(PLATFORM_INT MATCHES "^MAC_CATALYST") + set(C_TARGET_FLAGS "-isystem ${CMAKE_OSX_SYSROOT_INT}/System/iOSSupport/usr/include -iframework ${CMAKE_OSX_SYSROOT_INT}/System/iOSSupport/System/Library/Frameworks") +endif() + +if(ENABLE_BITCODE_INT) + set(BITCODE "-fembed-bitcode") + set(CMAKE_XCODE_ATTRIBUTE_BITCODE_GENERATION_MODE "bitcode") + set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "YES") +else() + set(BITCODE "") + set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "NO") +endif() + +if(ENABLE_ARC_INT) + set(FOBJC_ARC "-fobjc-arc") + set(CMAKE_XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC "YES") +else() + set(FOBJC_ARC "-fno-objc-arc") + set(CMAKE_XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC "NO") +endif() + +if(NAMED_LANGUAGE_SUPPORT_INT) + set(OBJC_VARS "-fobjc-abi-version=2 -DOBJC_OLD_DISPATCH_PROTOTYPES=0") + set(OBJC_LEGACY_VARS "") +else() + set(OBJC_VARS "") + set(OBJC_LEGACY_VARS "-fobjc-abi-version=2 -DOBJC_OLD_DISPATCH_PROTOTYPES=0") +endif() + +if(NOT ENABLE_VISIBILITY_INT) + foreach(lang ${languages}) + set(CMAKE_${lang}_VISIBILITY_PRESET "hidden" CACHE INTERNAL "") + endforeach() + set(CMAKE_XCODE_ATTRIBUTE_GCC_SYMBOLS_PRIVATE_EXTERN "YES") + set(VISIBILITY "-fvisibility=hidden -fvisibility-inlines-hidden") +else() + foreach(lang ${languages}) + set(CMAKE_${lang}_VISIBILITY_PRESET "default" CACHE INTERNAL "") + endforeach() + set(CMAKE_XCODE_ATTRIBUTE_GCC_SYMBOLS_PRIVATE_EXTERN "NO") + set(VISIBILITY "-fvisibility=default") +endif() + +if(DEFINED APPLE_TARGET_TRIPLE) + set(APPLE_TARGET_TRIPLE_FLAG "-target ${APPLE_TARGET_TRIPLE}") +endif() + +#Check if Xcode generator is used since that will handle these flags automagically +if(CMAKE_GENERATOR MATCHES "Xcode") + message(STATUS "Not setting any manual command-line buildflags, since Xcode is selected as the generator. Modifying the Xcode build-settings directly instead.") +else() + set(CMAKE_C_FLAGS "${C_TARGET_FLAGS} ${APPLE_TARGET_TRIPLE_FLAG} ${SDK_NAME_VERSION_FLAGS} ${OBJC_LEGACY_VARS} ${BITCODE} ${VISIBILITY} ${CMAKE_C_FLAGS}") + set(CMAKE_C_FLAGS_DEBUG "-O0 -g ${CMAKE_C_FLAGS_DEBUG}") + set(CMAKE_C_FLAGS_MINSIZEREL "-DNDEBUG -Os ${CMAKE_C_FLAGS_MINSIZEREL}") + set(CMAKE_C_FLAGS_RELWITHDEBINFO "-DNDEBUG -O2 -g ${CMAKE_C_FLAGS_RELWITHDEBINFO}") + set(CMAKE_C_FLAGS_RELEASE "-DNDEBUG -O3 ${CMAKE_C_FLAGS_RELEASE}") + set(CMAKE_CXX_FLAGS "${C_TARGET_FLAGS} ${APPLE_TARGET_TRIPLE_FLAG} ${SDK_NAME_VERSION_FLAGS} ${OBJC_LEGACY_VARS} ${BITCODE} ${VISIBILITY} ${CMAKE_CXX_FLAGS}") + set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g ${CMAKE_CXX_FLAGS_DEBUG}") + set(CMAKE_CXX_FLAGS_MINSIZEREL "-DNDEBUG -Os ${CMAKE_CXX_FLAGS_MINSIZEREL}") + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-DNDEBUG -O2 -g ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") + set(CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG -O3 ${CMAKE_CXX_FLAGS_RELEASE}") + if(NAMED_LANGUAGE_SUPPORT_INT) + set(CMAKE_OBJC_FLAGS "${C_TARGET_FLAGS} ${APPLE_TARGET_TRIPLE_FLAG} ${SDK_NAME_VERSION_FLAGS} ${BITCODE} ${VISIBILITY} ${FOBJC_ARC} ${OBJC_VARS} ${CMAKE_OBJC_FLAGS}") + set(CMAKE_OBJC_FLAGS_DEBUG "-O0 -g ${CMAKE_OBJC_FLAGS_DEBUG}") + set(CMAKE_OBJC_FLAGS_MINSIZEREL "-DNDEBUG -Os ${CMAKE_OBJC_FLAGS_MINSIZEREL}") + set(CMAKE_OBJC_FLAGS_RELWITHDEBINFO "-DNDEBUG -O2 -g ${CMAKE_OBJC_FLAGS_RELWITHDEBINFO}") + set(CMAKE_OBJC_FLAGS_RELEASE "-DNDEBUG -O3 ${CMAKE_OBJC_FLAGS_RELEASE}") + set(CMAKE_OBJCXX_FLAGS "${C_TARGET_FLAGS} ${APPLE_TARGET_TRIPLE_FLAG} ${SDK_NAME_VERSION_FLAGS} ${BITCODE} ${VISIBILITY} ${FOBJC_ARC} ${OBJC_VARS} ${CMAKE_OBJCXX_FLAGS}") + set(CMAKE_OBJCXX_FLAGS_DEBUG "-O0 -g ${CMAKE_OBJCXX_FLAGS_DEBUG}") + set(CMAKE_OBJCXX_FLAGS_MINSIZEREL "-DNDEBUG -Os ${CMAKE_OBJCXX_FLAGS_MINSIZEREL}") + set(CMAKE_OBJCXX_FLAGS_RELWITHDEBINFO "-DNDEBUG -O2 -g ${CMAKE_OBJCXX_FLAGS_RELWITHDEBINFO}") + set(CMAKE_OBJCXX_FLAGS_RELEASE "-DNDEBUG -O3 ${CMAKE_OBJCXX_FLAGS_RELEASE}") + endif() + set(CMAKE_C_LINK_FLAGS "${C_TARGET_FLAGS} ${SDK_NAME_VERSION_FLAGS} -Wl,-search_paths_first ${CMAKE_C_LINK_FLAGS}") + set(CMAKE_CXX_LINK_FLAGS "${C_TARGET_FLAGS} ${SDK_NAME_VERSION_FLAGS} -Wl,-search_paths_first ${CMAKE_CXX_LINK_FLAGS}") + if(NAMED_LANGUAGE_SUPPORT_INT) + set(CMAKE_OBJC_LINK_FLAGS "${C_TARGET_FLAGS} ${SDK_NAME_VERSION_FLAGS} -Wl,-search_paths_first ${CMAKE_OBJC_LINK_FLAGS}") + set(CMAKE_OBJCXX_LINK_FLAGS "${C_TARGET_FLAGS} ${SDK_NAME_VERSION_FLAGS} -Wl,-search_paths_first ${CMAKE_OBJCXX_LINK_FLAGS}") + endif() + set(CMAKE_ASM_FLAGS "${CMAKE_C_FLAGS} -x assembler-with-cpp -arch ${CMAKE_OSX_ARCHITECTURES} ${APPLE_TARGET_TRIPLE_FLAG}") +endif() + +## Print status messages to inform of the current state +message(STATUS "Configuring ${SDK_NAME} build for platform: ${PLATFORM_INT}, architecture(s): ${ARCHS}") +message(STATUS "Using SDK: ${CMAKE_OSX_SYSROOT_INT}") +message(STATUS "Using C compiler: ${CMAKE_C_COMPILER}") +message(STATUS "Using CXX compiler: ${CMAKE_CXX_COMPILER}") +message(STATUS "Using libtool: ${BUILD_LIBTOOL}") +message(STATUS "Using install name tool: ${CMAKE_INSTALL_NAME_TOOL}") +if(DEFINED APPLE_TARGET_TRIPLE) + message(STATUS "Autoconf target triple: ${APPLE_TARGET_TRIPLE}") +endif() +message(STATUS "Using minimum deployment version: ${DEPLOYMENT_TARGET}" + " (SDK version: ${SDK_VERSION})") +if(MODERN_CMAKE) + message(STATUS "Merging integrated CMake 3.14+ iOS,tvOS,watchOS,macOS toolchain(s) with this toolchain!") + if(PLATFORM_INT MATCHES ".*COMBINED") + message(STATUS "Will combine built (static) artifacts into FAT lib...") + endif() +endif() +if(CMAKE_GENERATOR MATCHES "Xcode") + message(STATUS "Using Xcode version: ${XCODE_VERSION_INT}") +endif() +message(STATUS "CMake version: ${CMAKE_VERSION}") +if(DEFINED SDK_NAME_VERSION_FLAGS) + message(STATUS "Using version flags: ${SDK_NAME_VERSION_FLAGS}") +endif() +message(STATUS "Using a data_ptr size of: ${CMAKE_CXX_SIZEOF_DATA_PTR}") +if(ENABLE_BITCODE_INT) + message(STATUS "Bitcode: Enabled") +else() + message(STATUS "Bitcode: Disabled") +endif() + +if(ENABLE_ARC_INT) + message(STATUS "ARC: Enabled") +else() + message(STATUS "ARC: Disabled") +endif() + +if(ENABLE_VISIBILITY_INT) + message(STATUS "Hiding symbols: Disabled") +else() + message(STATUS "Hiding symbols: Enabled") +endif() + +# Set global properties +set_property(GLOBAL PROPERTY PLATFORM "${PLATFORM}") +set_property(GLOBAL PROPERTY APPLE_TARGET_TRIPLE "${APPLE_TARGET_TRIPLE_INT}") +set_property(GLOBAL PROPERTY SDK_VERSION "${SDK_VERSION}") +set_property(GLOBAL PROPERTY XCODE_VERSION "${XCODE_VERSION_INT}") +set_property(GLOBAL PROPERTY OSX_ARCHITECTURES "${CMAKE_OSX_ARCHITECTURES}") + +# Export configurable variables for the try_compile() command. +set(CMAKE_TRY_COMPILE_PLATFORM_VARIABLES + PLATFORM + XCODE_VERSION_INT + SDK_VERSION + NAMED_LANGUAGE_SUPPORT + DEPLOYMENT_TARGET + CMAKE_DEVELOPER_ROOT + CMAKE_OSX_SYSROOT_INT + ENABLE_BITCODE + ENABLE_ARC + CMAKE_ASM_COMPILER + CMAKE_C_COMPILER + CMAKE_C_COMPILER_TARGET + CMAKE_CXX_COMPILER + CMAKE_CXX_COMPILER_TARGET + BUILD_LIBTOOL + CMAKE_INSTALL_NAME_TOOL + CMAKE_C_FLAGS + CMAKE_C_DEBUG + CMAKE_C_MINSIZEREL + CMAKE_C_RELWITHDEBINFO + CMAKE_C_RELEASE + CMAKE_CXX_FLAGS + CMAKE_CXX_FLAGS_DEBUG + CMAKE_CXX_FLAGS_MINSIZEREL + CMAKE_CXX_FLAGS_RELWITHDEBINFO + CMAKE_CXX_FLAGS_RELEASE + CMAKE_C_LINK_FLAGS + CMAKE_CXX_LINK_FLAGS + CMAKE_ASM_FLAGS +) + +if(NAMED_LANGUAGE_SUPPORT_INT) + list(APPEND CMAKE_TRY_COMPILE_PLATFORM_VARIABLES + CMAKE_OBJC_FLAGS + CMAKE_OBJC_DEBUG + CMAKE_OBJC_MINSIZEREL + CMAKE_OBJC_RELWITHDEBINFO + CMAKE_OBJC_RELEASE + CMAKE_OBJCXX_FLAGS + CMAKE_OBJCXX_DEBUG + CMAKE_OBJCXX_MINSIZEREL + CMAKE_OBJCXX_RELWITHDEBINFO + CMAKE_OBJCXX_RELEASE + CMAKE_OBJC_LINK_FLAGS + CMAKE_OBJCXX_LINK_FLAGS + ) +endif() + +set(CMAKE_PLATFORM_HAS_INSTALLNAME 1) +set(CMAKE_SHARED_LINKER_FLAGS "-rpath @executable_path/Frameworks -rpath @loader_path/Frameworks") +set(CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS "-dynamiclib -Wl,-headerpad_max_install_names") +set(CMAKE_SHARED_MODULE_CREATE_C_FLAGS "-bundle -Wl,-headerpad_max_install_names") +set(CMAKE_SHARED_MODULE_LOADER_C_FLAG "-Wl,-bundle_loader,") +set(CMAKE_SHARED_MODULE_LOADER_CXX_FLAG "-Wl,-bundle_loader,") +set(CMAKE_FIND_LIBRARY_SUFFIXES ".tbd" ".dylib" ".so" ".a") +set(CMAKE_SHARED_LIBRARY_SONAME_C_FLAG "-install_name") + +# Set the find root to the SDK developer roots. +# Note: CMAKE_FIND_ROOT_PATH is only useful when cross-compiling. Thus, do not set on macOS builds. +if(NOT PLATFORM_INT MATCHES "^MAC.*$") + list(APPEND CMAKE_FIND_ROOT_PATH "${CMAKE_OSX_SYSROOT_INT}" CACHE INTERNAL "") + set(CMAKE_IGNORE_PATH "/System/Library/Frameworks;/usr/local/lib" CACHE INTERNAL "") +endif() + +# Default to searching for frameworks first. +set(CMAKE_FIND_FRAMEWORK FIRST) + +# Set up the default search directories for frameworks. +if(PLATFORM_INT MATCHES "^MAC_CATALYST") + set(CMAKE_FRAMEWORK_PATH + ${CMAKE_DEVELOPER_ROOT}/Library/PrivateFrameworks + ${CMAKE_OSX_SYSROOT_INT}/System/Library/Frameworks + ${CMAKE_OSX_SYSROOT_INT}/System/iOSSupport/System/Library/Frameworks + ${CMAKE_FRAMEWORK_PATH} CACHE INTERNAL "") +else() + set(CMAKE_FRAMEWORK_PATH + ${CMAKE_DEVELOPER_ROOT}/Library/PrivateFrameworks + ${CMAKE_OSX_SYSROOT_INT}/System/Library/Frameworks + ${CMAKE_FRAMEWORK_PATH} CACHE INTERNAL "") +endif() + +# By default, search both the specified iOS SDK and the remainder of the host filesystem. +if(NOT CMAKE_FIND_ROOT_PATH_MODE_PROGRAM) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH CACHE INTERNAL "") +endif() +if(NOT CMAKE_FIND_ROOT_PATH_MODE_LIBRARY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH CACHE INTERNAL "") +endif() +if(NOT CMAKE_FIND_ROOT_PATH_MODE_INCLUDE) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH CACHE INTERNAL "") +endif() +if(NOT CMAKE_FIND_ROOT_PATH_MODE_PACKAGE) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH CACHE INTERNAL "") +endif() + +# +# Some helper-macros below to simplify and beautify the CMakeFile +# + +# This little macro lets you set any Xcode specific property. +macro(set_xcode_property TARGET XCODE_PROPERTY XCODE_VALUE XCODE_RELVERSION) + set(XCODE_RELVERSION_I "${XCODE_RELVERSION}") + if(XCODE_RELVERSION_I STREQUAL "All") + set_property(TARGET ${TARGET} PROPERTY XCODE_ATTRIBUTE_${XCODE_PROPERTY} "${XCODE_VALUE}") + else() + set_property(TARGET ${TARGET} PROPERTY XCODE_ATTRIBUTE_${XCODE_PROPERTY}[variant=${XCODE_RELVERSION_I}] "${XCODE_VALUE}") + endif() +endmacro(set_xcode_property) + +# This macro lets you find executable programs on the host system. +macro(find_host_package) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE NEVER) + set(_TOOLCHAIN_IOS ${IOS}) + set(IOS OFF) + find_package(${ARGN}) + set(IOS ${_TOOLCHAIN_IOS}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH) +endmacro(find_host_package) diff --git a/ww/libhv/cmake/libhvConfig.cmake b/ww/libhv/cmake/libhvConfig.cmake new file mode 100644 index 00000000..90881153 --- /dev/null +++ b/ww/libhv/cmake/libhvConfig.cmake @@ -0,0 +1,87 @@ + +include(SelectLibraryConfigurations) + +find_path(libhv_INCLUDE_DIRS hv/hv.h) +message("libhv_INCLUDE_DIRS: " ${libhv_INCLUDE_DIRS}) + +find_library(libhv_LIBRARY_RELEASE NAMES hv PATHS "${CMAKE_CURRENT_LIST_DIR}/../../lib" NO_DEFAULT_PATH) + +find_library(libhv_LIBRARY_DEBUG NAMES hv PATHS "${CMAKE_CURRENT_LIST_DIR}/../../debug/lib" NO_DEFAULT_PATH) +select_library_configurations(libhv) + +if(NOT libhv_LIBRARY) + set(libhv_FOUND FALSE) + set(LIBHV_FOUND FALSE) + return() +endif() + +if(WIN32) + find_file(libhv_LIBRARY_RELEASE_DLL NAMES hv.dll PATHS "${CMAKE_CURRENT_LIST_DIR}/../../bin" NO_DEFAULT_PATH) + find_file(libhv_LIBRARY_DEBUG_DLL NAMES hv.dll PATHS "${CMAKE_CURRENT_LIST_DIR}/../../debug/bin" NO_DEFAULT_PATH) +endif() + +# Manage Release Windows shared +if(EXISTS "${libhv_LIBRARY_RELEASE_DLL}") + add_library(libhv SHARED IMPORTED) + set_target_properties(libhv PROPERTIES + IMPORTED_CONFIGURATIONS Release + IMPORTED_LOCATION_RELEASE "${libhv_LIBRARY_RELEASE_DLL}" + IMPORTED_IMPLIB_RELEASE "${libhv_LIBRARY_RELEASE}" + INTERFACE_INCLUDE_DIRECTORIES "${libhv_INCLUDE_DIRS}" + ) +endif() + +# Manage Debug Windows shared +if(EXISTS "${libhv_LIBRARY_DEBUG_DLL}") + if(EXISTS "${libhv_LIBRARY_RELEASE_DLL}") + #message("Debug mode") + set_target_properties(libhv PROPERTIES + IMPORTED_CONFIGURATIONS "Release;Debug" + IMPORTED_LOCATION_RELEASE "${libhv_LIBRARY_RELEASE_DLL}" + IMPORTED_IMPLIB_RELEASE "${libhv_LIBRARY_RELEASE}" + IMPORTED_LOCATION_DEBUG "${libhv_LIBRARY_DEBUG_DLL}" + IMPORTED_IMPLIB_DEBUG "${libhv_LIBRARY_DEBUG}" + INTERFACE_INCLUDE_DIRECTORIES "${libhv_INCLUDE_DIRS}" + ) + else() + add_library(libhv SHARED IMPORTED) + set_target_properties(libhv PROPERTIES + IMPORTED_CONFIGURATIONS Debug + IMPORTED_LOCATION_DEBUG "${libhv_LIBRARY_DEBUG_DLL}" + IMPORTED_IMPLIB_DEBUG "${libhv_LIBRARY_DEBUG}" + INTERFACE_INCLUDE_DIRECTORIES "${libhv_INCLUDE_DIRS}" + ) + endif() +endif() + +# Manage Release Windows static and Linux shared/static +if((NOT EXISTS "${libhv_LIBRARY_RELEASE_DLL}") AND (EXISTS "${libhv_LIBRARY_RELEASE}")) + add_library(libhv UNKNOWN IMPORTED) + set_target_properties(libhv PROPERTIES + IMPORTED_CONFIGURATIONS Release + IMPORTED_LOCATION_RELEASE "${libhv_LIBRARY_RELEASE}" + INTERFACE_INCLUDE_DIRECTORIES "${libhv_INCLUDE_DIRS}" + ) +endif() + +# Manage Debug Windows static and Linux shared/static +if((NOT EXISTS "${libhv_LIBRARY_DEBUG_DLL}") AND (EXISTS "${libhv_LIBRARY_DEBUG}")) + if(EXISTS "${libhv_LIBRARY_RELEASE}") + set_target_properties(libhv PROPERTIES + IMPORTED_CONFIGURATIONS "Release;Debug" + IMPORTED_LOCATION_RELEASE "${libhv_LIBRARY_RELEASE}" + IMPORTED_LOCATION_DEBUG "${libhv_LIBRARY_DEBUG}" + INTERFACE_INCLUDE_DIRECTORIES "${libhv_INCLUDE_DIRS}" + ) + else() + add_library(libhv UNKNOWN IMPORTED) + set_target_properties(libhv PROPERTIES + IMPORTED_CONFIGURATIONS Debug + IMPORTED_LOCATION_DEBUG "${libhv_LIBRARY_DEBUG}" + INTERFACE_INCLUDE_DIRECTORIES "${libhv_INCLUDE_DIRS}" + ) + endif() +endif() + +set(libhv_FOUND TRUE) +set(LIBHV_FOUND TRUE) diff --git a/ww/libhv/cmake/utils.cmake b/ww/libhv/cmake/utils.cmake new file mode 100644 index 00000000..38ff5c9f --- /dev/null +++ b/ww/libhv/cmake/utils.cmake @@ -0,0 +1,42 @@ +include(CheckIncludeFiles) +macro(check_header header) + string(TOUPPER ${header} str1) + string(REGEX REPLACE "[/.]" "_" str2 ${str1}) + set(str3 HAVE_${str2}) + check_include_files(${header} ${str3}) + if (${str3}) + set(${str3} 1) + else() + set(${str3} 0) + endif() +endmacro() + +include(CheckSymbolExists) +macro(check_function function header) + string(TOUPPER ${function} str1) + set(str2 HAVE_${str1}) + check_symbol_exists(${function} ${header} ${str2}) + if (${str2}) + set(${str2} 1) + else() + set(${str2} 0) + endif() +endmacro() + +macro(list_source_directories srcs) + unset(tmp) + foreach(dir ${ARGN}) + aux_source_directory(${dir} tmp) + endforeach() + set(${srcs} ${tmp}) + list(FILTER ${srcs} EXCLUDE REGEX ".*_test\\.c") +endmacro() + +macro(glob_headers_and_sources files) + unset(tmp) + foreach(dir ${ARGN}) + file(GLOB tmp ${dir}/*.h ${dir}/*.c ${dir}/*.hpp ${dir}/*.cpp) + list(APPEND ${files} ${tmp}) + endforeach() + list(FILTER ${files} EXCLUDE REGEX ".*_test\\.c") +endmacro() diff --git a/ww/libhv/cmake/vars.cmake b/ww/libhv/cmake/vars.cmake new file mode 100644 index 00000000..af95f17b --- /dev/null +++ b/ww/libhv/cmake/vars.cmake @@ -0,0 +1,48 @@ +# see Makefile.vars + +set(BASE_HEADERS + base/hplatform.h + base/hdef.h + base/hatomic.h + base/herr.h + base/htime.h + base/hmath.h + base/hbase.h + base/hversion.h + base/hsysinfo.h + base/hproc.h + base/hthread.h + base/hmutex.h + base/hsocket.h + base/hlog.h + base/hbuf.h + base/hmain.h + base/hendian.h +) + + +set(EVENT_HEADERS + event/hloop.h + event/nlog.h +) + +set(UTIL_HEADERS + util/base64.h + util/md5.h + util/sha1.h +) + + + +set(PROTOCOL_HEADERS + protocol/icmp.h + protocol/dns.h + protocol/ftp.h + protocol/smtp.h +) + + +set(MQTT_HEADERS + mqtt/mqtt_protocol.h + mqtt/mqtt_client.h +) diff --git a/ww/libhv/event/README.md b/ww/libhv/event/README.md new file mode 100644 index 00000000..d9283428 --- /dev/null +++ b/ww/libhv/event/README.md @@ -0,0 +1,20 @@ +## 目录结构 + +``` +. +├── hloop.h 事件循环模块对外头文件 +├── hevent.h 事件结构体定义 +├── nlog.h 网络日志 +├── unpack.h 拆包 +├── rudp.h 可靠UDP +├── iowatcher.h IO多路复用统一抽象接口 +├── select.c EVENT_SELECT实现 +├── poll.c EVENT_POLL实现 +├── epoll.c EVENT_EPOLL实现 (for OS_LINUX) +├── iocp.c EVENT_IOCP实现 (for OS_WIN) +├── kqueue.c EVENT_KQUEUE实现(for OS_BSD/OS_MAC) +├── evport.c EVENT_PORT实现 (for OS_SOLARIS) +├── nio.c 非阻塞IO +└── overlapio.c 重叠IO + +``` diff --git a/ww/libhv/event/epoll.c b/ww/libhv/event/epoll.c new file mode 100644 index 00000000..5fc6871e --- /dev/null +++ b/ww/libhv/event/epoll.c @@ -0,0 +1,145 @@ +#include "iowatcher.h" + +#ifdef EVENT_EPOLL +#include "hplatform.h" +#include "hdef.h" +#include "hevent.h" + +#ifdef OS_WIN +#include "wepoll/wepoll.h" +typedef HANDLE epoll_handle_t; +#else +#include +typedef int epoll_handle_t; +#define epoll_close(epfd) close(epfd) +#endif + +#include "array.h" +#define EVENTS_INIT_SIZE 64 +ARRAY_DECL(struct epoll_event, events); + +typedef struct epoll_ctx_s { + epoll_handle_t epfd; + struct events events; +} epoll_ctx_t; + +int iowatcher_init(hloop_t* loop) { + if (loop->iowatcher) return 0; + epoll_ctx_t* epoll_ctx; + HV_ALLOC_SIZEOF(epoll_ctx); + epoll_ctx->epfd = epoll_create(EVENTS_INIT_SIZE); + events_init(&epoll_ctx->events, EVENTS_INIT_SIZE); + loop->iowatcher = epoll_ctx; + return 0; +} + +int iowatcher_cleanup(hloop_t* loop) { + if (loop->iowatcher == NULL) return 0; + epoll_ctx_t* epoll_ctx = (epoll_ctx_t*)loop->iowatcher; + epoll_close(epoll_ctx->epfd); + events_cleanup(&epoll_ctx->events); + HV_FREE(loop->iowatcher); + return 0; +} + +int iowatcher_add_event(hloop_t* loop, int fd, int events) { + if (loop->iowatcher == NULL) { + iowatcher_init(loop); + } + epoll_ctx_t* epoll_ctx = (epoll_ctx_t*)loop->iowatcher; + hio_t* io = loop->ios.ptr[fd]; + + struct epoll_event ee; + memset(&ee, 0, sizeof(ee)); + ee.data.fd = fd; + // pre events + if (io->events & HV_READ) { + ee.events |= EPOLLIN; + } + if (io->events & HV_WRITE) { + ee.events |= EPOLLOUT; + } + // now events + if (events & HV_READ) { + ee.events |= EPOLLIN; + } + if (events & HV_WRITE) { + ee.events |= EPOLLOUT; + } + int op = io->events == 0 ? EPOLL_CTL_ADD : EPOLL_CTL_MOD; + epoll_ctl(epoll_ctx->epfd, op, fd, &ee); + if (op == EPOLL_CTL_ADD) { + if (epoll_ctx->events.size == epoll_ctx->events.maxsize) { + events_double_resize(&epoll_ctx->events); + } + epoll_ctx->events.size++; + } + return 0; +} + +int iowatcher_del_event(hloop_t* loop, int fd, int events) { + epoll_ctx_t* epoll_ctx = (epoll_ctx_t*)loop->iowatcher; + if (epoll_ctx == NULL) return 0; + hio_t* io = loop->ios.ptr[fd]; + + struct epoll_event ee; + memset(&ee, 0, sizeof(ee)); + ee.data.fd = fd; + // pre events + if (io->events & HV_READ) { + ee.events |= EPOLLIN; + } + if (io->events & HV_WRITE) { + ee.events |= EPOLLOUT; + } + // now events + if (events & HV_READ) { + ee.events &= ~EPOLLIN; + } + if (events & HV_WRITE) { + ee.events &= ~EPOLLOUT; + } + int op = ee.events == 0 ? EPOLL_CTL_DEL : EPOLL_CTL_MOD; + epoll_ctl(epoll_ctx->epfd, op, fd, &ee); + if (op == EPOLL_CTL_DEL) { + epoll_ctx->events.size--; + } + return 0; +} + +int iowatcher_poll_events(hloop_t* loop, int timeout) { + epoll_ctx_t* epoll_ctx = (epoll_ctx_t*)loop->iowatcher; + if (epoll_ctx == NULL) return 0; + if (epoll_ctx->events.size == 0) return 0; + int nepoll = epoll_wait(epoll_ctx->epfd, epoll_ctx->events.ptr, epoll_ctx->events.size, timeout); + if (nepoll < 0) { + if (errno == EINTR) { + return 0; + } + perror("epoll"); + return nepoll; + } + if (nepoll == 0) return 0; + int nevents = 0; + for (int i = 0; i < epoll_ctx->events.size; ++i) { + struct epoll_event* ee = epoll_ctx->events.ptr + i; + int fd = ee->data.fd; + uint32_t revents = ee->events; + if (revents) { + ++nevents; + hio_t* io = loop->ios.ptr[fd]; + if (io) { + if (revents & (EPOLLIN | EPOLLHUP | EPOLLERR)) { + io->revents |= HV_READ; + } + if (revents & (EPOLLOUT | EPOLLHUP | EPOLLERR)) { + io->revents |= HV_WRITE; + } + EVENT_PENDING(io); + } + } + if (nevents == nepoll) break; + } + return nevents; +} +#endif diff --git a/ww/libhv/event/evport.c b/ww/libhv/event/evport.c new file mode 100644 index 00000000..8976dec7 --- /dev/null +++ b/ww/libhv/event/evport.c @@ -0,0 +1,137 @@ +#include "iowatcher.h" + +#ifdef EVENT_PORT + +#include "hplatform.h" +#include "hdef.h" +#include "hevent.h" + +#include + +#define EVENTS_INIT_SIZE 64 + +typedef struct evport_ctx_s { + int port; + int capacity; + int nevents; + port_event_t* events; +} evport_ctx_t; + +static void evport_ctx_resize(evport_ctx_t* evport_ctx, int size) { + int bytes = sizeof(port_event_t) * size; + int oldbytes = sizeof(port_event_t) * evport_ctx->capacity; + evport_ctx->events = (port_event_t*)hv_realloc(evport_ctx->events, bytes, oldbytes); + evport_ctx->capacity = size; +} + +int iowatcher_init(hloop_t* loop) { + if (loop->iowatcher) return 0; + evport_ctx_t* evport_ctx; + HV_ALLOC_SIZEOF(evport_ctx); + evport_ctx->port = port_create(); + evport_ctx->capacity = EVENTS_INIT_SIZE; + evport_ctx->nevents = 0; + int bytes = sizeof(port_event_t) * evport_ctx->capacity; + HV_ALLOC(evport_ctx->events, bytes); + loop->iowatcher = evport_ctx; + return 0; +} + +int iowatcher_cleanup(hloop_t* loop) { + if (loop->iowatcher == NULL) return 0; + evport_ctx_t* evport_ctx = (evport_ctx_t*)loop->iowatcher; + close(evport_ctx->port); + HV_FREE(evport_ctx->events); + HV_FREE(loop->iowatcher); + return 0; +} + +int iowatcher_add_event(hloop_t* loop, int fd, int events) { + if (loop->iowatcher == NULL) { + iowatcher_init(loop); + } + evport_ctx_t* evport_ctx = (evport_ctx_t*)loop->iowatcher; + hio_t* io = loop->ios.ptr[fd]; + + int evport_events = 0; + if (io->events & HV_READ) { + evport_events |= POLLIN; + } + if (io->events & HV_WRITE) { + evport_events |= POLLOUT; + } + if (events & HV_READ) { + evport_events |= POLLIN; + } + if (events & HV_WRITE) { + evport_events |= POLLOUT; + } + port_associate(evport_ctx->port, PORT_SOURCE_FD, fd, evport_events, NULL); + if (io->events == 0) { + if (evport_ctx->nevents == evport_ctx->capacity) { + evport_ctx_resize(evport_ctx, evport_ctx->capacity * 2); + } + ++evport_ctx->nevents; + } + return 0; +} + +int iowatcher_del_event(hloop_t* loop, int fd, int events) { + evport_ctx_t* evport_ctx = (evport_ctx_t*)loop->iowatcher; + if (evport_ctx == NULL) return 0; + hio_t* io = loop->ios.ptr[fd]; + + int evport_events = 0; + if (io->events & HV_READ) { + evport_events |= POLLIN; + } + if (io->events & HV_WRITE) { + evport_events |= POLLOUT; + } + if (events & HV_READ) { + evport_events &= ~POLLIN; + } + if (events & HV_WRITE) { + evport_events &= ~POLLOUT; + } + if (evport_events == 0) { + port_dissociate(evport_ctx->port, PORT_SOURCE_FD, fd); + --evport_ctx->nevents; + } else { + port_associate(evport_ctx->port, PORT_SOURCE_FD, fd, evport_events, NULL); + } + return 0; +} + +int iowatcher_poll_events(hloop_t* loop, int timeout) { + evport_ctx_t* evport_ctx = (evport_ctx_t*)loop->iowatcher; + if (evport_ctx == NULL) return 0; + struct timespec ts, *tp; + if (timeout == INFINITE) { + tp = NULL; + } else { + ts.tv_sec = timeout / 1000; + ts.tv_nsec = (timeout % 1000) * 1000000; + tp = &ts; + } + unsigned nevents = 1; + port_getn(evport_ctx->port, evport_ctx->events, evport_ctx->capacity, &nevents, tp); + for (int i = 0; i < nevents; ++i) { + int fd = evport_ctx->events[i].portev_object; + int revents = evport_ctx->events[i].portev_events; + hio_t* io = loop->ios.ptr[fd]; + if (io) { + if (revents & POLLIN) { + io->revents |= HV_READ; + } + if (revents & POLLOUT) { + io->revents |= HV_WRITE; + } + EVENT_PENDING(io); + } + // Upon retrieval, the event object is no longer associated with the port. + iowatcher_add_event(loop, fd, io->events); + } + return nevents; +} +#endif diff --git a/ww/libhv/event/hevent.c b/ww/libhv/event/hevent.c new file mode 100644 index 00000000..52948527 --- /dev/null +++ b/ww/libhv/event/hevent.c @@ -0,0 +1,830 @@ +#include "hevent.h" +#include "hsocket.h" +#include "hatomic.h" +#include "hlog.h" +#include "herr.h" + +#include "unpack.h" + +uint64_t hloop_next_event_id() { + static hatomic_t s_id = HATOMIC_VAR_INIT(0); + return ++s_id; +} + +uint32_t hio_next_id() { + static hatomic_t s_id = HATOMIC_VAR_INIT(0); + return ++s_id; +} + +static void fill_io_type(hio_t* io) { + int type = 0; + socklen_t optlen = sizeof(int); + int ret = getsockopt(io->fd, SOL_SOCKET, SO_TYPE, (char*)&type, &optlen); + printd("getsockopt SO_TYPE fd=%d ret=%d type=%d errno=%d\n", io->fd, ret, type, socket_errno()); + if (ret == 0) { + switch (type) { + case SOCK_STREAM: io->io_type = HIO_TYPE_TCP; break; + case SOCK_DGRAM: io->io_type = HIO_TYPE_UDP; break; + case SOCK_RAW: io->io_type = HIO_TYPE_IP; break; + default: io->io_type = HIO_TYPE_SOCKET; break; + } + } + else if (socket_errno() == ENOTSOCK) { + switch (io->fd) { + case 0: io->io_type = HIO_TYPE_STDIN; break; + case 1: io->io_type = HIO_TYPE_STDOUT; break; + case 2: io->io_type = HIO_TYPE_STDERR; break; + default: io->io_type = HIO_TYPE_FILE; break; + } + } + else { + io->io_type = HIO_TYPE_TCP; + } +} + +static void hio_socket_init(hio_t* io) { + if ((io->io_type & HIO_TYPE_SOCK_DGRAM) || (io->io_type & HIO_TYPE_SOCK_RAW)) { + // NOTE: sendto multiple peeraddr cannot use io->write_queue + blocking(io->fd); + } + else { + nonblocking(io->fd); + } + // fill io->localaddr io->peeraddr + if (io->localaddr == NULL) { + HV_ALLOC(io->localaddr, sizeof(sockaddr_u)); + } + if (io->peeraddr == NULL) { + HV_ALLOC(io->peeraddr, sizeof(sockaddr_u)); + } + socklen_t addrlen = sizeof(sockaddr_u); + int ret = getsockname(io->fd, io->localaddr, &addrlen); + printd("getsockname fd=%d ret=%d errno=%d\n", io->fd, ret, socket_errno()); + // NOTE: udp peeraddr set by recvfrom/sendto + if (io->io_type & HIO_TYPE_SOCK_STREAM) { + addrlen = sizeof(sockaddr_u); + ret = getpeername(io->fd, io->peeraddr, &addrlen); + printd("getpeername fd=%d ret=%d errno=%d\n", io->fd, ret, socket_errno()); + } +} + +void hio_init(hio_t* io) { + // alloc localaddr,peeraddr when hio_socket_init + /* + if (io->localaddr == NULL) { + HV_ALLOC(io->localaddr, sizeof(sockaddr_u)); + } + if (io->peeraddr == NULL) { + HV_ALLOC(io->peeraddr, sizeof(sockaddr_u)); + } + */ + + // write_queue init when hwrite try_write failed + // write_queue_init(&io->write_queue, 4); + + hrecursive_mutex_init(&io->write_mutex); +} + +void hio_ready(hio_t* io) { + if (io->ready) return; + // flags + io->ready = 1; + io->connected = 0; + io->closed = 0; + io->accept = io->connect = io->connectex = 0; + io->recv = io->send = 0; + io->recvfrom = io->sendto = 0; + io->close = 0; + // public: + io->id = hio_next_id(); + io->io_type = HIO_TYPE_UNKNOWN; + io->error = 0; + io->events = io->revents = 0; + io->last_read_hrtime = io->last_write_hrtime = io->loop->cur_hrtime; + // readbuf + io->alloced_readbuf = 0; + hio_use_loop_readbuf(io); + io->readbuf.head = io->readbuf.tail = 0; + io->read_flags = 0; + io->read_until_length = 0; + io->max_read_bufsize = MAX_READ_BUFSIZE; + io->small_readbytes_cnt = 0; + // write_queue + io->write_bufsize = 0; + io->max_write_bufsize = MAX_WRITE_BUFSIZE; + // callbacks + io->read_cb = NULL; + io->write_cb = NULL; + io->close_cb = NULL; + io->accept_cb = NULL; + io->connect_cb = NULL; + // timers + io->connect_timeout = 0; + io->connect_timer = NULL; + io->close_timeout = 0; + io->close_timer = NULL; + io->read_timeout = 0; + io->read_timer = NULL; + io->write_timeout = 0; + io->write_timer = NULL; + io->keepalive_timeout = 0; + io->keepalive_timer = NULL; + io->heartbeat_interval = 0; + io->heartbeat_fn = NULL; + io->heartbeat_timer = NULL; + // private: +#if defined(EVENT_POLL) || defined(EVENT_KQUEUE) + io->event_index[0] = io->event_index[1] = -1; +#endif +#ifdef EVENT_IOCP + io->hovlp = NULL; +#endif + + // io_type + fill_io_type(io); + if (io->io_type & HIO_TYPE_SOCKET) { + hio_socket_init(io); + } + +#if WITH_RUDP + if ((io->io_type & HIO_TYPE_SOCK_DGRAM) || (io->io_type & HIO_TYPE_SOCK_RAW)) { + rudp_init(&io->rudp); + } +#endif +} + +void hio_done(hio_t* io) { + if (!io->ready) return; + io->ready = 0; + + hio_del(io, HV_RDWR); + + // readbuf + hio_free_readbuf(io); + + // write_queue + offset_buf_t* pbuf = NULL; + hrecursive_mutex_lock(&io->write_mutex); + while (!write_queue_empty(&io->write_queue)) { + pbuf = write_queue_front(&io->write_queue); +#if defined(OS_LINUX) && defined(HAVE_PIPE) + if (pbuf->base != NULL) HV_FREE(pbuf->base); +#else + HV_FREE(pbuf->base); +#endif + write_queue_pop_front(&io->write_queue); + } + write_queue_cleanup(&io->write_queue); + hrecursive_mutex_unlock(&io->write_mutex); + +#if WITH_RUDP + if ((io->io_type & HIO_TYPE_SOCK_DGRAM) || (io->io_type & HIO_TYPE_SOCK_RAW)) { + rudp_cleanup(&io->rudp); + } +#endif +} + +void hio_free(hio_t* io) { + if (io == NULL || io->destroy) return; + io->destroy = 1; + hio_close(io); + hrecursive_mutex_destroy(&io->write_mutex); + HV_FREE(io->localaddr); + HV_FREE(io->peeraddr); + HV_FREE(io); +} + +bool hio_is_opened(hio_t* io) { + if (io == NULL) return false; + return io->ready == 1 && io->closed == 0; +} + +bool hio_is_connected(hio_t* io) { + if (io == NULL) return false; + return io->ready == 1 && io->connected == 1 && io->closed == 0; +} + +bool hio_is_closed(hio_t* io) { + if (io == NULL) return true; + return io->ready == 0 && io->closed == 1; +} + +uint32_t hio_id(hio_t* io) { + return io->id; +} + +int hio_fd(hio_t* io) { + return io->fd; +} + +hio_type_e hio_type(hio_t* io) { + return io->io_type; +} + +int hio_error(hio_t* io) { + return io->error; +} + +int hio_events(hio_t* io) { + return io->events; +} + +int hio_revents(hio_t* io) { + return io->revents; +} + +struct sockaddr* hio_localaddr(hio_t* io) { + return io->localaddr; +} + +struct sockaddr* hio_peeraddr(hio_t* io) { + return io->peeraddr; +} + +haccept_cb hio_getcb_accept(hio_t* io) { + return io->accept_cb; +} + +hconnect_cb hio_getcb_connect(hio_t* io) { + return io->connect_cb; +} + +hread_cb hio_getcb_read(hio_t* io) { + return io->read_cb; +} + +hwrite_cb hio_getcb_write(hio_t* io) { + return io->write_cb; +} + +hclose_cb hio_getcb_close(hio_t* io) { + return io->close_cb; +} + +void hio_setcb_accept(hio_t* io, haccept_cb accept_cb) { + io->accept_cb = accept_cb; +} + +void hio_setcb_connect(hio_t* io, hconnect_cb connect_cb) { + io->connect_cb = connect_cb; +} + +void hio_setcb_read(hio_t* io, hread_cb read_cb) { + io->read_cb = read_cb; +} + +void hio_setcb_write(hio_t* io, hwrite_cb write_cb) { + io->write_cb = write_cb; +} + +void hio_setcb_close(hio_t* io, hclose_cb close_cb) { + io->close_cb = close_cb; +} + +void hio_accept_cb(hio_t* io) { + /* + char localaddrstr[SOCKADDR_STRLEN] = {0}; + char peeraddrstr[SOCKADDR_STRLEN] = {0}; + printd("accept connfd=%d [%s] <= [%s]\n", io->fd, + SOCKADDR_STR(io->localaddr, localaddrstr), + SOCKADDR_STR(io->peeraddr, peeraddrstr)); + */ + if (io->accept_cb) { + // printd("accept_cb------\n"); + io->accept_cb(io); + // printd("accept_cb======\n"); + } +} + +void hio_connect_cb(hio_t* io) { + /* + char localaddrstr[SOCKADDR_STRLEN] = {0}; + char peeraddrstr[SOCKADDR_STRLEN] = {0}; + printd("connect connfd=%d [%s] => [%s]\n", io->fd, + SOCKADDR_STR(io->localaddr, localaddrstr), + SOCKADDR_STR(io->peeraddr, peeraddrstr)); + */ + io->connected = 1; + if (io->connect_cb) { + // printd("connect_cb------\n"); + io->connect_cb(io); + // printd("connect_cb======\n"); + } +} + +void hio_handle_read(hio_t* io, void* buf, int readbytes) { +#if WITH_KCP + if (io->io_type == HIO_TYPE_KCP) { + hio_read_kcp(io, buf, readbytes); + io->readbuf.head = io->readbuf.tail = 0; + return; + } +#endif + + const unsigned char* sp = (const unsigned char*)io->readbuf.base + io->readbuf.head; + const unsigned char* ep = (const unsigned char*)buf + readbytes; + + // hio_read + io->readbuf.head = io->readbuf.tail = 0; + hio_read_cb(io, (void*)sp, ep - sp); + + if (io->readbuf.head == io->readbuf.tail) { + io->readbuf.head = io->readbuf.tail = 0; + } + // readbuf autosize + if (io->readbuf.tail == io->readbuf.len) { + if (io->readbuf.head == 0) { + // scale up * 2 + hio_alloc_readbuf(io, io->readbuf.len * 2); + } + else { + hio_memmove_readbuf(io); + } + } + else { + size_t small_size = io->readbuf.len / 2; + if (io->readbuf.tail < small_size && io->small_readbytes_cnt >= 3) { + // scale down / 2 + hio_alloc_readbuf(io, small_size); + } + } +} + +void hio_read_cb(hio_t* io, void* buf, int len) { + if (io->read_flags & HIO_READ_ONCE) { + io->read_flags &= ~HIO_READ_ONCE; + hio_read_stop(io); + } + + if (io->read_cb) { + // printd("read_cb------\n"); + io->read_cb(io, buf, len); + // printd("read_cb======\n"); + } + + // for readbuf autosize + if (hio_is_alloced_readbuf(io) && io->readbuf.len > READ_BUFSIZE_HIGH_WATER) { + size_t small_size = io->readbuf.len / 2; + if (len < small_size) { + ++io->small_readbytes_cnt; + } + else { + io->small_readbytes_cnt = 0; + } + } +} + +void hio_write_cb(hio_t* io, const void* buf, int len) { + if (io->write_cb) { + // printd("write_cb------\n"); + io->write_cb(io, buf, len); + // printd("write_cb======\n"); + } +} + +void hio_close_cb(hio_t* io) { + io->connected = 0; + io->closed = 1; + if (io->close_cb) { + // printd("close_cb------\n"); + io->close_cb(io); + // printd("close_cb======\n"); + } +} + +void hio_set_type(hio_t* io, hio_type_e type) { + io->io_type = type; +} + +void hio_set_localaddr(hio_t* io, struct sockaddr* addr, int addrlen) { + if (io->localaddr == NULL) { + HV_ALLOC(io->localaddr, sizeof(sockaddr_u)); + } + memcpy(io->localaddr, addr, addrlen); +} + +void hio_set_peeraddr(hio_t* io, struct sockaddr* addr, int addrlen) { + if (io->peeraddr == NULL) { + HV_ALLOC(io->peeraddr, sizeof(sockaddr_u)); + } + memcpy(io->peeraddr, addr, addrlen); +} + +void hio_del_connect_timer(hio_t* io) { + if (io->connect_timer) { + htimer_del(io->connect_timer); + io->connect_timer = NULL; + io->connect_timeout = 0; + } +} + +void hio_del_close_timer(hio_t* io) { + if (io->close_timer) { + htimer_del(io->close_timer); + io->close_timer = NULL; + io->close_timeout = 0; + } +} + +void hio_del_read_timer(hio_t* io) { + if (io->read_timer) { + htimer_del(io->read_timer); + io->read_timer = NULL; + io->read_timeout = 0; + } +} + +void hio_del_write_timer(hio_t* io) { + if (io->write_timer) { + htimer_del(io->write_timer); + io->write_timer = NULL; + io->write_timeout = 0; + } +} + +void hio_del_keepalive_timer(hio_t* io) { + if (io->keepalive_timer) { + htimer_del(io->keepalive_timer); + io->keepalive_timer = NULL; + io->keepalive_timeout = 0; + } +} + +void hio_del_heartbeat_timer(hio_t* io) { + if (io->heartbeat_timer) { + htimer_del(io->heartbeat_timer); + io->heartbeat_timer = NULL; + io->heartbeat_interval = 0; + io->heartbeat_fn = NULL; + } +} + +void hio_set_connect_timeout(hio_t* io, int timeout_ms) { + io->connect_timeout = timeout_ms; +} + +void hio_set_close_timeout(hio_t* io, int timeout_ms) { + io->close_timeout = timeout_ms; +} + +static void __read_timeout_cb(htimer_t* timer) { + hio_t* io = (hio_t*)timer->privdata; + uint64_t inactive_ms = (io->loop->cur_hrtime - io->last_read_hrtime) / 1000; + if (inactive_ms + 100 < io->read_timeout) { + htimer_reset(io->read_timer, io->read_timeout - inactive_ms); + } + else { + if (io->io_type & HIO_TYPE_SOCKET) { + char localaddrstr[SOCKADDR_STRLEN] = {0}; + char peeraddrstr[SOCKADDR_STRLEN] = {0}; + hlogw("read timeout [%s] <=> [%s]", SOCKADDR_STR(io->localaddr, localaddrstr), SOCKADDR_STR(io->peeraddr, peeraddrstr)); + } + io->error = ETIMEDOUT; + hio_close(io); + } +} + +void hio_set_read_timeout(hio_t* io, int timeout_ms) { + if (timeout_ms <= 0) { + // del + hio_del_read_timer(io); + return; + } + + if (io->read_timer) { + // reset + htimer_reset(io->read_timer, timeout_ms); + } + else { + // add + io->read_timer = htimer_add(io->loop, __read_timeout_cb, timeout_ms, 1); + io->read_timer->privdata = io; + } + io->read_timeout = timeout_ms; +} + +static void __write_timeout_cb(htimer_t* timer) { + hio_t* io = (hio_t*)timer->privdata; + uint64_t inactive_ms = (io->loop->cur_hrtime - io->last_write_hrtime) / 1000; + if (inactive_ms + 100 < io->write_timeout) { + htimer_reset(io->write_timer, io->write_timeout - inactive_ms); + } + else { + if (io->io_type & HIO_TYPE_SOCKET) { + char localaddrstr[SOCKADDR_STRLEN] = {0}; + char peeraddrstr[SOCKADDR_STRLEN] = {0}; + hlogw("write timeout [%s] <=> [%s]", SOCKADDR_STR(io->localaddr, localaddrstr), SOCKADDR_STR(io->peeraddr, peeraddrstr)); + } + io->error = ETIMEDOUT; + hio_close(io); + } +} + +void hio_set_write_timeout(hio_t* io, int timeout_ms) { + if (timeout_ms <= 0) { + // del + hio_del_write_timer(io); + return; + } + + if (io->write_timer) { + // reset + htimer_reset(io->write_timer, timeout_ms); + } + else { + // add + io->write_timer = htimer_add(io->loop, __write_timeout_cb, timeout_ms, 1); + io->write_timer->privdata = io; + } + io->write_timeout = timeout_ms; +} + +static void __keepalive_timeout_cb(htimer_t* timer) { + hio_t* io = (hio_t*)timer->privdata; + uint64_t last_rw_hrtime = MAX(io->last_read_hrtime, io->last_write_hrtime); + uint64_t inactive_ms = (io->loop->cur_hrtime - last_rw_hrtime) / 1000; + if (inactive_ms + 100 < io->keepalive_timeout) { + htimer_reset(io->keepalive_timer, io->keepalive_timeout - inactive_ms); + } + else { + if (io->io_type & HIO_TYPE_SOCKET) { + char localaddrstr[SOCKADDR_STRLEN] = {0}; + char peeraddrstr[SOCKADDR_STRLEN] = {0}; + hlogw("keepalive timeout [%s] <=> [%s]", SOCKADDR_STR(io->localaddr, localaddrstr), SOCKADDR_STR(io->peeraddr, peeraddrstr)); + } + io->error = ETIMEDOUT; + hio_close(io); + } +} + +void hio_set_keepalive_timeout(hio_t* io, int timeout_ms) { + if (timeout_ms <= 0) { + // del + hio_del_keepalive_timer(io); + return; + } + + if (io->keepalive_timer) { + // reset + htimer_reset(io->keepalive_timer, timeout_ms); + } + else { + // add + io->keepalive_timer = htimer_add(io->loop, __keepalive_timeout_cb, timeout_ms, 1); + io->keepalive_timer->privdata = io; + } + io->keepalive_timeout = timeout_ms; +} + +static void __heartbeat_timer_cb(htimer_t* timer) { + hio_t* io = (hio_t*)timer->privdata; + if (io && io->heartbeat_fn) { + io->heartbeat_fn(io); + } +} + +void hio_set_heartbeat(hio_t* io, int interval_ms, hio_send_heartbeat_fn fn) { + if (interval_ms <= 0) { + // del + hio_del_heartbeat_timer(io); + return; + } + + if (io->heartbeat_timer) { + // reset + htimer_reset(io->heartbeat_timer, interval_ms); + } + else { + // add + io->heartbeat_timer = htimer_add(io->loop, __heartbeat_timer_cb, interval_ms, INFINITE); + io->heartbeat_timer->privdata = io; + } + io->heartbeat_interval = interval_ms; + io->heartbeat_fn = fn; +} + +//-----------------iobuf--------------------------------------------- +void hio_alloc_readbuf(hio_t* io, int len) { + if (len > io->max_read_bufsize) { + hloge("read bufsize > %u, close it!", io->max_read_bufsize); + io->error = ERR_OVER_LIMIT; + hio_close_async(io); + return; + } + if (hio_is_alloced_readbuf(io)) { + io->readbuf.base = (char*)hv_realloc(io->readbuf.base, len, io->readbuf.len); + } + else { + HV_ALLOC(io->readbuf.base, len); + } + io->readbuf.len = len; + io->alloced_readbuf = 1; + io->small_readbytes_cnt = 0; +} + +void hio_free_readbuf(hio_t* io) { + if (hio_is_alloced_readbuf(io)) { + HV_FREE(io->readbuf.base); + io->alloced_readbuf = 0; + // reset to loop->readbuf + io->readbuf.base = io->loop->readbuf.base; + io->readbuf.len = io->loop->readbuf.len; + } +} + +void hio_memmove_readbuf(hio_t* io) { + fifo_buf_t* buf = &io->readbuf; + if (buf->tail == buf->head) { + buf->head = buf->tail = 0; + return; + } + if (buf->tail > buf->head) { + size_t size = buf->tail - buf->head; + // [head, tail] => [0, tail - head] + memmove(buf->base, buf->base + buf->head, size); + buf->head = 0; + buf->tail = size; + } +} + +void hio_set_readbuf(hio_t* io, void* buf, size_t len) { + assert(io && buf && len != 0); + hio_free_readbuf(io); + io->readbuf.base = (char*)buf; + io->readbuf.len = len; + io->readbuf.head = io->readbuf.tail = 0; + io->alloced_readbuf = 0; +} + +hio_readbuf_t* hio_get_readbuf(hio_t* io) { + return &io->readbuf; +} + +void hio_set_max_read_bufsize(hio_t* io, uint32_t size) { + io->max_read_bufsize = size; +} + +void hio_set_max_write_bufsize(hio_t* io, uint32_t size) { + io->max_write_bufsize = size; +} + +size_t hio_write_bufsize(hio_t* io) { + return io->write_bufsize; +} + +int hio_read_once(hio_t* io) { + io->read_flags |= HIO_READ_ONCE; + return hio_read_start(io); +} + +int hio_read_until_length(hio_t* io, unsigned int len) { + if (len == 0) return 0; + if (io->readbuf.tail - io->readbuf.head >= len) { + void* buf = io->readbuf.base + io->readbuf.head; + io->readbuf.head += len; + if (io->readbuf.head == io->readbuf.tail) { + io->readbuf.head = io->readbuf.tail = 0; + } + hio_read_cb(io, buf, len); + return len; + } + io->read_flags = HIO_READ_UNTIL_LENGTH; + io->read_until_length = len; + if (io->readbuf.head > 1024 || io->readbuf.tail - io->readbuf.head < 1024) { + hio_memmove_readbuf(io); + } + // NOTE: prepare readbuf + int need_len = io->readbuf.head + len; + if (hio_is_loop_readbuf(io) || io->readbuf.len < need_len) { + hio_alloc_readbuf(io, need_len); + } + return hio_read_once(io); +} + +int hio_read_remain(hio_t* io) { + int remain = io->readbuf.tail - io->readbuf.head; + if (remain > 0) { + void* buf = io->readbuf.base + io->readbuf.head; + io->readbuf.head = io->readbuf.tail = 0; + hio_read_cb(io, buf, remain); + } + return remain; +} + +//-----------------upstream--------------------------------------------- +// void hio_read_upstream(hio_t* io) { +// hio_t* upstream_io = io->upstream_io; +// if (upstream_io) { +// hio_read(io); +// hio_read(upstream_io); +// } +// } + +// void hio_read_upstream_on_write_complete(hio_t* io, const void* buf, int writebytes) { +// hio_t* upstream_io = io->upstream_io; +// if (upstream_io && hio_write_is_complete(io)) { +// hio_setcb_write(io, NULL); +// hio_read(upstream_io); +// } +// } + +// void hio_write_upstream(hio_t* io, void* buf, int bytes) { +// hio_t* upstream_io = io->upstream_io; +// if (upstream_io) { +// int nwrite = hio_write(upstream_io, buf, bytes); +// // if (!hio_write_is_complete(upstream_io)) { +// if (nwrite >= 0 && nwrite < bytes) { +// hio_read_stop(io); +// hio_setcb_write(upstream_io, hio_read_upstream_on_write_complete); +// } +// } +// } +// #if defined(OS_LINUX) && defined(HAVE_PIPE) + +// void hio_close_upstream(hio_t* io) { +// hio_t* upstream_io = io->upstream_io; +// if(io->pfd_w != 0x0){ +// close(io->pfd_w); +// close(io->pfd_r); +// } +// if (upstream_io) { +// hio_close(upstream_io); +// } +// } + +// static bool hio_setup_pipe(hio_t* io) { +// int fds[2]; +// int r = pipe(fds); +// if (r != 0) +// return false; +// io->pfd_r = fds[0]; +// io->pfd_w = fds[1]; +// return true; + +// } + +// void hio_setup_upstream_splice(hio_t* restrict io1, hio_t* restrict io2) { +// io1->upstream_io = io2; +// io2->upstream_io = io1; +// assert (io1->io_type == HIO_TYPE_TCP && io2->io_type == HIO_TYPE_TCP); +// if (!hio_setup_pipe(io1)) +// return; + +// if (!hio_setup_pipe(io2)){ +// close(io1->pfd_w); +// close(io1->pfd_r); +// io1->pfd_w = io1->pfd_r = 0; +// return; +// } +// const int tmp_fd = io1->pfd_w; +// io1->pfd_w = io2->pfd_w; +// io2->pfd_w = tmp_fd; +// } +// #else + +// void hio_close_upstream(hio_t* io) { +// hio_t* upstream_io = io->upstream_io; +// if (upstream_io) { +// hio_close(upstream_io); +// } +// } + +// #endif + +// void hio_setup_upstream(hio_t* restrict io1, hio_t* restrict io2) { +// io1->upstream_io = io2; +// io2->upstream_io = io1; +// } + +// hio_t* hio_get_upstream(hio_t* io) { +// return io->upstream_io; +// } + +// hio_t* hio_setup_tcp_upstream(hio_t* io, const char* host, int port) { +// hio_t* upstream_io = hio_create_socket(io->loop, host, port, HIO_TYPE_TCP, HIO_CLIENT_SIDE); +// if (upstream_io == NULL) return NULL; +// // #if defined(OS_LINUX) && defined(HAVE_PIPE) +// // hio_setup_upstream_splice(io, upstream_io); +// // #else +// hio_setup_upstream(io, upstream_io); +// // #endif + +// hio_setcb_read(io, hio_write_upstream); +// hio_setcb_read(upstream_io, hio_write_upstream); + +// hio_setcb_close(io, hio_close_upstream); +// hio_setcb_close(upstream_io, hio_close_upstream); +// hio_setcb_connect(upstream_io, hio_read_upstream); +// hio_connect(upstream_io); +// return upstream_io; +// } + +// hio_t* hio_setup_udp_upstream(hio_t* io, const char* host, int port) { +// hio_t* upstream_io = hio_create_socket(io->loop, host, port, HIO_TYPE_UDP, HIO_CLIENT_SIDE); +// if (upstream_io == NULL) return NULL; +// hio_setup_upstream(io, upstream_io); +// hio_setcb_read(io, hio_write_upstream); +// hio_setcb_read(upstream_io, hio_write_upstream); +// hio_read_upstream(io); +// return upstream_io; +// } diff --git a/ww/libhv/event/hevent.h b/ww/libhv/event/hevent.h new file mode 100644 index 00000000..a152c4f5 --- /dev/null +++ b/ww/libhv/event/hevent.h @@ -0,0 +1,285 @@ +#ifndef HV_EVENT_H_ +#define HV_EVENT_H_ + +#include "hloop.h" +#include "iowatcher.h" +#include "rudp.h" + +#include "hbuf.h" +#include "hmutex.h" + +#include "array.h" +#include "list.h" +#include "heap.h" +#include "queue.h" + +#define HLOOP_READ_BUFSIZE (1U << 15) // 32K +#define READ_BUFSIZE_HIGH_WATER (1U << 20) // 1M +#define WRITE_BUFSIZE_HIGH_WATER (1U << 23) // 8M +#define MAX_READ_BUFSIZE (1U << 24) // 16M +#define MAX_WRITE_BUFSIZE (1U << 24) // 16M + +// hio_read_flags +#define HIO_READ_ONCE 0x1 +#define HIO_READ_UNTIL_LENGTH 0x2 +#define HIO_READ_UNTIL_DELIM 0x4 + +ARRAY_DECL(hio_t*, io_array); +QUEUE_DECL(hevent_t, event_queue); + +struct hloop_s { + uint32_t flags; + hloop_status_e status; + uint64_t start_ms; // ms + uint64_t start_hrtime; // us + uint64_t end_hrtime; + uint64_t cur_hrtime; + uint64_t loop_cnt; + long pid; + long tid; + void* userdata; +//private: + // events + uint32_t intern_nevents; + uint32_t nactives; + uint32_t npendings; + // pendings: with priority as array.index + hevent_t* pendings[HEVENT_PRIORITY_SIZE]; + // idles + struct list_head idles; + uint32_t nidles; + // timers + struct heap timers; // monotonic time + struct heap realtimers; // realtime + uint32_t ntimers; + // ios: with fd as array.index + struct io_array ios; + uint32_t nios; + // one loop per thread, so one readbuf per loop is OK. + hbuf_t readbuf; + void* iowatcher; + // custom_events + int eventfds[2]; + event_queue custom_events; + hmutex_t custom_events_mutex; +}; + +uint64_t hloop_next_event_id(); + +struct hidle_s { + HEVENT_FIELDS + uint32_t repeat; +//private: + struct list_node node; +}; + +#define HTIMER_FIELDS \ + HEVENT_FIELDS \ + uint32_t repeat; \ + uint64_t next_timeout; \ + struct heap_node node; + +struct htimer_s { + HTIMER_FIELDS +}; + +struct htimeout_s { + HTIMER_FIELDS + uint32_t timeout; \ +}; + +struct hperiod_s { + HTIMER_FIELDS + int8_t minute; + int8_t hour; + int8_t day; + int8_t week; + int8_t month; +}; + +QUEUE_DECL(offset_buf_t, write_queue); +// sizeof(struct hio_s)=416 on linux-x64 +struct hio_s { + HEVENT_FIELDS + // flags + unsigned ready :1; + unsigned connected :1; + unsigned closed :1; + unsigned accept :1; + unsigned connect :1; + unsigned connectex :1; // for ConnectEx/DisconnectEx + unsigned recv :1; + unsigned send :1; + unsigned recvfrom :1; + unsigned sendto :1; + unsigned close :1; + unsigned alloced_readbuf :1; // for hio_alloc_readbuf +// public: + hio_type_e io_type; + uint32_t id; // fd cannot be used as unique identifier, so we provide an id + int fd; +// #if defined(OS_LINUX) && defined(HAVE_PIPE) +// int pfd_r; // pipe read file descriptor for splice, (empty by default) +// int pfd_w; // pipe read file descriptor for splice, (empty by default) +// #endif + int error; + int events; + int revents; + struct sockaddr* localaddr; + struct sockaddr* peeraddr; + uint64_t last_read_hrtime; + uint64_t last_write_hrtime; + // read + fifo_buf_t readbuf; + unsigned int read_flags; + // for hio_read_until + union { + unsigned int read_until_length; + unsigned char read_until_delim; + }; + uint32_t max_read_bufsize; + uint32_t small_readbytes_cnt; // for readbuf autosize + // write + struct write_queue write_queue; + hrecursive_mutex_t write_mutex; // lock write and write_queue + uint32_t write_bufsize; + uint32_t max_write_bufsize; + // callbacks + hread_cb read_cb; + hwrite_cb write_cb; + hclose_cb close_cb; + haccept_cb accept_cb; + hconnect_cb connect_cb; + // timers + int connect_timeout; // ms + int close_timeout; // ms + int read_timeout; // ms + int write_timeout; // ms + int keepalive_timeout; // ms + int heartbeat_interval; // ms + hio_send_heartbeat_fn heartbeat_fn; + htimer_t* connect_timer; + htimer_t* close_timer; + htimer_t* read_timer; + htimer_t* write_timer; + htimer_t* keepalive_timer; + htimer_t* heartbeat_timer; + +// private: +#if defined(EVENT_POLL) || defined(EVENT_KQUEUE) + int event_index[2]; // for poll,kqueue +#endif + +#ifdef EVENT_IOCP + void* hovlp; // for iocp/overlapio +#endif + +#if WITH_RUDP + rudp_t rudp; +#if WITH_KCP + kcp_setting_t* kcp_setting; +#endif +#endif +}; +/* + * hio lifeline: + * + * fd => + * hio_get => HV_ALLOC_SIZEOF(io) => hio_init => hio_ready + * + * hio_read => hio_add(HV_READ) => hio_read_cb + * hio_write => hio_add(HV_WRITE) => hio_write_cb + * hio_close => hio_done => hio_del(HV_RDWR) => hio_close_cb + * + * hloop_stop => hloop_free => hio_free => HV_FREE(io) + */ +void hio_init(hio_t* io); +void hio_ready(hio_t* io); +void hio_done(hio_t* io); +void hio_free(hio_t* io); +uint32_t hio_next_id(); + +void hio_accept_cb(hio_t* io); +void hio_connect_cb(hio_t* io); +void hio_handle_read(hio_t* io, void* buf, int readbytes); +void hio_read_cb(hio_t* io, void* buf, int len); +void hio_write_cb(hio_t* io, const void* buf, int len); +void hio_close_cb(hio_t* io); + +void hio_del_connect_timer(hio_t* io); +void hio_del_close_timer(hio_t* io); +void hio_del_read_timer(hio_t* io); +void hio_del_write_timer(hio_t* io); +void hio_del_keepalive_timer(hio_t* io); +void hio_del_heartbeat_timer(hio_t* io); + +static inline void hio_use_loop_readbuf(hio_t* io) { + hloop_t* loop = io->loop; + if (loop->readbuf.len == 0) { + loop->readbuf.len = HLOOP_READ_BUFSIZE; + HV_ALLOC(loop->readbuf.base, loop->readbuf.len); + } + io->readbuf.base = loop->readbuf.base; + io->readbuf.len = loop->readbuf.len; +} +static inline bool hio_is_loop_readbuf(hio_t* io) { + return io->readbuf.base == io->loop->readbuf.base; +} +static inline bool hio_is_alloced_readbuf(hio_t* io) { + return io->alloced_readbuf; +} +void hio_alloc_readbuf(hio_t* io, int len); +void hio_free_readbuf(hio_t* io); +void hio_memmove_readbuf(hio_t* io); + +#define EVENT_ENTRY(p) container_of(p, hevent_t, pending_node) +#define IDLE_ENTRY(p) container_of(p, hidle_t, node) +#define TIMER_ENTRY(p) container_of(p, htimer_t, node) + +#define EVENT_ACTIVE(ev) \ + if (!ev->active) {\ + ev->active = 1;\ + ev->loop->nactives++;\ + }\ + +#define EVENT_INACTIVE(ev) \ + if (ev->active) {\ + ev->active = 0;\ + ev->loop->nactives--;\ + }\ + +#define EVENT_PENDING(ev) \ + do {\ + if (!ev->pending) {\ + ev->pending = 1;\ + ev->loop->npendings++;\ + hevent_t** phead = &ev->loop->pendings[HEVENT_PRIORITY_INDEX(ev->priority)];\ + ev->pending_next = *phead;\ + *phead = (hevent_t*)ev;\ + }\ + } while(0) + +#define EVENT_ADD(loop, ev, cb) \ + do {\ + ev->loop = loop;\ + ev->event_id = hloop_next_event_id();\ + ev->cb = (hevent_cb)cb;\ + EVENT_ACTIVE(ev);\ + } while(0) + +#define EVENT_DEL(ev) \ + do {\ + EVENT_INACTIVE(ev);\ + if (!ev->pending) {\ + HV_FREE(ev);\ + }\ + } while(0) + +#define EVENT_RESET(ev) \ + do {\ + ev->destroy = 0;\ + EVENT_ACTIVE(ev);\ + ev->pending = 0;\ + } while(0) + +#endif // HV_EVENT_H_ diff --git a/ww/libhv/event/hloop.c b/ww/libhv/event/hloop.c new file mode 100644 index 00000000..542d87be --- /dev/null +++ b/ww/libhv/event/hloop.c @@ -0,0 +1,1012 @@ +#include "hloop.h" +#include "hevent.h" +#include "iowatcher.h" + +#include "hdef.h" +#include "hbase.h" +#include "hlog.h" +#include "hmath.h" +#include "htime.h" +#include "hsocket.h" +#include "hthread.h" + +#if defined(OS_UNIX) && HAVE_EVENTFD +#include "sys/eventfd.h" +#endif + +#define HLOOP_PAUSE_TIME 10 // ms +#define HLOOP_MAX_BLOCK_TIME 100 // ms +#define HLOOP_STAT_TIMEOUT 60000 // ms + +#define IO_ARRAY_INIT_SIZE 1024 +#define CUSTOM_EVENT_QUEUE_INIT_SIZE 16 + +#define EVENTFDS_READ_INDEX 0 +#define EVENTFDS_WRITE_INDEX 1 + +static void __hidle_del(hidle_t* idle); +static void __htimer_del(htimer_t* timer); + +static int timers_compare(const struct heap_node* lhs, const struct heap_node* rhs) { + return TIMER_ENTRY(lhs)->next_timeout < TIMER_ENTRY(rhs)->next_timeout; +} + +static int hloop_process_idles(hloop_t* loop) { + int nidles = 0; + struct list_node* node = loop->idles.next; + hidle_t* idle = NULL; + while (node != &loop->idles) { + idle = IDLE_ENTRY(node); + node = node->next; + if (idle->repeat != INFINITE) { + --idle->repeat; + } + if (idle->repeat == 0) { + // NOTE: Just mark it as destroy and remove from list. + // Real deletion occurs after hloop_process_pendings. + __hidle_del(idle); + } + EVENT_PENDING(idle); + ++nidles; + } + return nidles; +} + +static int __hloop_process_timers(struct heap* timers, uint64_t timeout) { + int ntimers = 0; + htimer_t* timer = NULL; + while (timers->root) { + // NOTE: root of minheap has min timeout. + timer = TIMER_ENTRY(timers->root); + if (timer->next_timeout > timeout) { + break; + } + if (timer->repeat != INFINITE) { + --timer->repeat; + } + if (timer->repeat == 0) { + // NOTE: Just mark it as destroy and remove from heap. + // Real deletion occurs after hloop_process_pendings. + __htimer_del(timer); + } + else { + // NOTE: calc next timeout, then re-insert heap. + heap_dequeue(timers); + if (timer->event_type == HEVENT_TYPE_TIMEOUT) { + while (timer->next_timeout <= timeout) { + timer->next_timeout += (uint64_t)((htimeout_t*)timer)->timeout * 1000; + } + } + else if (timer->event_type == HEVENT_TYPE_PERIOD) { + hperiod_t* period = (hperiod_t*)timer; + timer->next_timeout = (uint64_t)cron_next_timeout(period->minute, period->hour, period->day, + period->week, period->month) * 1000000; + } + heap_insert(timers, &timer->node); + } + EVENT_PENDING(timer); + ++ntimers; + } + return ntimers; +} + +static int hloop_process_timers(hloop_t* loop) { + uint64_t now = hloop_now_us(loop); + int ntimers = __hloop_process_timers(&loop->timers, loop->cur_hrtime); + ntimers += __hloop_process_timers(&loop->realtimers, now); + return ntimers; +} + +static int hloop_process_ios(hloop_t* loop, int timeout) { + // That is to call IO multiplexing function such as select, poll, epoll, etc. + int nevents = iowatcher_poll_events(loop, timeout); + if (nevents < 0) { + hlogd("poll_events error=%d", -nevents); + } + return nevents < 0 ? 0 : nevents; +} + +static int hloop_process_pendings(hloop_t* loop) { + if (loop->npendings == 0) return 0; + + hevent_t* cur = NULL; + hevent_t* next = NULL; + int ncbs = 0; + // NOTE: invoke event callback from high to low sorted by priority. + for (int i = HEVENT_PRIORITY_SIZE-1; i >= 0; --i) { + cur = loop->pendings[i]; + while (cur) { + next = cur->pending_next; + if (cur->pending) { + if (cur->active && cur->cb) { + cur->cb(cur); + ++ncbs; + } + cur->pending = 0; + // NOTE: Now we can safely delete event marked as destroy. + if (cur->destroy) { + EVENT_DEL(cur); + } + } + cur = next; + } + loop->pendings[i] = NULL; + } + loop->npendings = 0; + return ncbs; +} + +// hloop_process_ios -> hloop_process_timers -> hloop_process_idles -> hloop_process_pendings +int hloop_process_events(hloop_t* loop, int timeout_ms) { + // ios -> timers -> idles + int nios, ntimers, nidles; + nios = ntimers = nidles = 0; + + // calc blocktime + int32_t blocktime_ms = timeout_ms; + if (loop->ntimers) { + hloop_update_time(loop); + int64_t blocktime_us = blocktime_ms * 1000; + if (loop->timers.root) { + int64_t min_timeout = TIMER_ENTRY(loop->timers.root)->next_timeout - loop->cur_hrtime; + blocktime_us = MIN(blocktime_us, min_timeout); + } + if (loop->realtimers.root) { + int64_t min_timeout = TIMER_ENTRY(loop->realtimers.root)->next_timeout - hloop_now_us(loop); + blocktime_us = MIN(blocktime_us, min_timeout); + } + if (blocktime_us < 0) goto process_timers; + blocktime_ms = blocktime_us / 1000 + 1; + blocktime_ms = MIN(blocktime_ms, timeout_ms); + } + + if (loop->nios) { + nios = hloop_process_ios(loop, blocktime_ms); + } else { + hv_msleep(blocktime_ms); + } + hloop_update_time(loop); + // wakeup by hloop_stop + if (loop->status == HLOOP_STATUS_STOP) { + return 0; + } + +process_timers: + if (loop->ntimers) { + ntimers = hloop_process_timers(loop); + } + + int npendings = loop->npendings; + if (npendings == 0) { + if (loop->nidles) { + nidles= hloop_process_idles(loop); + } + } + int ncbs = hloop_process_pendings(loop); + // printd("blocktime=%d nios=%d/%u ntimers=%d/%u nidles=%d/%u nactives=%d npendings=%d ncbs=%d\n", + // blocktime, nios, loop->nios, ntimers, loop->ntimers, nidles, loop->nidles, + // loop->nactives, npendings, ncbs); + return ncbs; +} + +static void hloop_stat_timer_cb(htimer_t* timer) { + hloop_t* loop = timer->loop; + // hlog_set_level(LOG_LEVEL_DEBUG); + hlogd("[loop] pid=%ld tid=%ld uptime=%lluus cnt=%llu nactives=%u nios=%u ntimers=%u nidles=%u", + loop->pid, loop->tid, + (unsigned long long)loop->cur_hrtime - loop->start_hrtime, + (unsigned long long)loop->loop_cnt, + loop->nactives, loop->nios, loop->ntimers, loop->nidles); +} + +static void eventfd_read_cb(hio_t* io, void* buf, int readbytes) { + hloop_t* loop = io->loop; + hevent_t* pev = NULL; + hevent_t ev; + uint64_t count = readbytes; +#if defined(OS_UNIX) && HAVE_EVENTFD + assert(readbytes == sizeof(count)); + count = *(uint64_t*)buf; +#endif + for (uint64_t i = 0; i < count; ++i) { + hmutex_lock(&loop->custom_events_mutex); + if (event_queue_empty(&loop->custom_events)) { + goto unlock; + } + pev = event_queue_front(&loop->custom_events); + if (pev == NULL) { + goto unlock; + } + ev = *pev; + event_queue_pop_front(&loop->custom_events); + // NOTE: unlock before cb, avoid deadlock if hloop_post_event called in cb. + hmutex_unlock(&loop->custom_events_mutex); + if (ev.cb) { + ev.cb(&ev); + } + } + return; +unlock: + hmutex_unlock(&loop->custom_events_mutex); +} + +static int hloop_create_eventfds(hloop_t* loop) { +#if defined(OS_UNIX) && HAVE_EVENTFD + int efd = eventfd(0, 0); + if (efd < 0) { + hloge("eventfd create failed!"); + return -1; + } + loop->eventfds[0] = loop->eventfds[1] = efd; +#elif defined(OS_UNIX) && HAVE_PIPE + if (pipe(loop->eventfds) != 0) { + hloge("pipe create failed!"); + return -1; + } +#else + if (Socketpair(AF_INET, SOCK_STREAM, 0, loop->eventfds) != 0) { + hloge("socketpair create failed!"); + return -1; + } +#endif + hio_t* io = hread(loop, loop->eventfds[EVENTFDS_READ_INDEX], NULL, 0, eventfd_read_cb); + io->priority = HEVENT_HIGH_PRIORITY; + ++loop->intern_nevents; + return 0; +} + +static void hloop_destroy_eventfds(hloop_t* loop) { +#if defined(OS_UNIX) && HAVE_EVENTFD + // NOTE: eventfd has only one fd + SAFE_CLOSE(loop->eventfds[0]); +#elif defined(OS_UNIX) && HAVE_PIPE + SAFE_CLOSE(loop->eventfds[0]); + SAFE_CLOSE(loop->eventfds[1]); +#else + // NOTE: Avoid duplication closesocket in hio_cleanup + // SAFE_CLOSESOCKET(loop->eventfds[EVENTFDS_READ_INDEX]); + SAFE_CLOSESOCKET(loop->eventfds[EVENTFDS_WRITE_INDEX]); +#endif + loop->eventfds[0] = loop->eventfds[1] = -1; +} + +void hloop_post_event(hloop_t* loop, hevent_t* ev) { + if (ev->loop == NULL) { + ev->loop = loop; + } + if (ev->event_type == 0) { + ev->event_type = HEVENT_TYPE_CUSTOM; + } + if (ev->event_id == 0) { + ev->event_id = hloop_next_event_id(); + } + + int nwrite = 0; + uint64_t count = 1; + hmutex_lock(&loop->custom_events_mutex); + if (loop->eventfds[EVENTFDS_WRITE_INDEX] == -1) { + if (hloop_create_eventfds(loop) != 0) { + goto unlock; + } + } +#if defined(OS_UNIX) && HAVE_EVENTFD + nwrite = write(loop->eventfds[EVENTFDS_WRITE_INDEX], &count, sizeof(count)); +#elif defined(OS_UNIX) && HAVE_PIPE + nwrite = write(loop->eventfds[EVENTFDS_WRITE_INDEX], "e", 1); +#else + nwrite = send(loop->eventfds[EVENTFDS_WRITE_INDEX], "e", 1, 0); +#endif + if (nwrite <= 0) { + hloge("hloop_post_event failed!"); + goto unlock; + } + if (loop->custom_events.maxsize == 0) { + event_queue_init(&loop->custom_events, CUSTOM_EVENT_QUEUE_INIT_SIZE); + } + event_queue_push_back(&loop->custom_events, ev); +unlock: + hmutex_unlock(&loop->custom_events_mutex); +} + +static void hloop_init(hloop_t* loop) { +#ifdef OS_WIN + WSAInit(); +#endif +#ifdef SIGPIPE + // NOTE: if not ignore SIGPIPE, write twice when peer close will lead to exit process by SIGPIPE. + signal(SIGPIPE, SIG_IGN); +#endif + + loop->status = HLOOP_STATUS_STOP; + loop->pid = hv_getpid(); + loop->tid = hv_gettid(); + + // idles + list_init(&loop->idles); + + // timers + heap_init(&loop->timers, timers_compare); + heap_init(&loop->realtimers, timers_compare); + + // ios + // NOTE: io_array_init when hio_get -> io_array_resize + // io_array_init(&loop->ios, IO_ARRAY_INIT_SIZE); + + // readbuf + // NOTE: alloc readbuf when hio_use_loop_readbuf + // loop->readbuf.len = HLOOP_READ_BUFSIZE; + // HV_ALLOC(loop->readbuf.base, loop->readbuf.len); + + // NOTE: iowatcher_init when hio_add -> iowatcher_add_event + // iowatcher_init(loop); + + // custom_events + hmutex_init(&loop->custom_events_mutex); + // NOTE: hloop_create_eventfds when hloop_post_event or hloop_run + loop->eventfds[0] = loop->eventfds[1] = -1; + + // NOTE: init start_time here, because htimer_add use it. + loop->start_ms = gettimeofday_ms(); + loop->start_hrtime = loop->cur_hrtime = gethrtime_us(); +} + +static void hloop_cleanup(hloop_t* loop) { + // pendings + printd("cleanup pendings...\n"); + for (int i = 0; i < HEVENT_PRIORITY_SIZE; ++i) { + loop->pendings[i] = NULL; + } + + // ios + printd("cleanup ios...\n"); + for (int i = 0; i < loop->ios.maxsize; ++i) { + hio_t* io = loop->ios.ptr[i]; + if (io) { + hio_free(io); + } + } + io_array_cleanup(&loop->ios); + + // idles + printd("cleanup idles...\n"); + struct list_node* node = loop->idles.next; + hidle_t* idle; + while (node != &loop->idles) { + idle = IDLE_ENTRY(node); + node = node->next; + HV_FREE(idle); + } + list_init(&loop->idles); + + // timers + printd("cleanup timers...\n"); + htimer_t* timer; + while (loop->timers.root) { + timer = TIMER_ENTRY(loop->timers.root); + heap_dequeue(&loop->timers); + HV_FREE(timer); + } + heap_init(&loop->timers, NULL); + while (loop->realtimers.root) { + timer = TIMER_ENTRY(loop->realtimers.root); + heap_dequeue(&loop->realtimers); + HV_FREE(timer); + } + heap_init(&loop->realtimers, NULL); + + // readbuf + if (loop->readbuf.base && loop->readbuf.len) { + HV_FREE(loop->readbuf.base); + loop->readbuf.base = NULL; + loop->readbuf.len = 0; + } + + // iowatcher + iowatcher_cleanup(loop); + + // custom_events + hmutex_lock(&loop->custom_events_mutex); + hloop_destroy_eventfds(loop); + event_queue_cleanup(&loop->custom_events); + hmutex_unlock(&loop->custom_events_mutex); + hmutex_destroy(&loop->custom_events_mutex); +} + +hloop_t* hloop_new(int flags) { + hloop_t* loop; + HV_ALLOC_SIZEOF(loop); + hloop_init(loop); + loop->flags |= flags; + // hlogd("hloop_new tid=%ld", loop->tid); + return loop; +} + +void hloop_free(hloop_t** pp) { + if (pp == NULL || *pp == NULL) return; + hloop_t* loop = *pp; + if (loop->status == HLOOP_STATUS_DESTROY) return; + loop->status = HLOOP_STATUS_DESTROY; + hlogd("hloop_free tid=%ld", hv_gettid()); + hloop_cleanup(loop); + HV_FREE(loop); + *pp = NULL; +} + +// while (loop->status) { hloop_process_events(loop); } +int hloop_run(hloop_t* loop) { + if (loop == NULL) return -1; + if (loop->status == HLOOP_STATUS_RUNNING) return -2; + + loop->status = HLOOP_STATUS_RUNNING; + loop->pid = hv_getpid(); + loop->tid = hv_gettid(); + // hlogd("hloop_run tid=%ld", loop->tid); + + if (loop->intern_nevents == 0) { + hmutex_lock(&loop->custom_events_mutex); + if (loop->eventfds[EVENTFDS_WRITE_INDEX] == -1) { + hloop_create_eventfds(loop); + } + hmutex_unlock(&loop->custom_events_mutex); + +#ifdef DEBUG + htimer_add(loop, hloop_stat_timer_cb, HLOOP_STAT_TIMEOUT, INFINITE); + ++loop->intern_nevents; +#endif + } + + while (loop->status != HLOOP_STATUS_STOP) { + if (loop->status == HLOOP_STATUS_PAUSE) { + hv_msleep(HLOOP_PAUSE_TIME); + hloop_update_time(loop); + continue; + } + ++loop->loop_cnt; + if ((loop->flags & HLOOP_FLAG_QUIT_WHEN_NO_ACTIVE_EVENTS) && + loop->nactives <= loop->intern_nevents) { + break; + } + hloop_process_events(loop, HLOOP_MAX_BLOCK_TIME); + if (loop->flags & HLOOP_FLAG_RUN_ONCE) { + break; + } + } + + loop->status = HLOOP_STATUS_STOP; + loop->end_hrtime = gethrtime_us(); + + if (loop->flags & HLOOP_FLAG_AUTO_FREE) { + hloop_free(&loop); + } + return 0; +} + +int hloop_wakeup(hloop_t* loop) { + hevent_t ev; + memset(&ev, 0, sizeof(ev)); + hloop_post_event(loop, &ev); + return 0; +} + +int hloop_stop(hloop_t* loop) { + if (loop == NULL) return -1; + if (loop->status == HLOOP_STATUS_STOP) return -2; + hlogd("hloop_stop tid=%ld", hv_gettid()); + if (hv_gettid() != loop->tid) { + hloop_wakeup(loop); + } + loop->status = HLOOP_STATUS_STOP; + return 0; +} + +int hloop_pause(hloop_t* loop) { + if (loop->status == HLOOP_STATUS_RUNNING) { + loop->status = HLOOP_STATUS_PAUSE; + } + return 0; +} + +int hloop_resume(hloop_t* loop) { + if (loop->status == HLOOP_STATUS_PAUSE) { + loop->status = HLOOP_STATUS_RUNNING; + } + return 0; +} + +hloop_status_e hloop_status(hloop_t* loop) { + return loop->status; +} + +void hloop_update_time(hloop_t* loop) { + loop->cur_hrtime = gethrtime_us(); + if (hloop_now(loop) != time(NULL)) { + // systemtime changed, we adjust start_ms + loop->start_ms = gettimeofday_ms() - (loop->cur_hrtime - loop->start_hrtime) / 1000; + } +} + +uint64_t hloop_now(hloop_t* loop) { + return loop->start_ms / 1000 + (loop->cur_hrtime - loop->start_hrtime) / 1000000; +} + +uint64_t hloop_now_ms(hloop_t* loop) { + return loop->start_ms + (loop->cur_hrtime - loop->start_hrtime) / 1000; +} + +uint64_t hloop_now_us(hloop_t* loop) { + return loop->start_ms * 1000 + (loop->cur_hrtime - loop->start_hrtime); +} + +uint64_t hloop_now_hrtime(hloop_t* loop) { + return loop->cur_hrtime; +} + +uint64_t hio_last_read_time(hio_t* io) { + hloop_t* loop = io->loop; + return loop->start_ms + (io->last_read_hrtime - loop->start_hrtime) / 1000; +} + +uint64_t hio_last_write_time(hio_t* io) { + hloop_t* loop = io->loop; + return loop->start_ms + (io->last_write_hrtime - loop->start_hrtime) / 1000; +} + +long hloop_pid(hloop_t* loop) { + return loop->pid; +} + +long hloop_tid(hloop_t* loop) { + return loop->tid; +} + +uint64_t hloop_count(hloop_t* loop) { + return loop->loop_cnt; +} + +uint32_t hloop_nios(hloop_t* loop) { + return loop->nios; +} + +uint32_t hloop_ntimers(hloop_t* loop) { + return loop->ntimers; +} + +uint32_t hloop_nidles(hloop_t* loop) { + return loop->nidles; +} + +uint32_t hloop_nactives(hloop_t* loop) { + return loop->nactives; +} + +void hloop_set_userdata(hloop_t* loop, void* userdata) { + loop->userdata = userdata; +} + +void* hloop_userdata(hloop_t* loop) { + return loop->userdata; +} + +hidle_t* hidle_add(hloop_t* loop, hidle_cb cb, uint32_t repeat) { + hidle_t* idle; + HV_ALLOC_SIZEOF(idle); + idle->event_type = HEVENT_TYPE_IDLE; + idle->priority = HEVENT_LOWEST_PRIORITY; + idle->repeat = repeat; + list_add(&idle->node, &loop->idles); + EVENT_ADD(loop, idle, cb); + loop->nidles++; + return idle; +} + +static void __hidle_del(hidle_t* idle) { + if (idle->destroy) return; + idle->destroy = 1; + list_del(&idle->node); + idle->loop->nidles--; +} + +void hidle_del(hidle_t* idle) { + if (!idle->active) return; + __hidle_del(idle); + EVENT_DEL(idle); +} + +htimer_t* htimer_add(hloop_t* loop, htimer_cb cb, uint32_t timeout_ms, uint32_t repeat) { + if (timeout_ms == 0) return NULL; + htimeout_t* timer; + HV_ALLOC_SIZEOF(timer); + timer->event_type = HEVENT_TYPE_TIMEOUT; + timer->priority = HEVENT_HIGHEST_PRIORITY; + timer->repeat = repeat; + timer->timeout = timeout_ms; + hloop_update_time(loop); + timer->next_timeout = loop->cur_hrtime + (uint64_t)timeout_ms * 1000; + // NOTE: Limit granularity to 100ms + if (timeout_ms >= 1000 && timeout_ms % 100 == 0) { + timer->next_timeout = timer->next_timeout / 100000 * 100000; + } + heap_insert(&loop->timers, &timer->node); + EVENT_ADD(loop, timer, cb); + loop->ntimers++; + return (htimer_t*)timer; +} + +void htimer_reset(htimer_t* timer, uint32_t timeout_ms) { + if (timer->event_type != HEVENT_TYPE_TIMEOUT) { + return; + } + hloop_t* loop = timer->loop; + htimeout_t* timeout = (htimeout_t*)timer; + if (timer->destroy) { + loop->ntimers++; + } else { + heap_remove(&loop->timers, &timer->node); + } + if (timer->repeat == 0) { + timer->repeat = 1; + } + if (timeout_ms > 0) { + timeout->timeout = timeout_ms; + } + timer->next_timeout = loop->cur_hrtime + (uint64_t)timeout->timeout * 1000; + // NOTE: Limit granularity to 100ms + if (timeout->timeout >= 1000 && timeout->timeout % 100 == 0) { + timer->next_timeout = timer->next_timeout / 100000 * 100000; + } + heap_insert(&loop->timers, &timer->node); + EVENT_RESET(timer); +} + +htimer_t* htimer_add_period(hloop_t* loop, htimer_cb cb, + int8_t minute, int8_t hour, int8_t day, + int8_t week, int8_t month, uint32_t repeat) { + if (minute > 59 || hour > 23 || day > 31 || week > 6 || month > 12) { + return NULL; + } + hperiod_t* timer; + HV_ALLOC_SIZEOF(timer); + timer->event_type = HEVENT_TYPE_PERIOD; + timer->priority = HEVENT_HIGH_PRIORITY; + timer->repeat = repeat; + timer->minute = minute; + timer->hour = hour; + timer->day = day; + timer->month = month; + timer->week = week; + timer->next_timeout = (uint64_t)cron_next_timeout(minute, hour, day, week, month) * 1000000; + heap_insert(&loop->realtimers, &timer->node); + EVENT_ADD(loop, timer, cb); + loop->ntimers++; + return (htimer_t*)timer; +} + +static void __htimer_del(htimer_t* timer) { + if (timer->destroy) return; + if (timer->event_type == HEVENT_TYPE_TIMEOUT) { + heap_remove(&timer->loop->timers, &timer->node); + } else if (timer->event_type == HEVENT_TYPE_PERIOD) { + heap_remove(&timer->loop->realtimers, &timer->node); + } + timer->loop->ntimers--; + timer->destroy = 1; +} + +void htimer_del(htimer_t* timer) { + if (!timer->active) return; + __htimer_del(timer); + EVENT_DEL(timer); +} + +const char* hio_engine() { +#ifdef EVENT_SELECT + return "select"; +#elif defined(EVENT_POLL) + return "poll"; +#elif defined(EVENT_EPOLL) + return "epoll"; +#elif defined(EVENT_KQUEUE) + return "kqueue"; +#elif defined(EVENT_IOCP) + return "iocp"; +#elif defined(EVENT_PORT) + return "evport"; +#else + return "noevent"; +#endif +} + +static inline hio_t* __hio_get(hloop_t* loop, int fd) { + if (fd >= loop->ios.maxsize) { + int newsize = ceil2e(fd); + newsize = MAX(newsize, IO_ARRAY_INIT_SIZE); + io_array_resize(&loop->ios, newsize > fd ? newsize : 2*fd); + } + return loop->ios.ptr[fd]; +} + +hio_t* hio_get(hloop_t* loop, int fd) { + hio_t* io = __hio_get(loop, fd); + if (io == NULL) { + HV_ALLOC_SIZEOF(io); + hio_init(io); + io->event_type = HEVENT_TYPE_IO; + io->loop = loop; + io->fd = fd; + loop->ios.ptr[fd] = io; + } + + if (!io->ready) { + hio_ready(io); + } + + return io; +} + +void hio_detach(hio_t* io) { + hloop_t* loop = io->loop; + int fd = io->fd; + assert(loop != NULL && fd < loop->ios.maxsize); + loop->ios.ptr[fd] = NULL; +} + +void hio_attach(hloop_t* loop, hio_t* io) { + int fd = io->fd; + // NOTE: hio was not freed for reused when closed, but attached hio can't be reused, + // so we need to free it if fd exists to avoid memory leak. + hio_t* preio = __hio_get(loop, fd); + if (preio != NULL && preio != io) { + hio_free(preio); + } + + io->loop = loop; + // NOTE: use new_loop readbuf + hio_use_loop_readbuf(io); + loop->ios.ptr[fd] = io; +} + +bool hio_exists(hloop_t* loop, int fd) { + if (fd >= loop->ios.maxsize) { + return false; + } + return loop->ios.ptr[fd] != NULL; +} + +int hio_add(hio_t* io, hio_cb cb, int events) { + printd("hio_add fd=%d io->events=%d events=%d\n", io->fd, io->events, events); +#ifdef OS_WIN + // Windows iowatcher not work on stdio + if (io->fd < 3) return -1; +#endif + hloop_t* loop = io->loop; + if (!io->active) { + EVENT_ADD(loop, io, cb); + loop->nios++; + } + + if (!io->ready) { + hio_ready(io); + } + + if (cb) { + io->cb = (hevent_cb)cb; + } + + if (!(io->events & events)) { + iowatcher_add_event(loop, io->fd, events); + io->events |= events; + } + return 0; +} + +int hio_del(hio_t* io, int events) { + printd("hio_del fd=%d io->events=%d events=%d\n", io->fd, io->events, events); +#ifdef OS_WIN + // Windows iowatcher not work on stdio + if (io->fd < 3) return -1; +#endif + if (!io->active) return -1; + + if (io->events & events) { + iowatcher_del_event(io->loop, io->fd, events); + io->events &= ~events; + } + if (io->events == 0) { + io->loop->nios--; + // NOTE: not EVENT_DEL, avoid free + EVENT_INACTIVE(io); + } + return 0; +} + +static void hio_close_event_cb(hevent_t* ev) { + hio_t* io = (hio_t*)ev->userdata; + uint32_t id = (uintptr_t)ev->privdata; + if (io->id != id) return; + hio_close(io); +} + +int hio_close_async(hio_t* io) { + hevent_t ev; + memset(&ev, 0, sizeof(ev)); + ev.cb = hio_close_event_cb; + ev.userdata = io; + ev.privdata = (void*)(uintptr_t)io->id; + hloop_post_event(io->loop, &ev); + return 0; +} + +//------------------high-level apis------------------------------------------- +hio_t* hread(hloop_t* loop, int fd, void* buf, size_t len, hread_cb read_cb) { + hio_t* io = hio_get(loop, fd); + assert(io != NULL); + if (buf && len) { + io->readbuf.base = (char*)buf; + io->readbuf.len = len; + } + if (read_cb) { + io->read_cb = read_cb; + } + hio_read(io); + return io; +} + +hio_t* hwrite(hloop_t* loop, int fd, const void* buf, size_t len, hwrite_cb write_cb) { + hio_t* io = hio_get(loop, fd); + assert(io != NULL); + if (write_cb) { + io->write_cb = write_cb; + } + hio_write(io, buf, len); + return io; +} + +hio_t* haccept(hloop_t* loop, int listenfd, haccept_cb accept_cb) { + hio_t* io = hio_get(loop, listenfd); + assert(io != NULL); + if (accept_cb) { + io->accept_cb = accept_cb; + } + if (hio_accept(io) != 0) return NULL; + return io; +} + +hio_t* hconnect (hloop_t* loop, int connfd, hconnect_cb connect_cb) { + hio_t* io = hio_get(loop, connfd); + assert(io != NULL); + if (connect_cb) { + io->connect_cb = connect_cb; + } + if (hio_connect(io) != 0) return NULL; + return io; +} + +void hclose (hloop_t* loop, int fd) { + hio_t* io = hio_get(loop, fd); + assert(io != NULL); + hio_close(io); +} + +hio_t* hrecv (hloop_t* loop, int connfd, void* buf, size_t len, hread_cb read_cb) { + //hio_t* io = hio_get(loop, connfd); + //assert(io != NULL); + //io->recv = 1; + //if (io->io_type != HIO_TYPE_SSL) { + //io->io_type = HIO_TYPE_TCP; + //} + return hread(loop, connfd, buf, len, read_cb); +} + +hio_t* hsend (hloop_t* loop, int connfd, const void* buf, size_t len, hwrite_cb write_cb) { + //hio_t* io = hio_get(loop, connfd); + //assert(io != NULL); + //io->send = 1; + //if (io->io_type != HIO_TYPE_SSL) { + //io->io_type = HIO_TYPE_TCP; + //} + return hwrite(loop, connfd, buf, len, write_cb); +} + +hio_t* hrecvfrom (hloop_t* loop, int sockfd, void* buf, size_t len, hread_cb read_cb) { + //hio_t* io = hio_get(loop, sockfd); + //assert(io != NULL); + //io->recvfrom = 1; + //io->io_type = HIO_TYPE_UDP; + return hread(loop, sockfd, buf, len, read_cb); +} + +hio_t* hsendto (hloop_t* loop, int sockfd, const void* buf, size_t len, hwrite_cb write_cb) { + //hio_t* io = hio_get(loop, sockfd); + //assert(io != NULL); + //io->sendto = 1; + //io->io_type = HIO_TYPE_UDP; + return hwrite(loop, sockfd, buf, len, write_cb); +} + +//-----------------top-level apis--------------------------------------------- +hio_t* hio_create_socket(hloop_t* loop, const char* host, int port, hio_type_e type, hio_side_e side) { + int sock_type = (type & HIO_TYPE_SOCK_STREAM) ? SOCK_STREAM : + (type & HIO_TYPE_SOCK_DGRAM) ? SOCK_DGRAM : + (type & HIO_TYPE_SOCK_RAW) ? SOCK_RAW : -1; + if (sock_type == -1) return NULL; + sockaddr_u addr; + memset(&addr, 0, sizeof(addr)); + int ret = -1; +#ifdef ENABLE_UDS + if (port < 0) { + sockaddr_set_path(&addr, host); + ret = 0; + } +#endif + if (port >= 0) { + ret = sockaddr_set_ipport(&addr, host, port); + } + if (ret != 0) { + // fprintf(stderr, "unknown host: %s\n", host); + return NULL; + } + int sockfd = socket(addr.sa.sa_family, sock_type, 0); + if (sockfd < 0) { + perror("socket"); + return NULL; + } + hio_t* io = NULL; + if (side == HIO_SERVER_SIDE) { +#ifdef OS_UNIX + so_reuseaddr(sockfd, 1); + // so_reuseport(sockfd, 1); +#endif + if (addr.sa.sa_family == AF_INET6) { + ip_v6only(sockfd, 0); + } + if (bind(sockfd, &addr.sa, sockaddr_len(&addr)) < 0) { + perror("bind"); + closesocket(sockfd); + return NULL; + } + if (sock_type == SOCK_STREAM) { + if (listen(sockfd, SOMAXCONN) < 0) { + perror("listen"); + closesocket(sockfd); + return NULL; + } + } + } + io = hio_get(loop, sockfd); + assert(io != NULL); + io->io_type = type; + if (side == HIO_SERVER_SIDE) { + hio_set_localaddr(io, &addr.sa, sockaddr_len(&addr)); + io->priority = HEVENT_HIGH_PRIORITY; + } else { + hio_set_peeraddr(io, &addr.sa, sockaddr_len(&addr)); + } + return io; +} + +hio_t* hloop_create_tcp_server (hloop_t* loop, const char* host, int port, haccept_cb accept_cb) { + hio_t* io = hio_create_socket(loop, host, port, HIO_TYPE_TCP, HIO_SERVER_SIDE); + if (io == NULL) return NULL; + hio_setcb_accept(io, accept_cb); + if (hio_accept(io) != 0) return NULL; + return io; +} + +hio_t* hloop_create_tcp_client (hloop_t* loop, const char* host, int port, hconnect_cb connect_cb, hclose_cb close_cb) { + hio_t* io = hio_create_socket(loop, host, port, HIO_TYPE_TCP, HIO_CLIENT_SIDE); + if (io == NULL) return NULL; + hio_setcb_connect(io, connect_cb); + hio_setcb_close(io, close_cb); + if (hio_connect(io) != 0) return NULL; + return io; +} + + + +hio_t* hloop_create_udp_server(hloop_t* loop, const char* host, int port) { + return hio_create_socket(loop, host, port, HIO_TYPE_UDP, HIO_SERVER_SIDE); +} + +hio_t* hloop_create_udp_client(hloop_t* loop, const char* host, int port) { + return hio_create_socket(loop, host, port, HIO_TYPE_UDP, HIO_CLIENT_SIDE); +} diff --git a/ww/libhv/event/hloop.h b/ww/libhv/event/hloop.h new file mode 100644 index 00000000..732c748b --- /dev/null +++ b/ww/libhv/event/hloop.h @@ -0,0 +1,719 @@ +#ifndef HV_LOOP_H_ +#define HV_LOOP_H_ + +#include "hexport.h" +#include "hplatform.h" +#include "hdef.h" + +typedef struct hloop_s hloop_t; +typedef struct hevent_s hevent_t; + +// NOTE: The following structures are subclasses of hevent_t, +// inheriting hevent_t data members and function members. +typedef struct hidle_s hidle_t; +typedef struct htimer_s htimer_t; +typedef struct htimeout_s htimeout_t; +typedef struct hperiod_s hperiod_t; +typedef struct hio_s hio_t; + +typedef void (*hevent_cb) (hevent_t* ev); +typedef void (*hidle_cb) (hidle_t* idle); +typedef void (*htimer_cb) (htimer_t* timer); +typedef void (*hio_cb) (hio_t* io); + +typedef void (*haccept_cb) (hio_t* io); +typedef void (*hconnect_cb) (hio_t* io); +typedef void (*hread_cb) (hio_t* io, void* buf, int readbytes); +typedef void (*hwrite_cb) (hio_t* io, const void* buf, int writebytes); +typedef void (*hclose_cb) (hio_t* io); + +typedef enum { + HLOOP_STATUS_STOP, + HLOOP_STATUS_RUNNING, + HLOOP_STATUS_PAUSE, + HLOOP_STATUS_DESTROY +} hloop_status_e; + +typedef enum { + HEVENT_TYPE_NONE = 0, + HEVENT_TYPE_IO = 0x00000001, + HEVENT_TYPE_TIMEOUT = 0x00000010, + HEVENT_TYPE_PERIOD = 0x00000020, + HEVENT_TYPE_TIMER = HEVENT_TYPE_TIMEOUT|HEVENT_TYPE_PERIOD, + HEVENT_TYPE_IDLE = 0x00000100, + HEVENT_TYPE_CUSTOM = 0x00000400, // 1024 +} hevent_type_e; + +#define HEVENT_LOWEST_PRIORITY (-5) +#define HEVENT_LOW_PRIORITY (-3) +#define HEVENT_NORMAL_PRIORITY 0 +#define HEVENT_HIGH_PRIORITY 3 +#define HEVENT_HIGHEST_PRIORITY 5 +#define HEVENT_PRIORITY_SIZE (HEVENT_HIGHEST_PRIORITY-HEVENT_LOWEST_PRIORITY+1) +#define HEVENT_PRIORITY_INDEX(priority) (priority-HEVENT_LOWEST_PRIORITY) + +#define HEVENT_FLAGS \ + unsigned destroy :1; \ + unsigned active :1; \ + unsigned pending :1; + +#define HEVENT_FIELDS \ + hloop_t* loop; \ + hevent_type_e event_type; \ + uint64_t event_id; \ + hevent_cb cb; \ + void* userdata; \ + void* privdata; \ + struct hevent_s* pending_next; \ + int priority; \ + HEVENT_FLAGS + +// sizeof(struct hevent_s)=64 on x64 +struct hevent_s { + HEVENT_FIELDS +}; + +#define hevent_set_id(ev, id) ((hevent_t*)(ev))->event_id = id +#define hevent_set_cb(ev, cb) ((hevent_t*)(ev))->cb = cb +#define hevent_set_priority(ev, prio) ((hevent_t*)(ev))->priority = prio +#define hevent_set_userdata(ev, udata) ((hevent_t*)(ev))->userdata = (void*)udata + +#define hevent_loop(ev) (((hevent_t*)(ev))->loop) +#define hevent_type(ev) (((hevent_t*)(ev))->event_type) +#define hevent_id(ev) (((hevent_t*)(ev))->event_id) +#define hevent_cb(ev) (((hevent_t*)(ev))->cb) +#define hevent_priority(ev) (((hevent_t*)(ev))->priority) +#define hevent_userdata(ev) (((hevent_t*)(ev))->userdata) + +typedef enum { + HIO_TYPE_UNKNOWN = 0, + HIO_TYPE_STDIN = 0x00000001, + HIO_TYPE_STDOUT = 0x00000002, + HIO_TYPE_STDERR = 0x00000004, + HIO_TYPE_STDIO = 0x0000000F, + + HIO_TYPE_FILE = 0x00000010, + + HIO_TYPE_IP = 0x00000100, + HIO_TYPE_SOCK_RAW = 0x00000F00, + + HIO_TYPE_UDP = 0x00001000, + HIO_TYPE_KCP = 0x00002000, + HIO_TYPE_DTLS = 0x00010000, + HIO_TYPE_SOCK_DGRAM = 0x000FF000, + + HIO_TYPE_TCP = 0x00100000, + // HIO_TYPE_SSL = 0x01000000, + // HIO_TYPE_TLS = HIO_TYPE_SSL, + HIO_TYPE_SOCK_STREAM= 0x0FF00000, + + HIO_TYPE_SOCKET = 0x0FFFFF00, +} hio_type_e; + +typedef enum { + HIO_SERVER_SIDE = 0, + HIO_CLIENT_SIDE = 1, +} hio_side_e; + +#define HIO_DEFAULT_CONNECT_TIMEOUT 10000 // ms +#define HIO_DEFAULT_CLOSE_TIMEOUT 60000 // ms +#define HIO_DEFAULT_KEEPALIVE_TIMEOUT 75000 // ms +#define HIO_DEFAULT_HEARTBEAT_INTERVAL 10000 // ms + +BEGIN_EXTERN_C + +// loop +#define HLOOP_FLAG_RUN_ONCE 0x00000001 +#define HLOOP_FLAG_AUTO_FREE 0x00000002 +#define HLOOP_FLAG_QUIT_WHEN_NO_ACTIVE_EVENTS 0x00000004 +HV_EXPORT hloop_t* hloop_new(int flags DEFAULT(HLOOP_FLAG_AUTO_FREE)); + +// WARN: Forbid to call hloop_free if HLOOP_FLAG_AUTO_FREE set. +HV_EXPORT void hloop_free(hloop_t** pp); + +HV_EXPORT int hloop_process_events(hloop_t* loop, int timeout_ms DEFAULT(0)); + +// NOTE: when no active events, loop will quit if HLOOP_FLAG_QUIT_WHEN_NO_ACTIVE_EVENTS set. +HV_EXPORT int hloop_run(hloop_t* loop); +// NOTE: hloop_stop called in loop-thread just set flag to quit in next loop, +// if called in other thread, it will wakeup loop-thread from blocking poll system call, +// then you should join loop thread to safely exit loop thread. +HV_EXPORT int hloop_stop(hloop_t* loop); +HV_EXPORT int hloop_pause(hloop_t* loop); +HV_EXPORT int hloop_resume(hloop_t* loop); +HV_EXPORT int hloop_wakeup(hloop_t* loop); +HV_EXPORT hloop_status_e hloop_status(hloop_t* loop); + +HV_EXPORT void hloop_update_time(hloop_t* loop); +HV_EXPORT uint64_t hloop_now(hloop_t* loop); // s +HV_EXPORT uint64_t hloop_now_ms(hloop_t* loop); // ms +HV_EXPORT uint64_t hloop_now_us(hloop_t* loop); // us +HV_EXPORT uint64_t hloop_now_hrtime(hloop_t* loop); // us + +// export some hloop's members +// @return pid of hloop_run +HV_EXPORT long hloop_pid(hloop_t* loop); +// @return tid of hloop_run +HV_EXPORT long hloop_tid(hloop_t* loop); +// @return count of loop +HV_EXPORT uint64_t hloop_count(hloop_t* loop); +// @return number of ios +HV_EXPORT uint32_t hloop_nios(hloop_t* loop); +// @return number of timers +HV_EXPORT uint32_t hloop_ntimers(hloop_t* loop); +// @return number of idles +HV_EXPORT uint32_t hloop_nidles(hloop_t* loop); +// @return number of active events +HV_EXPORT uint32_t hloop_nactives(hloop_t* loop); + +// userdata +HV_EXPORT void hloop_set_userdata(hloop_t* loop, void* userdata); +HV_EXPORT void* hloop_userdata(hloop_t* loop); + +// custom_event +/* + * hevent_t ev; + * memset(&ev, 0, sizeof(hevent_t)); + * ev.event_type = (hevent_type_e)(HEVENT_TYPE_CUSTOM + 1); + * ev.cb = custom_event_cb; + * ev.userdata = userdata; + * hloop_post_event(loop, &ev); + */ +// NOTE: hloop_post_event is thread-safe, used to post event from other thread to loop thread. +HV_EXPORT void hloop_post_event(hloop_t* loop, hevent_t* ev); + +// idle +HV_EXPORT hidle_t* hidle_add(hloop_t* loop, hidle_cb cb, uint32_t repeat DEFAULT(INFINITE)); +HV_EXPORT void hidle_del(hidle_t* idle); + +// timer +HV_EXPORT htimer_t* htimer_add(hloop_t* loop, htimer_cb cb, uint32_t timeout_ms, uint32_t repeat DEFAULT(INFINITE)); +/* + * minute hour day week month cb + * 0~59 0~23 1~31 0~6 1~12 + * -1 -1 -1 -1 -1 cron.minutely + * 30 -1 -1 -1 -1 cron.hourly + * 30 1 -1 -1 -1 cron.daily + * 30 1 15 -1 -1 cron.monthly + * 30 1 -1 5 -1 cron.weekly + * 30 1 1 -1 10 cron.yearly + */ +HV_EXPORT htimer_t* htimer_add_period(hloop_t* loop, htimer_cb cb, + int8_t minute DEFAULT(0), int8_t hour DEFAULT(-1), int8_t day DEFAULT(-1), + int8_t week DEFAULT(-1), int8_t month DEFAULT(-1), uint32_t repeat DEFAULT(INFINITE)); + +HV_EXPORT void htimer_del(htimer_t* timer); +HV_EXPORT void htimer_reset(htimer_t* timer, uint32_t timeout_ms DEFAULT(0)); + +// io +//-----------------------low-level apis--------------------------------------- +#define HV_READ 0x0001 +#define HV_WRITE 0x0004 +#define HV_RDWR (HV_READ|HV_WRITE) +/* +const char* hio_engine() { +#ifdef EVENT_SELECT + return "select"; +#elif defined(EVENT_POLL) + return "poll"; +#elif defined(EVENT_EPOLL) + return "epoll"; +#elif defined(EVENT_KQUEUE) + return "kqueue"; +#elif defined(EVENT_IOCP) + return "iocp"; +#elif defined(EVENT_PORT) + return "evport"; +#else + return "noevent"; +#endif +} +*/ +HV_EXPORT const char* hio_engine(); + +HV_EXPORT hio_t* hio_get(hloop_t* loop, int fd); +HV_EXPORT int hio_add(hio_t* io, hio_cb cb, int events DEFAULT(HV_READ)); +HV_EXPORT int hio_del(hio_t* io, int events DEFAULT(HV_RDWR)); + +// NOTE: io detach from old loop and attach to new loop +/* @see examples/multi-thread/one-acceptor-multi-workers.c +void new_conn_event(hevent_t* ev) { + hloop_t* loop = ev->loop; + hio_t* io = (hio_t*)hevent_userdata(ev); + hio_attach(loop, io); +} + +void on_accpet(hio_t* io) { + hio_detach(io); + + hloop_t* worker_loop = get_one_loop(); + hevent_t ev; + memset(&ev, 0, sizeof(ev)); + ev.loop = worker_loop; + ev.cb = new_conn_event; + ev.userdata = io; + hloop_post_event(worker_loop, &ev); +} + */ +HV_EXPORT void hio_detach(/*hloop_t* loop,*/ hio_t* io); +HV_EXPORT void hio_attach(hloop_t* loop, hio_t* io); +HV_EXPORT bool hio_exists(hloop_t* loop, int fd); + +// hio_t fields +// NOTE: fd cannot be used as unique identifier, so we provide an id. +HV_EXPORT uint32_t hio_id (hio_t* io); +HV_EXPORT int hio_fd (hio_t* io); +HV_EXPORT int hio_error (hio_t* io); +HV_EXPORT int hio_events (hio_t* io); +HV_EXPORT int hio_revents (hio_t* io); +HV_EXPORT hio_type_e hio_type (hio_t* io); +HV_EXPORT struct sockaddr* hio_localaddr(hio_t* io); +HV_EXPORT struct sockaddr* hio_peeraddr (hio_t* io); +HV_EXPORT void hio_set_context(hio_t* io, void* ctx); +HV_EXPORT void* hio_context(hio_t* io); +HV_EXPORT bool hio_is_opened(hio_t* io); +HV_EXPORT bool hio_is_connected(hio_t* io); +HV_EXPORT bool hio_is_closed(hio_t* io); + +// iobuf +// #include "hbuf.h" +typedef struct fifo_buf_s hio_readbuf_t; +// NOTE: One loop per thread, one readbuf per loop. +// But you can pass in your own readbuf instead of the default readbuf to avoid memcopy. +HV_EXPORT void hio_set_readbuf(hio_t* io, void* buf, size_t len); +HV_EXPORT hio_readbuf_t* hio_get_readbuf(hio_t* io); +HV_EXPORT void hio_set_max_read_bufsize (hio_t* io, uint32_t size); +HV_EXPORT void hio_set_max_write_bufsize(hio_t* io, uint32_t size); +// NOTE: hio_write is non-blocking, so there is a write queue inside hio_t to cache unwritten data and wait for writable. +// @return current buffer size of write queue. +HV_EXPORT size_t hio_write_bufsize(hio_t* io); +#define hio_write_is_complete(io) (hio_write_bufsize(io) == 0) + +HV_EXPORT uint64_t hio_last_read_time(hio_t* io); // ms +HV_EXPORT uint64_t hio_last_write_time(hio_t* io); // ms + +// set callbacks +HV_EXPORT void hio_setcb_accept (hio_t* io, haccept_cb accept_cb); +HV_EXPORT void hio_setcb_connect (hio_t* io, hconnect_cb connect_cb); +HV_EXPORT void hio_setcb_read (hio_t* io, hread_cb read_cb); +HV_EXPORT void hio_setcb_write (hio_t* io, hwrite_cb write_cb); +HV_EXPORT void hio_setcb_close (hio_t* io, hclose_cb close_cb); +// get callbacks +HV_EXPORT haccept_cb hio_getcb_accept(hio_t* io); +HV_EXPORT hconnect_cb hio_getcb_connect(hio_t* io); +HV_EXPORT hread_cb hio_getcb_read(hio_t* io); +HV_EXPORT hwrite_cb hio_getcb_write(hio_t* io); +HV_EXPORT hclose_cb hio_getcb_close(hio_t* io); + +// connect timeout => hclose_cb +HV_EXPORT void hio_set_connect_timeout(hio_t* io, int timeout_ms DEFAULT(HIO_DEFAULT_CONNECT_TIMEOUT)); +// close timeout => hclose_cb +HV_EXPORT void hio_set_close_timeout(hio_t* io, int timeout_ms DEFAULT(HIO_DEFAULT_CLOSE_TIMEOUT)); +// read timeout => hclose_cb +HV_EXPORT void hio_set_read_timeout(hio_t* io, int timeout_ms); +// write timeout => hclose_cb +HV_EXPORT void hio_set_write_timeout(hio_t* io, int timeout_ms); +// keepalive timeout => hclose_cb +HV_EXPORT void hio_set_keepalive_timeout(hio_t* io, int timeout_ms DEFAULT(HIO_DEFAULT_KEEPALIVE_TIMEOUT)); +/* +void send_heartbeat(hio_t* io) { + static char buf[] = "PING\r\n"; + hio_write(io, buf, 6); +} +hio_set_heartbeat(io, 3000, send_heartbeat); +*/ +typedef void (*hio_send_heartbeat_fn)(hio_t* io); +// heartbeat interval => hio_send_heartbeat_fn +HV_EXPORT void hio_set_heartbeat(hio_t* io, int interval_ms, hio_send_heartbeat_fn fn); + +// Nonblocking, poll IO events in the loop to call corresponding callback. +// hio_add(io, HV_READ) => accept => haccept_cb +HV_EXPORT int hio_accept (hio_t* io); + +// connect => hio_add(io, HV_WRITE) => hconnect_cb +HV_EXPORT int hio_connect(hio_t* io); + +// hio_add(io, HV_READ) => read => hread_cb +HV_EXPORT int hio_read (hio_t* io); +#define hio_read_start(io) hio_read(io) +#define hio_read_stop(io) hio_del(io, HV_READ) + +// hio_read_start => hread_cb => hio_read_stop +HV_EXPORT int hio_read_once (hio_t* io); +// hio_read_once => hread_cb(len) +// HV_EXPORT int hio_read_until_length(hio_t* io, unsigned int len); +// hio_read_once => hread_cb(...delim) +// HV_EXPORT int hio_read_until_delim (hio_t* io, unsigned char delim); +HV_EXPORT int hio_read_remain(hio_t* io); +// @see examples/tinyhttpd.c examples/tinyproxyd.c +// #define hio_readline(io) hio_read_until_delim(io, '\n') +// #define hio_readstring(io) hio_read_until_delim(io, '\0') +#define hio_readbytes(io, len) hio_read_until_length(io, len) +#define hio_read_until(io, len) hio_read_until_length(io, len) + +// NOTE: hio_write is thread-safe, locked by recursive_mutex, allow to be called by other threads. +// hio_try_write => hio_add(io, HV_WRITE) => write => hwrite_cb +HV_EXPORT int hio_write (hio_t* io, const void* buf, size_t len); + +// NOTE: hio_close is thread-safe, hio_close_async will be called actually in other thread. +// hio_del(io, HV_RDWR) => close => hclose_cb +HV_EXPORT int hio_close (hio_t* io); +// NOTE: hloop_post_event(hio_close_event) +HV_EXPORT int hio_close_async(hio_t* io); + +//------------------high-level apis------------------------------------------- +// hio_get -> hio_set_readbuf -> hio_setcb_read -> hio_read +HV_EXPORT hio_t* hread (hloop_t* loop, int fd, void* buf, size_t len, hread_cb read_cb); +// hio_get -> hio_setcb_write -> hio_write +HV_EXPORT hio_t* hwrite (hloop_t* loop, int fd, const void* buf, size_t len, hwrite_cb write_cb DEFAULT(NULL)); +// hio_get -> hio_close +HV_EXPORT void hclose (hloop_t* loop, int fd); + +// tcp +// hio_get -> hio_setcb_accept -> hio_accept +HV_EXPORT hio_t* haccept (hloop_t* loop, int listenfd, haccept_cb accept_cb); +// hio_get -> hio_setcb_connect -> hio_connect +HV_EXPORT hio_t* hconnect (hloop_t* loop, int connfd, hconnect_cb connect_cb); +// hio_get -> hio_set_readbuf -> hio_setcb_read -> hio_read +HV_EXPORT hio_t* hrecv (hloop_t* loop, int connfd, void* buf, size_t len, hread_cb read_cb); +// hio_get -> hio_setcb_write -> hio_write +HV_EXPORT hio_t* hsend (hloop_t* loop, int connfd, const void* buf, size_t len, hwrite_cb write_cb DEFAULT(NULL)); + +// udp +HV_EXPORT void hio_set_type(hio_t* io, hio_type_e type); +HV_EXPORT void hio_set_localaddr(hio_t* io, struct sockaddr* addr, int addrlen); +HV_EXPORT void hio_set_peeraddr (hio_t* io, struct sockaddr* addr, int addrlen); +// NOTE: must call hio_set_peeraddr before hrecvfrom/hsendto +// hio_get -> hio_set_readbuf -> hio_setcb_read -> hio_read +HV_EXPORT hio_t* hrecvfrom (hloop_t* loop, int sockfd, void* buf, size_t len, hread_cb read_cb); +// hio_get -> hio_setcb_write -> hio_write +HV_EXPORT hio_t* hsendto (hloop_t* loop, int sockfd, const void* buf, size_t len, hwrite_cb write_cb DEFAULT(NULL)); + +//-----------------top-level apis--------------------------------------------- +// @hio_create_socket: socket -> bind -> listen +// sockaddr_set_ipport -> socket -> hio_get(loop, sockfd) -> +// side == HIO_SERVER_SIDE ? bind -> +// type & HIO_TYPE_SOCK_STREAM ? listen -> +HV_EXPORT hio_t* hio_create_socket(hloop_t* loop, const char* host, int port, + hio_type_e type DEFAULT(HIO_TYPE_TCP), + hio_side_e side DEFAULT(HIO_SERVER_SIDE)); + +// @tcp_server: hio_create_socket(loop, host, port, HIO_TYPE_TCP, HIO_SERVER_SIDE) -> hio_setcb_accept -> hio_accept +// @see examples/tcp_echo_server.c +HV_EXPORT hio_t* hloop_create_tcp_server (hloop_t* loop, const char* host, int port, haccept_cb accept_cb); + +// @tcp_client: hio_create_socket(loop, host, port, HIO_TYPE_TCP, HIO_CLIENT_SIDE) -> hio_setcb_connect -> hio_setcb_close -> hio_connect +// @see examples/nc.c +HV_EXPORT hio_t* hloop_create_tcp_client (hloop_t* loop, const char* host, int port, hconnect_cb connect_cb, hclose_cb close_cb); + +// @udp_server: hio_create_socket(loop, host, port, HIO_TYPE_UDP, HIO_SERVER_SIDE) +// @see examples/udp_echo_server.c +HV_EXPORT hio_t* hloop_create_udp_server (hloop_t* loop, const char* host, int port); + +// @udp_server: hio_create_socket(loop, host, port, HIO_TYPE_UDP, HIO_CLIENT_SIDE) +// @see examples/nc.c +HV_EXPORT hio_t* hloop_create_udp_client (hloop_t* loop, const char* host, int port); + +//-----------------upstream--------------------------------------------- +// hio_read(io) +// hio_read(io->upstream_io) +HV_EXPORT void hio_read_upstream(hio_t* io); +// on_write(io) -> hio_write_is_complete(io) -> hio_read(io->upstream_io) +HV_EXPORT void hio_read_upstream_on_write_complete(hio_t* io, const void* buf, int writebytes); +// hio_write(io->upstream_io, buf, bytes) +HV_EXPORT void hio_write_upstream(hio_t* io, void* buf, int bytes); +// hio_close(io->upstream_io) +HV_EXPORT void hio_close_upstream(hio_t* io); + +// io1->upstream_io = io2; +// io2->upstream_io = io1; +// @see examples/socks5_proxy_server.c +HV_EXPORT void hio_setup_upstream(hio_t* io1, hio_t* io2); + +// @return io->upstream_io +HV_EXPORT hio_t* hio_get_upstream(hio_t* io); + + +// @udp_upstream: hio_create_socket -> hio_setup_upstream -> hio_read_upstream +// @return upstream_io +// @see examples/udp_proxy_server.c +HV_EXPORT hio_t* hio_setup_udp_upstream(hio_t* io, const char* host, int port); + +//-----------------unpack--------------------------------------------- +typedef enum { + UNPACK_MODE_NONE = 0, + UNPACK_BY_FIXED_LENGTH = 1, // Not recommended + UNPACK_BY_DELIMITER = 2, // Suitable for text protocol + UNPACK_BY_LENGTH_FIELD = 3, // Suitable for binary protocol +} unpack_mode_e; + +#define DEFAULT_PACKAGE_MAX_LENGTH (1 << 21) // 2M + +// UNPACK_BY_DELIMITER +#define PACKAGE_MAX_DELIMITER_BYTES 8 + +// UNPACK_BY_LENGTH_FIELD +typedef enum { + ENCODE_BY_VARINT = 17, // 1 MSB + 7 bits + ENCODE_BY_LITTEL_ENDIAN = LITTLE_ENDIAN, // 1234 + ENCODE_BY_BIG_ENDIAN = BIG_ENDIAN, // 4321 +} unpack_coding_e; + +typedef struct unpack_setting_s { + unpack_mode_e mode; + unsigned int package_max_length; + union { + // UNPACK_BY_FIXED_LENGTH + struct { + unsigned int fixed_length; + }; + // UNPACK_BY_DELIMITER + struct { + unsigned char delimiter[PACKAGE_MAX_DELIMITER_BYTES]; + unsigned short delimiter_bytes; + }; + /* + * UNPACK_BY_LENGTH_FIELD + * + * package_len = head_len + body_len + length_adjustment + * + * if (length_field_coding == ENCODE_BY_VARINT) head_len = body_offset + varint_bytes - length_field_bytes; + * else head_len = body_offset; + * + * length_field stores body length, exclude head length, + * if length_field = head_len + body_len, then length_adjustment should be set to -head_len. + * + */ + struct { + unsigned short body_offset; // Equal to head length usually + unsigned short length_field_offset; + unsigned short length_field_bytes; + short length_adjustment; + unpack_coding_e length_field_coding; + }; + }; +#ifdef __cplusplus + unpack_setting_s() { + // Recommended setting: + // head = flags:1byte + length:4bytes = 5bytes + mode = UNPACK_BY_LENGTH_FIELD; + package_max_length = DEFAULT_PACKAGE_MAX_LENGTH; + fixed_length = 0; + delimiter_bytes = 0; + body_offset = 5; + length_field_offset = 1; + length_field_bytes = 4; + length_field_coding = ENCODE_BY_BIG_ENDIAN; + length_adjustment = 0; + } +#endif +} unpack_setting_t; + +/* + * @see examples/jsonrpc examples/protorpc + * + * NOTE: unpack_setting_t of multiple IOs of the same function also are same, + * so only the pointer of unpack_setting_t is stored in hio_t, + * the life time of unpack_setting_t shoud be guaranteed by caller. + */ +HV_EXPORT void hio_set_unpack(hio_t* io, unpack_setting_t* setting); +HV_EXPORT void hio_unset_unpack(hio_t* io); + +// unpack examples +/* +unpack_setting_t ftp_unpack_setting; +memset(&ftp_unpack_setting, 0, sizeof(unpack_setting_t)); +ftp_unpack_setting.package_max_length = DEFAULT_PACKAGE_MAX_LENGTH; +ftp_unpack_setting.mode = UNPACK_BY_DELIMITER; +ftp_unpack_setting.delimiter[0] = '\r'; +ftp_unpack_setting.delimiter[1] = '\n'; +ftp_unpack_setting.delimiter_bytes = 2; + +unpack_setting_t mqtt_unpack_setting = { + .mode = UNPACK_BY_LENGTH_FIELD, + .package_max_length = DEFAULT_PACKAGE_MAX_LENGTH, + .body_offset = 2, + .length_field_offset = 1, + .length_field_bytes = 1, + .length_field_coding = ENCODE_BY_VARINT, +}; + +unpack_setting_t grpc_unpack_setting = { + .mode = UNPACK_BY_LENGTH_FIELD, + .package_max_length = DEFAULT_PACKAGE_MAX_LENGTH, + .body_offset = 5, + .length_field_offset = 1, + .length_field_bytes = 4, + .length_field_coding = ENCODE_BY_BIG_ENDIAN, +}; +*/ + +//-----------------reconnect---------------------------------------- +#define DEFAULT_RECONNECT_MIN_DELAY 1000 // ms +#define DEFAULT_RECONNECT_MAX_DELAY 60000 // ms +#define DEFAULT_RECONNECT_DELAY_POLICY 2 // exponential +#define DEFAULT_RECONNECT_MAX_RETRY_CNT INFINITE +typedef struct reconn_setting_s { + uint32_t min_delay; // ms + uint32_t max_delay; // ms + uint32_t cur_delay; // ms + /* + * @delay_policy + * 0: fixed + * min_delay=3s => 3,3,3... + * 1: linear + * min_delay=3s max_delay=10s => 3,6,9,10,10... + * other: exponential + * min_delay=3s max_delay=60s delay_policy=2 => 3,6,12,24,48,60,60... + */ + uint32_t delay_policy; + uint32_t max_retry_cnt; + uint32_t cur_retry_cnt; + +#ifdef __cplusplus + reconn_setting_s() { + min_delay = DEFAULT_RECONNECT_MIN_DELAY; + max_delay = DEFAULT_RECONNECT_MAX_DELAY; + cur_delay = 0; + // 1,2,4,8,16,32,60,60... + delay_policy = DEFAULT_RECONNECT_DELAY_POLICY; + max_retry_cnt = DEFAULT_RECONNECT_MAX_RETRY_CNT; + cur_retry_cnt = 0; + } +#endif +} reconn_setting_t; + +HV_INLINE void reconn_setting_init(reconn_setting_t* reconn) { + reconn->min_delay = DEFAULT_RECONNECT_MIN_DELAY; + reconn->max_delay = DEFAULT_RECONNECT_MAX_DELAY; + reconn->cur_delay = 0; + // 1,2,4,8,16,32,60,60... + reconn->delay_policy = DEFAULT_RECONNECT_DELAY_POLICY; + reconn->max_retry_cnt = DEFAULT_RECONNECT_MAX_RETRY_CNT; + reconn->cur_retry_cnt = 0; +} + +HV_INLINE void reconn_setting_reset(reconn_setting_t* reconn) { + reconn->cur_delay = 0; + reconn->cur_retry_cnt = 0; +} + +HV_INLINE bool reconn_setting_can_retry(reconn_setting_t* reconn) { + ++reconn->cur_retry_cnt; + return reconn->max_retry_cnt == INFINITE || + reconn->cur_retry_cnt < reconn->max_retry_cnt; +} + +HV_INLINE uint32_t reconn_setting_calc_delay(reconn_setting_t* reconn) { + if (reconn->delay_policy == 0) { + // fixed + reconn->cur_delay = reconn->min_delay; + } else if (reconn->delay_policy == 1) { + // linear + reconn->cur_delay += reconn->min_delay; + } else { + // exponential + reconn->cur_delay *= reconn->delay_policy; + } + reconn->cur_delay = MAX(reconn->cur_delay, reconn->min_delay); + reconn->cur_delay = MIN(reconn->cur_delay, reconn->max_delay); + return reconn->cur_delay; +} + +//-----------------LoadBalance------------------------------------- +typedef enum { + LB_RoundRobin, + LB_Random, + LB_LeastConnections, + LB_IpHash, + LB_UrlHash, +} load_balance_e; + +//-----------------rudp--------------------------------------------- +#if WITH_KCP +#define WITH_RUDP 1 +#endif + +#if WITH_RUDP +// NOTE: hio_close_rudp is thread-safe. +HV_EXPORT int hio_close_rudp(hio_t* io, struct sockaddr* peeraddr DEFAULT(NULL)); +#endif + +#if WITH_KCP +typedef struct kcp_setting_s { + // ikcp_create(conv, ...) + unsigned int conv; + // ikcp_nodelay(kcp, nodelay, interval, fastresend, nocwnd) + int nodelay; + int interval; + int fastresend; + int nocwnd; + // ikcp_wndsize(kcp, sndwnd, rcvwnd) + int sndwnd; + int rcvwnd; + // ikcp_setmtu(kcp, mtu) + int mtu; + // ikcp_update + int update_interval; + +#ifdef __cplusplus + kcp_setting_s() { + conv = 0x11223344; + // normal mode + nodelay = 0; + interval = 40; + fastresend = 0; + nocwnd = 0; + // fast mode + // nodelay = 1; + // interval = 10; + // fastresend = 2; + // nocwnd = 1; + + sndwnd = 0; + rcvwnd = 0; + mtu = 1400; + update_interval = 10; // ms + } +#endif +} kcp_setting_t; + +HV_INLINE void kcp_setting_init_with_normal_mode(kcp_setting_t* setting) { + memset(setting, 0, sizeof(kcp_setting_t)); + setting->nodelay = 0; + setting->interval = 40; + setting->fastresend = 0; + setting->nocwnd = 0; +} + +HV_INLINE void kcp_setting_init_with_fast_mode(kcp_setting_t* setting) { + memset(setting, 0, sizeof(kcp_setting_t)); + setting->nodelay = 0; + setting->interval = 30; + setting->fastresend = 2; + setting->nocwnd = 1; +} + +HV_INLINE void kcp_setting_init_with_fast2_mode(kcp_setting_t* setting) { + memset(setting, 0, sizeof(kcp_setting_t)); + setting->nodelay = 1; + setting->interval = 20; + setting->fastresend = 2; + setting->nocwnd = 1; +} + +HV_INLINE void kcp_setting_init_with_fast3_mode(kcp_setting_t* setting) { + memset(setting, 0, sizeof(kcp_setting_t)); + setting->nodelay = 1; + setting->interval = 10; + setting->fastresend = 2; + setting->nocwnd = 1; +} + +// @see examples/udp_echo_server.c => #define TEST_KCP 1 +HV_EXPORT int hio_set_kcp(hio_t* io, kcp_setting_t* setting DEFAULT(NULL)); +#endif + +END_EXTERN_C + +#endif // HV_LOOP_H_ diff --git a/ww/libhv/event/iocp.c b/ww/libhv/event/iocp.c new file mode 100644 index 00000000..caef1cfd --- /dev/null +++ b/ww/libhv/event/iocp.c @@ -0,0 +1,81 @@ +#include "iowatcher.h" + +#ifdef EVENT_IOCP +#include "hplatform.h" +#include "hdef.h" + +#include "hevent.h" +#include "overlapio.h" + +typedef struct iocp_ctx_s { + HANDLE iocp; +} iocp_ctx_t; + +int iowatcher_init(hloop_t* loop) { + if (loop->iowatcher) return 0; + iocp_ctx_t* iocp_ctx; + HV_ALLOC_SIZEOF(iocp_ctx); + iocp_ctx->iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); + loop->iowatcher = iocp_ctx; + return 0; +} + +int iowatcher_cleanup(hloop_t* loop) { + if (loop->iowatcher == NULL) return 0; + iocp_ctx_t* iocp_ctx = (iocp_ctx_t*)loop->iowatcher; + CloseHandle(iocp_ctx->iocp); + HV_FREE(loop->iowatcher); + return 0; +} + +int iowatcher_add_event(hloop_t* loop, int fd, int events) { + if (loop->iowatcher == NULL) { + iowatcher_init(loop); + } + iocp_ctx_t* iocp_ctx = (iocp_ctx_t*)loop->iowatcher; + hio_t* io = loop->ios.ptr[fd]; + if (io && io->events == 0 && events != 0) { + CreateIoCompletionPort((HANDLE)fd, iocp_ctx->iocp, 0, 0); + } + return 0; +} + +int iowatcher_del_event(hloop_t* loop, int fd, int events) { + hio_t* io = loop->ios.ptr[fd]; + if ((io->events & ~events) == 0) { + CancelIo((HANDLE)fd); + } + return 0; +} + +int iowatcher_poll_events(hloop_t* loop, int timeout) { + if (loop->iowatcher == NULL) return 0; + iocp_ctx_t* iocp_ctx = (iocp_ctx_t*)loop->iowatcher; + DWORD bytes = 0; + ULONG_PTR key = 0; + LPOVERLAPPED povlp = NULL; + BOOL bRet = GetQueuedCompletionStatus(iocp_ctx->iocp, &bytes, &key, &povlp, timeout); + int err = 0; + if (povlp == NULL) { + err = WSAGetLastError(); + if (err == WAIT_TIMEOUT || ERROR_NETNAME_DELETED || ERROR_OPERATION_ABORTED) { + return 0; + } + return -err; + } + hoverlapped_t* hovlp = (hoverlapped_t*)povlp; + hio_t* io = hovlp->io; + if (bRet == FALSE) { + err = WSAGetLastError(); + printd("iocp ret=%d err=%d bytes=%u\n", bRet, err, bytes); + // NOTE: when ConnectEx failed, err != 0 + hovlp->error = err; + } + // NOTE: when WSASend/WSARecv disconnect, bytes = 0 + hovlp->bytes = bytes; + io->hovlp = hovlp; + io->revents |= hovlp->event; + EVENT_PENDING(hovlp->io); + return 1; +} +#endif diff --git a/ww/libhv/event/iowatcher.h b/ww/libhv/event/iowatcher.h new file mode 100644 index 00000000..1c6853d6 --- /dev/null +++ b/ww/libhv/event/iowatcher.h @@ -0,0 +1,39 @@ +#ifndef IO_WATCHER_H_ +#define IO_WATCHER_H_ + +#include "hloop.h" + +#include "hplatform.h" +#if !defined(EVENT_SELECT) && \ + !defined(EVENT_POLL) && \ + !defined(EVENT_EPOLL) && \ + !defined(EVENT_KQUEUE) && \ + !defined(EVENT_IOCP) && \ + !defined(EVENT_PORT) && \ + !defined(EVENT_NOEVENT) +#ifdef OS_WIN + #if WITH_WEPOLL + #define EVENT_EPOLL // wepoll -> iocp + #else + #define EVENT_POLL // WSAPoll + #endif +#elif defined(OS_LINUX) +#define EVENT_EPOLL +#elif defined(OS_MAC) +#define EVENT_KQUEUE +#elif defined(OS_BSD) +#define EVENT_KQUEUE +#elif defined(OS_SOLARIS) +#define EVENT_PORT +#else +#define EVENT_SELECT +#endif +#endif + +int iowatcher_init(hloop_t* loop); +int iowatcher_cleanup(hloop_t* loop); +int iowatcher_add_event(hloop_t* loop, int fd, int events); +int iowatcher_del_event(hloop_t* loop, int fd, int events); +int iowatcher_poll_events(hloop_t* loop, int timeout); + +#endif diff --git a/ww/libhv/event/kcp/LICENSE b/ww/libhv/event/kcp/LICENSE new file mode 100644 index 00000000..4f9f28fd --- /dev/null +++ b/ww/libhv/event/kcp/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Lin Wei (skywind3000 at gmail.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/ww/libhv/event/kcp/hkcp.c b/ww/libhv/event/kcp/hkcp.c new file mode 100644 index 00000000..2c9fe6c2 --- /dev/null +++ b/ww/libhv/event/kcp/hkcp.c @@ -0,0 +1,150 @@ +#include "hkcp.h" + +#if WITH_KCP + +#include "hevent.h" +#include "hlog.h" +#include "hthread.h" + +static kcp_setting_t s_kcp_setting; + +static int __kcp_output(const char* buf, int len, ikcpcb* ikcp, void* userdata) { + // printf("ikcp_output len=%d\n", len); + rudp_entry_t* rudp = (rudp_entry_t*)userdata; + assert(rudp != NULL && rudp->io != NULL); + int nsend = sendto(rudp->io->fd, buf, len, 0, &rudp->addr.sa, SOCKADDR_LEN(&rudp->addr)); + // printf("sendto nsend=%d\n", nsend); + return nsend; +} + +static void __kcp_update_timer_cb(htimer_t* timer) { + rudp_entry_t* rudp = (rudp_entry_t*)timer->privdata; + assert(rudp != NULL && rudp->io != NULL && rudp->kcp.ikcp != NULL); + ikcp_update(rudp->kcp.ikcp, (IUINT32)(rudp->io->loop->cur_hrtime / 1000)); +} + +void kcp_release(kcp_t* kcp) { + if (kcp->ikcp == NULL) return; + if (kcp->update_timer) { + htimer_del(kcp->update_timer); + kcp->update_timer = NULL; + } + HV_FREE(kcp->readbuf.base); + kcp->readbuf.len = 0; + // printf("ikcp_release ikcp=%p\n", kcp->ikcp); + ikcp_release(kcp->ikcp); + kcp->ikcp = NULL; +} + +int hio_set_kcp(hio_t* io, kcp_setting_t* setting) { + io->io_type = HIO_TYPE_KCP; + io->kcp_setting = setting; + return 0; +} + +kcp_t* hio_get_kcp(hio_t* io, uint32_t conv) { + rudp_entry_t* rudp = hio_get_rudp(io); + assert(rudp != NULL); + kcp_t* kcp = &rudp->kcp; + if (kcp->ikcp != NULL) return kcp; + if (io->kcp_setting == NULL) { + io->kcp_setting = &s_kcp_setting; + } + kcp_setting_t* setting = io->kcp_setting; + kcp->ikcp = ikcp_create(conv, rudp); + // printf("ikcp_create conv=%u ikcp=%p\n", conv, kcp->ikcp); + kcp->ikcp->output = __kcp_output; + kcp->conv = conv; + if (setting->interval > 0) { + ikcp_nodelay(kcp->ikcp, setting->nodelay, setting->interval, setting->fastresend, setting->nocwnd); + } + if (setting->sndwnd > 0 && setting->rcvwnd > 0) { + ikcp_wndsize(kcp->ikcp, setting->sndwnd, setting->rcvwnd); + } + if (setting->mtu > 0) { + ikcp_setmtu(kcp->ikcp, setting->mtu); + } + if (kcp->update_timer == NULL) { + int update_interval = setting->update_interval; + if (update_interval == 0) { + update_interval = DEFAULT_KCP_UPDATE_INTERVAL; + } + kcp->update_timer = htimer_add(io->loop, __kcp_update_timer_cb, update_interval, INFINITE); + kcp->update_timer->privdata = rudp; + } + // NOTE: alloc kcp->readbuf when hio_read_kcp + return kcp; +} + +static void hio_write_kcp_event_cb(hevent_t* ev) { + hio_t* io = (hio_t*)ev->userdata; + hbuf_t* buf = (hbuf_t*)ev->privdata; + + hio_write_kcp(io, buf->base, buf->len); + + HV_FREE(buf); +} + +static int hio_write_kcp_async(hio_t* io, const void* data, size_t len) { + hbuf_t* buf = NULL; + HV_ALLOC(buf, sizeof(hbuf_t) + len); + buf->base = (char*)buf + sizeof(hbuf_t); + buf->len = len; + memcpy(buf->base, data, len); + + hevent_t ev; + memset(&ev, 0, sizeof(ev)); + ev.cb = hio_write_kcp_event_cb; + ev.userdata = io; + ev.privdata = buf; + hloop_post_event(io->loop, &ev); + return len; +} + +int hio_write_kcp(hio_t* io, const void* buf, size_t len) { + if (hv_gettid() != io->loop->tid) { + return hio_write_kcp_async(io, buf, len); + } + IUINT32 conv = io->kcp_setting ? io->kcp_setting->conv : 0; + kcp_t* kcp = hio_get_kcp(io, conv); + // printf("hio_write_kcp conv=%u=%u\n", conv, kcp->conv); + int nsend = ikcp_send(kcp->ikcp, (const char*)buf, len); + // printf("ikcp_send len=%d nsend=%d\n", (int)len, nsend); + if (nsend < 0) { + hloge("ikcp_send error: %d", nsend); + return nsend; + } + ikcp_update(kcp->ikcp, (IUINT32)io->loop->cur_hrtime / 1000); + return len; +} + +int hio_read_kcp (hio_t* io, void* buf, int readbytes) { + IUINT32 conv = ikcp_getconv(buf); + kcp_t* kcp = hio_get_kcp(io, conv); + // printf("hio_read_kcp conv=%u=%u\n", conv, kcp->conv); + if (kcp->conv != conv) { + hloge("recv invalid kcp packet!"); + hio_close_rudp(io, io->peeraddr); + return -1; + } + // printf("ikcp_input len=%d\n", readbytes); + int ret = ikcp_input(kcp->ikcp, (const char*)buf, readbytes); + // printf("ikcp_input ret=%d\n", ret); + if (ret != 0) { + return 0; + } + if (kcp->readbuf.base == NULL || kcp->readbuf.len == 0) { + kcp->readbuf.len = DEFAULT_KCP_READ_BUFSIZE; + HV_ALLOC(kcp->readbuf.base, kcp->readbuf.len); + } + while (1) { + int nrecv = ikcp_recv(kcp->ikcp, kcp->readbuf.base, kcp->readbuf.len); + // printf("ikcp_recv nrecv=%d\n", nrecv); + if (nrecv < 0) break; + hio_read_cb(io, kcp->readbuf.base, nrecv); + ret += nrecv; + } + return ret; +} + +#endif diff --git a/ww/libhv/event/kcp/hkcp.h b/ww/libhv/event/kcp/hkcp.h new file mode 100644 index 00000000..ca9a0615 --- /dev/null +++ b/ww/libhv/event/kcp/hkcp.h @@ -0,0 +1,30 @@ +#ifndef HV_KCP_H_ +#define HV_KCP_H_ + +#include "hloop.h" + +#if WITH_KCP + +#include "ikcp.h" +#include "hbuf.h" + +#define DEFAULT_KCP_UPDATE_INTERVAL 10 // ms +#define DEFAULT_KCP_READ_BUFSIZE 1400 + +typedef struct kcp_s { + ikcpcb* ikcp; + uint32_t conv; + htimer_t* update_timer; + hbuf_t readbuf; +} kcp_t; + +// NOTE: kcp_create in hio_get_kcp +void kcp_release(kcp_t* kcp); + +kcp_t* hio_get_kcp (hio_t* io, uint32_t conv); +int hio_read_kcp (hio_t* io, void* buf, int readbytes); +int hio_write_kcp(hio_t* io, const void* buf, size_t len); + +#endif + +#endif // HV_KCP_H_ diff --git a/ww/libhv/event/kcp/ikcp.c b/ww/libhv/event/kcp/ikcp.c new file mode 100644 index 00000000..fd4cf7d8 --- /dev/null +++ b/ww/libhv/event/kcp/ikcp.c @@ -0,0 +1,1299 @@ +//===================================================================== +// +// KCP - A Better ARQ Protocol Implementation +// skywind3000 (at) gmail.com, 2010-2011 +// +// Features: +// + Average RTT reduce 30% - 40% vs traditional ARQ like tcp. +// + Maximum RTT reduce three times vs tcp. +// + Lightweight, distributed as a single source file. +// +//===================================================================== +#include "ikcp.h" + +#include +#include +#include +#include +#include + + + +//===================================================================== +// KCP BASIC +//===================================================================== +const IUINT32 IKCP_RTO_NDL = 30; // no delay min rto +const IUINT32 IKCP_RTO_MIN = 100; // normal min rto +const IUINT32 IKCP_RTO_DEF = 200; +const IUINT32 IKCP_RTO_MAX = 60000; +const IUINT32 IKCP_CMD_PUSH = 81; // cmd: push data +const IUINT32 IKCP_CMD_ACK = 82; // cmd: ack +const IUINT32 IKCP_CMD_WASK = 83; // cmd: window probe (ask) +const IUINT32 IKCP_CMD_WINS = 84; // cmd: window size (tell) +const IUINT32 IKCP_ASK_SEND = 1; // need to send IKCP_CMD_WASK +const IUINT32 IKCP_ASK_TELL = 2; // need to send IKCP_CMD_WINS +const IUINT32 IKCP_WND_SND = 32; +const IUINT32 IKCP_WND_RCV = 128; // must >= max fragment size +const IUINT32 IKCP_MTU_DEF = 1400; +const IUINT32 IKCP_ACK_FAST = 3; +const IUINT32 IKCP_INTERVAL = 100; +const IUINT32 IKCP_OVERHEAD = 24; +const IUINT32 IKCP_DEADLINK = 20; +const IUINT32 IKCP_THRESH_INIT = 2; +const IUINT32 IKCP_THRESH_MIN = 2; +const IUINT32 IKCP_PROBE_INIT = 7000; // 7 secs to probe window size +const IUINT32 IKCP_PROBE_LIMIT = 120000; // up to 120 secs to probe window +const IUINT32 IKCP_FASTACK_LIMIT = 5; // max times to trigger fastack + + +//--------------------------------------------------------------------- +// encode / decode +//--------------------------------------------------------------------- + +/* encode 8 bits unsigned int */ +static inline char *ikcp_encode8u(char *p, unsigned char c) +{ + *(unsigned char*)p++ = c; + return p; +} + +/* decode 8 bits unsigned int */ +static inline const char *ikcp_decode8u(const char *p, unsigned char *c) +{ + *c = *(unsigned char*)p++; + return p; +} + +/* encode 16 bits unsigned int (lsb) */ +static inline char *ikcp_encode16u(char *p, unsigned short w) +{ +#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN + *(unsigned char*)(p + 0) = (w & 255); + *(unsigned char*)(p + 1) = (w >> 8); +#else + memcpy(p, &w, 2); +#endif + p += 2; + return p; +} + +/* decode 16 bits unsigned int (lsb) */ +static inline const char *ikcp_decode16u(const char *p, unsigned short *w) +{ +#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN + *w = *(const unsigned char*)(p + 1); + *w = *(const unsigned char*)(p + 0) + (*w << 8); +#else + memcpy(w, p, 2); +#endif + p += 2; + return p; +} + +/* encode 32 bits unsigned int (lsb) */ +static inline char *ikcp_encode32u(char *p, IUINT32 l) +{ +#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN + *(unsigned char*)(p + 0) = (unsigned char)((l >> 0) & 0xff); + *(unsigned char*)(p + 1) = (unsigned char)((l >> 8) & 0xff); + *(unsigned char*)(p + 2) = (unsigned char)((l >> 16) & 0xff); + *(unsigned char*)(p + 3) = (unsigned char)((l >> 24) & 0xff); +#else + memcpy(p, &l, 4); +#endif + p += 4; + return p; +} + +/* decode 32 bits unsigned int (lsb) */ +static inline const char *ikcp_decode32u(const char *p, IUINT32 *l) +{ +#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN + *l = *(const unsigned char*)(p + 3); + *l = *(const unsigned char*)(p + 2) + (*l << 8); + *l = *(const unsigned char*)(p + 1) + (*l << 8); + *l = *(const unsigned char*)(p + 0) + (*l << 8); +#else + memcpy(l, p, 4); +#endif + p += 4; + return p; +} + +static inline IUINT32 _imin_(IUINT32 a, IUINT32 b) { + return a <= b ? a : b; +} + +static inline IUINT32 _imax_(IUINT32 a, IUINT32 b) { + return a >= b ? a : b; +} + +static inline IUINT32 _ibound_(IUINT32 lower, IUINT32 middle, IUINT32 upper) +{ + return _imin_(_imax_(lower, middle), upper); +} + +static inline long _itimediff(IUINT32 later, IUINT32 earlier) +{ + return ((IINT32)(later - earlier)); +} + +//--------------------------------------------------------------------- +// manage segment +//--------------------------------------------------------------------- +typedef struct IKCPSEG IKCPSEG; + +static void* (*ikcp_malloc_hook)(size_t) = NULL; +static void (*ikcp_free_hook)(void *) = NULL; + +// internal malloc +static void* ikcp_malloc(size_t size) { + if (ikcp_malloc_hook) + return ikcp_malloc_hook(size); + return malloc(size); +} + +// internal free +static void ikcp_free(void *ptr) { + if (ikcp_free_hook) { + ikcp_free_hook(ptr); + } else { + free(ptr); + } +} + +// redefine allocator +void ikcp_allocator(void* (*new_malloc)(size_t), void (*new_free)(void*)) +{ + ikcp_malloc_hook = new_malloc; + ikcp_free_hook = new_free; +} + +// allocate a new kcp segment +static IKCPSEG* ikcp_segment_new(ikcpcb *kcp, int size) +{ + return (IKCPSEG*)ikcp_malloc(sizeof(IKCPSEG) + size); +} + +// delete a segment +static void ikcp_segment_delete(ikcpcb *kcp, IKCPSEG *seg) +{ + ikcp_free(seg); +} + +// write log +void ikcp_log(ikcpcb *kcp, int mask, const char *fmt, ...) +{ + char buffer[1024]; + va_list argptr; + if ((mask & kcp->logmask) == 0 || kcp->writelog == 0) return; + va_start(argptr, fmt); + vsprintf(buffer, fmt, argptr); + va_end(argptr); + kcp->writelog(buffer, kcp, kcp->user); +} + +// check log mask +static int ikcp_canlog(const ikcpcb *kcp, int mask) +{ + if ((mask & kcp->logmask) == 0 || kcp->writelog == NULL) return 0; + return 1; +} + +// output segment +static int ikcp_output(ikcpcb *kcp, const void *data, int size) +{ + assert(kcp); + assert(kcp->output); + if (ikcp_canlog(kcp, IKCP_LOG_OUTPUT)) { + ikcp_log(kcp, IKCP_LOG_OUTPUT, "[RO] %ld bytes", (long)size); + } + if (size == 0) return 0; + return kcp->output((const char*)data, size, kcp, kcp->user); +} + +// output queue +void ikcp_qprint(const char *name, const struct IQUEUEHEAD *head) +{ +#if 0 + const struct IQUEUEHEAD *p; + printf("<%s>: [", name); + for (p = head->next; p != head; p = p->next) { + const IKCPSEG *seg = iqueue_entry(p, const IKCPSEG, node); + printf("(%lu %d)", (unsigned long)seg->sn, (int)(seg->ts % 10000)); + if (p->next != head) printf(","); + } + printf("]\n"); +#endif +} + + +//--------------------------------------------------------------------- +// create a new kcpcb +//--------------------------------------------------------------------- +ikcpcb* ikcp_create(IUINT32 conv, void *user) +{ + ikcpcb *kcp = (ikcpcb*)ikcp_malloc(sizeof(struct IKCPCB)); + if (kcp == NULL) return NULL; + kcp->conv = conv; + kcp->user = user; + kcp->snd_una = 0; + kcp->snd_nxt = 0; + kcp->rcv_nxt = 0; + kcp->ts_recent = 0; + kcp->ts_lastack = 0; + kcp->ts_probe = 0; + kcp->probe_wait = 0; + kcp->snd_wnd = IKCP_WND_SND; + kcp->rcv_wnd = IKCP_WND_RCV; + kcp->rmt_wnd = IKCP_WND_RCV; + kcp->cwnd = 0; + kcp->incr = 0; + kcp->probe = 0; + kcp->mtu = IKCP_MTU_DEF; + kcp->mss = kcp->mtu - IKCP_OVERHEAD; + kcp->stream = 0; + + kcp->buffer = (char*)ikcp_malloc((kcp->mtu + IKCP_OVERHEAD) * 3); + if (kcp->buffer == NULL) { + ikcp_free(kcp); + return NULL; + } + + iqueue_init(&kcp->snd_queue); + iqueue_init(&kcp->rcv_queue); + iqueue_init(&kcp->snd_buf); + iqueue_init(&kcp->rcv_buf); + kcp->nrcv_buf = 0; + kcp->nsnd_buf = 0; + kcp->nrcv_que = 0; + kcp->nsnd_que = 0; + kcp->state = 0; + kcp->acklist = NULL; + kcp->ackblock = 0; + kcp->ackcount = 0; + kcp->rx_srtt = 0; + kcp->rx_rttval = 0; + kcp->rx_rto = IKCP_RTO_DEF; + kcp->rx_minrto = IKCP_RTO_MIN; + kcp->current = 0; + kcp->interval = IKCP_INTERVAL; + kcp->ts_flush = IKCP_INTERVAL; + kcp->nodelay = 0; + kcp->updated = 0; + kcp->logmask = 0; + kcp->ssthresh = IKCP_THRESH_INIT; + kcp->fastresend = 0; + kcp->fastlimit = IKCP_FASTACK_LIMIT; + kcp->nocwnd = 0; + kcp->xmit = 0; + kcp->dead_link = IKCP_DEADLINK; + kcp->output = NULL; + kcp->writelog = NULL; + + return kcp; +} + + +//--------------------------------------------------------------------- +// release a new kcpcb +//--------------------------------------------------------------------- +void ikcp_release(ikcpcb *kcp) +{ + assert(kcp); + if (kcp) { + IKCPSEG *seg; + while (!iqueue_is_empty(&kcp->snd_buf)) { + seg = iqueue_entry(kcp->snd_buf.next, IKCPSEG, node); + iqueue_del(&seg->node); + ikcp_segment_delete(kcp, seg); + } + while (!iqueue_is_empty(&kcp->rcv_buf)) { + seg = iqueue_entry(kcp->rcv_buf.next, IKCPSEG, node); + iqueue_del(&seg->node); + ikcp_segment_delete(kcp, seg); + } + while (!iqueue_is_empty(&kcp->snd_queue)) { + seg = iqueue_entry(kcp->snd_queue.next, IKCPSEG, node); + iqueue_del(&seg->node); + ikcp_segment_delete(kcp, seg); + } + while (!iqueue_is_empty(&kcp->rcv_queue)) { + seg = iqueue_entry(kcp->rcv_queue.next, IKCPSEG, node); + iqueue_del(&seg->node); + ikcp_segment_delete(kcp, seg); + } + if (kcp->buffer) { + ikcp_free(kcp->buffer); + } + if (kcp->acklist) { + ikcp_free(kcp->acklist); + } + + kcp->nrcv_buf = 0; + kcp->nsnd_buf = 0; + kcp->nrcv_que = 0; + kcp->nsnd_que = 0; + kcp->ackcount = 0; + kcp->buffer = NULL; + kcp->acklist = NULL; + ikcp_free(kcp); + } +} + + +//--------------------------------------------------------------------- +// set output callback, which will be invoked by kcp +//--------------------------------------------------------------------- +void ikcp_setoutput(ikcpcb *kcp, int (*output)(const char *buf, int len, + ikcpcb *kcp, void *user)) +{ + kcp->output = output; +} + + +//--------------------------------------------------------------------- +// user/upper level recv: returns size, returns below zero for EAGAIN +//--------------------------------------------------------------------- +int ikcp_recv(ikcpcb *kcp, char *buffer, int len) +{ + struct IQUEUEHEAD *p; + int ispeek = (len < 0)? 1 : 0; + int peeksize; + int recover = 0; + IKCPSEG *seg; + assert(kcp); + + if (iqueue_is_empty(&kcp->rcv_queue)) + return -1; + + if (len < 0) len = -len; + + peeksize = ikcp_peeksize(kcp); + + if (peeksize < 0) + return -2; + + if (peeksize > len) + return -3; + + if (kcp->nrcv_que >= kcp->rcv_wnd) + recover = 1; + + // merge fragment + for (len = 0, p = kcp->rcv_queue.next; p != &kcp->rcv_queue; ) { + int fragment; + seg = iqueue_entry(p, IKCPSEG, node); + p = p->next; + + if (buffer) { + memcpy(buffer, seg->data, seg->len); + buffer += seg->len; + } + + len += seg->len; + fragment = seg->frg; + + if (ikcp_canlog(kcp, IKCP_LOG_RECV)) { + ikcp_log(kcp, IKCP_LOG_RECV, "recv sn=%lu", (unsigned long)seg->sn); + } + + if (ispeek == 0) { + iqueue_del(&seg->node); + ikcp_segment_delete(kcp, seg); + kcp->nrcv_que--; + } + + if (fragment == 0) + break; + } + + assert(len == peeksize); + + // move available data from rcv_buf -> rcv_queue + while (! iqueue_is_empty(&kcp->rcv_buf)) { + seg = iqueue_entry(kcp->rcv_buf.next, IKCPSEG, node); + if (seg->sn == kcp->rcv_nxt && kcp->nrcv_que < kcp->rcv_wnd) { + iqueue_del(&seg->node); + kcp->nrcv_buf--; + iqueue_add_tail(&seg->node, &kcp->rcv_queue); + kcp->nrcv_que++; + kcp->rcv_nxt++; + } else { + break; + } + } + + // fast recover + if (kcp->nrcv_que < kcp->rcv_wnd && recover) { + // ready to send back IKCP_CMD_WINS in ikcp_flush + // tell remote my window size + kcp->probe |= IKCP_ASK_TELL; + } + + return len; +} + + +//--------------------------------------------------------------------- +// peek data size +//--------------------------------------------------------------------- +int ikcp_peeksize(const ikcpcb *kcp) +{ + struct IQUEUEHEAD *p; + IKCPSEG *seg; + int length = 0; + + assert(kcp); + + if (iqueue_is_empty(&kcp->rcv_queue)) return -1; + + seg = iqueue_entry(kcp->rcv_queue.next, IKCPSEG, node); + if (seg->frg == 0) return seg->len; + + if (kcp->nrcv_que < seg->frg + 1) return -1; + + for (p = kcp->rcv_queue.next; p != &kcp->rcv_queue; p = p->next) { + seg = iqueue_entry(p, IKCPSEG, node); + length += seg->len; + if (seg->frg == 0) break; + } + + return length; +} + + +//--------------------------------------------------------------------- +// user/upper level send, returns below zero for error +//--------------------------------------------------------------------- +int ikcp_send(ikcpcb *kcp, const char *buffer, int len) +{ + IKCPSEG *seg; + int count, i; + + assert(kcp->mss > 0); + if (len < 0) return -1; + + // append to previous segment in streaming mode (if possible) + if (kcp->stream != 0) { + if (!iqueue_is_empty(&kcp->snd_queue)) { + IKCPSEG *old = iqueue_entry(kcp->snd_queue.prev, IKCPSEG, node); + if (old->len < kcp->mss) { + int capacity = kcp->mss - old->len; + int extend = (len < capacity)? len : capacity; + seg = ikcp_segment_new(kcp, old->len + extend); + assert(seg); + if (seg == NULL) { + return -2; + } + iqueue_add_tail(&seg->node, &kcp->snd_queue); + memcpy(seg->data, old->data, old->len); + if (buffer) { + memcpy(seg->data + old->len, buffer, extend); + buffer += extend; + } + seg->len = old->len + extend; + seg->frg = 0; + len -= extend; + iqueue_del_init(&old->node); + ikcp_segment_delete(kcp, old); + } + } + if (len <= 0) { + return 0; + } + } + + if (len <= (int)kcp->mss) count = 1; + else count = (len + kcp->mss - 1) / kcp->mss; + + if (count >= (int)IKCP_WND_RCV) return -2; + + if (count == 0) count = 1; + + // fragment + for (i = 0; i < count; i++) { + int size = len > (int)kcp->mss ? (int)kcp->mss : len; + seg = ikcp_segment_new(kcp, size); + assert(seg); + if (seg == NULL) { + return -2; + } + if (buffer && len > 0) { + memcpy(seg->data, buffer, size); + } + seg->len = size; + seg->frg = (kcp->stream == 0)? (count - i - 1) : 0; + iqueue_init(&seg->node); + iqueue_add_tail(&seg->node, &kcp->snd_queue); + kcp->nsnd_que++; + if (buffer) { + buffer += size; + } + len -= size; + } + + return 0; +} + + +//--------------------------------------------------------------------- +// parse ack +//--------------------------------------------------------------------- +static void ikcp_update_ack(ikcpcb *kcp, IINT32 rtt) +{ + IINT32 rto = 0; + if (kcp->rx_srtt == 0) { + kcp->rx_srtt = rtt; + kcp->rx_rttval = rtt / 2; + } else { + long delta = rtt - kcp->rx_srtt; + if (delta < 0) delta = -delta; + kcp->rx_rttval = (3 * kcp->rx_rttval + delta) / 4; + kcp->rx_srtt = (7 * kcp->rx_srtt + rtt) / 8; + if (kcp->rx_srtt < 1) kcp->rx_srtt = 1; + } + rto = kcp->rx_srtt + _imax_(kcp->interval, 4 * kcp->rx_rttval); + kcp->rx_rto = _ibound_(kcp->rx_minrto, rto, IKCP_RTO_MAX); +} + +static void ikcp_shrink_buf(ikcpcb *kcp) +{ + struct IQUEUEHEAD *p = kcp->snd_buf.next; + if (p != &kcp->snd_buf) { + IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); + kcp->snd_una = seg->sn; + } else { + kcp->snd_una = kcp->snd_nxt; + } +} + +static void ikcp_parse_ack(ikcpcb *kcp, IUINT32 sn) +{ + struct IQUEUEHEAD *p, *next; + + if (_itimediff(sn, kcp->snd_una) < 0 || _itimediff(sn, kcp->snd_nxt) >= 0) + return; + + for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = next) { + IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); + next = p->next; + if (sn == seg->sn) { + iqueue_del(p); + ikcp_segment_delete(kcp, seg); + kcp->nsnd_buf--; + break; + } + if (_itimediff(sn, seg->sn) < 0) { + break; + } + } +} + +static void ikcp_parse_una(ikcpcb *kcp, IUINT32 una) +{ + struct IQUEUEHEAD *p, *next; + for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = next) { + IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); + next = p->next; + if (_itimediff(una, seg->sn) > 0) { + iqueue_del(p); + ikcp_segment_delete(kcp, seg); + kcp->nsnd_buf--; + } else { + break; + } + } +} + +static void ikcp_parse_fastack(ikcpcb *kcp, IUINT32 sn, IUINT32 ts) +{ + struct IQUEUEHEAD *p, *next; + + if (_itimediff(sn, kcp->snd_una) < 0 || _itimediff(sn, kcp->snd_nxt) >= 0) + return; + + for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = next) { + IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); + next = p->next; + if (_itimediff(sn, seg->sn) < 0) { + break; + } + else if (sn != seg->sn) { + #ifndef IKCP_FASTACK_CONSERVE + seg->fastack++; + #else + if (_itimediff(ts, seg->ts) >= 0) + seg->fastack++; + #endif + } + } +} + + +//--------------------------------------------------------------------- +// ack append +//--------------------------------------------------------------------- +static void ikcp_ack_push(ikcpcb *kcp, IUINT32 sn, IUINT32 ts) +{ + IUINT32 newsize = kcp->ackcount + 1; + IUINT32 *ptr; + + if (newsize > kcp->ackblock) { + IUINT32 *acklist; + IUINT32 newblock; + + for (newblock = 8; newblock < newsize; newblock <<= 1); + acklist = (IUINT32*)ikcp_malloc(newblock * sizeof(IUINT32) * 2); + + if (acklist == NULL) { + assert(acklist != NULL); + abort(); + } + + if (kcp->acklist != NULL) { + IUINT32 x; + for (x = 0; x < kcp->ackcount; x++) { + acklist[x * 2 + 0] = kcp->acklist[x * 2 + 0]; + acklist[x * 2 + 1] = kcp->acklist[x * 2 + 1]; + } + ikcp_free(kcp->acklist); + } + + kcp->acklist = acklist; + kcp->ackblock = newblock; + } + + ptr = &kcp->acklist[kcp->ackcount * 2]; + ptr[0] = sn; + ptr[1] = ts; + kcp->ackcount++; +} + +static void ikcp_ack_get(const ikcpcb *kcp, int p, IUINT32 *sn, IUINT32 *ts) +{ + if (sn) sn[0] = kcp->acklist[p * 2 + 0]; + if (ts) ts[0] = kcp->acklist[p * 2 + 1]; +} + + +//--------------------------------------------------------------------- +// parse data +//--------------------------------------------------------------------- +void ikcp_parse_data(ikcpcb *kcp, IKCPSEG *newseg) +{ + struct IQUEUEHEAD *p, *prev; + IUINT32 sn = newseg->sn; + int repeat = 0; + + if (_itimediff(sn, kcp->rcv_nxt + kcp->rcv_wnd) >= 0 || + _itimediff(sn, kcp->rcv_nxt) < 0) { + ikcp_segment_delete(kcp, newseg); + return; + } + + for (p = kcp->rcv_buf.prev; p != &kcp->rcv_buf; p = prev) { + IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); + prev = p->prev; + if (seg->sn == sn) { + repeat = 1; + break; + } + if (_itimediff(sn, seg->sn) > 0) { + break; + } + } + + if (repeat == 0) { + iqueue_init(&newseg->node); + iqueue_add(&newseg->node, p); + kcp->nrcv_buf++; + } else { + ikcp_segment_delete(kcp, newseg); + } + +#if 0 + ikcp_qprint("rcvbuf", &kcp->rcv_buf); + printf("rcv_nxt=%lu\n", kcp->rcv_nxt); +#endif + + // move available data from rcv_buf -> rcv_queue + while (! iqueue_is_empty(&kcp->rcv_buf)) { + IKCPSEG *seg = iqueue_entry(kcp->rcv_buf.next, IKCPSEG, node); + if (seg->sn == kcp->rcv_nxt && kcp->nrcv_que < kcp->rcv_wnd) { + iqueue_del(&seg->node); + kcp->nrcv_buf--; + iqueue_add_tail(&seg->node, &kcp->rcv_queue); + kcp->nrcv_que++; + kcp->rcv_nxt++; + } else { + break; + } + } + +#if 0 + ikcp_qprint("queue", &kcp->rcv_queue); + printf("rcv_nxt=%lu\n", kcp->rcv_nxt); +#endif + +#if 1 +// printf("snd(buf=%d, queue=%d)\n", kcp->nsnd_buf, kcp->nsnd_que); +// printf("rcv(buf=%d, queue=%d)\n", kcp->nrcv_buf, kcp->nrcv_que); +#endif +} + + +//--------------------------------------------------------------------- +// input data +//--------------------------------------------------------------------- +int ikcp_input(ikcpcb *kcp, const char *data, long size) +{ + IUINT32 prev_una = kcp->snd_una; + IUINT32 maxack = 0, latest_ts = 0; + int flag = 0; + + if (ikcp_canlog(kcp, IKCP_LOG_INPUT)) { + ikcp_log(kcp, IKCP_LOG_INPUT, "[RI] %d bytes", (int)size); + } + + if (data == NULL || (int)size < (int)IKCP_OVERHEAD) return -1; + + while (1) { + IUINT32 ts, sn, len, una, conv; + IUINT16 wnd; + IUINT8 cmd, frg; + IKCPSEG *seg; + + if (size < (int)IKCP_OVERHEAD) break; + + data = ikcp_decode32u(data, &conv); + if (conv != kcp->conv) return -1; + + data = ikcp_decode8u(data, &cmd); + data = ikcp_decode8u(data, &frg); + data = ikcp_decode16u(data, &wnd); + data = ikcp_decode32u(data, &ts); + data = ikcp_decode32u(data, &sn); + data = ikcp_decode32u(data, &una); + data = ikcp_decode32u(data, &len); + + size -= IKCP_OVERHEAD; + + if ((long)size < (long)len || (int)len < 0) return -2; + + if (cmd != IKCP_CMD_PUSH && cmd != IKCP_CMD_ACK && + cmd != IKCP_CMD_WASK && cmd != IKCP_CMD_WINS) + return -3; + + kcp->rmt_wnd = wnd; + ikcp_parse_una(kcp, una); + ikcp_shrink_buf(kcp); + + if (cmd == IKCP_CMD_ACK) { + if (_itimediff(kcp->current, ts) >= 0) { + ikcp_update_ack(kcp, _itimediff(kcp->current, ts)); + } + ikcp_parse_ack(kcp, sn); + ikcp_shrink_buf(kcp); + if (flag == 0) { + flag = 1; + maxack = sn; + latest_ts = ts; + } else { + if (_itimediff(sn, maxack) > 0) { + #ifndef IKCP_FASTACK_CONSERVE + maxack = sn; + latest_ts = ts; + #else + if (_itimediff(ts, latest_ts) > 0) { + maxack = sn; + latest_ts = ts; + } + #endif + } + } + if (ikcp_canlog(kcp, IKCP_LOG_IN_ACK)) { + ikcp_log(kcp, IKCP_LOG_IN_ACK, + "input ack: sn=%lu rtt=%ld rto=%ld", (unsigned long)sn, + (long)_itimediff(kcp->current, ts), + (long)kcp->rx_rto); + } + } + else if (cmd == IKCP_CMD_PUSH) { + if (ikcp_canlog(kcp, IKCP_LOG_IN_DATA)) { + ikcp_log(kcp, IKCP_LOG_IN_DATA, + "input psh: sn=%lu ts=%lu", (unsigned long)sn, (unsigned long)ts); + } + if (_itimediff(sn, kcp->rcv_nxt + kcp->rcv_wnd) < 0) { + ikcp_ack_push(kcp, sn, ts); + if (_itimediff(sn, kcp->rcv_nxt) >= 0) { + seg = ikcp_segment_new(kcp, len); + seg->conv = conv; + seg->cmd = cmd; + seg->frg = frg; + seg->wnd = wnd; + seg->ts = ts; + seg->sn = sn; + seg->una = una; + seg->len = len; + + if (len > 0) { + memcpy(seg->data, data, len); + } + + ikcp_parse_data(kcp, seg); + } + } + } + else if (cmd == IKCP_CMD_WASK) { + // ready to send back IKCP_CMD_WINS in ikcp_flush + // tell remote my window size + kcp->probe |= IKCP_ASK_TELL; + if (ikcp_canlog(kcp, IKCP_LOG_IN_PROBE)) { + ikcp_log(kcp, IKCP_LOG_IN_PROBE, "input probe"); + } + } + else if (cmd == IKCP_CMD_WINS) { + // do nothing + if (ikcp_canlog(kcp, IKCP_LOG_IN_WINS)) { + ikcp_log(kcp, IKCP_LOG_IN_WINS, + "input wins: %lu", (unsigned long)(wnd)); + } + } + else { + return -3; + } + + data += len; + size -= len; + } + + if (flag != 0) { + ikcp_parse_fastack(kcp, maxack, latest_ts); + } + + if (_itimediff(kcp->snd_una, prev_una) > 0) { + if (kcp->cwnd < kcp->rmt_wnd) { + IUINT32 mss = kcp->mss; + if (kcp->cwnd < kcp->ssthresh) { + kcp->cwnd++; + kcp->incr += mss; + } else { + if (kcp->incr < mss) kcp->incr = mss; + kcp->incr += (mss * mss) / kcp->incr + (mss / 16); + if ((kcp->cwnd + 1) * mss <= kcp->incr) { + #if 1 + kcp->cwnd = (kcp->incr + mss - 1) / ((mss > 0)? mss : 1); + #else + kcp->cwnd++; + #endif + } + } + if (kcp->cwnd > kcp->rmt_wnd) { + kcp->cwnd = kcp->rmt_wnd; + kcp->incr = kcp->rmt_wnd * mss; + } + } + } + + return 0; +} + + +//--------------------------------------------------------------------- +// ikcp_encode_seg +//--------------------------------------------------------------------- +static char *ikcp_encode_seg(char *ptr, const IKCPSEG *seg) +{ + ptr = ikcp_encode32u(ptr, seg->conv); + ptr = ikcp_encode8u(ptr, (IUINT8)seg->cmd); + ptr = ikcp_encode8u(ptr, (IUINT8)seg->frg); + ptr = ikcp_encode16u(ptr, (IUINT16)seg->wnd); + ptr = ikcp_encode32u(ptr, seg->ts); + ptr = ikcp_encode32u(ptr, seg->sn); + ptr = ikcp_encode32u(ptr, seg->una); + ptr = ikcp_encode32u(ptr, seg->len); + return ptr; +} + +static int ikcp_wnd_unused(const ikcpcb *kcp) +{ + if (kcp->nrcv_que < kcp->rcv_wnd) { + return kcp->rcv_wnd - kcp->nrcv_que; + } + return 0; +} + + +//--------------------------------------------------------------------- +// ikcp_flush +//--------------------------------------------------------------------- +void ikcp_flush(ikcpcb *kcp) +{ + IUINT32 current = kcp->current; + char *buffer = kcp->buffer; + char *ptr = buffer; + int count, size, i; + IUINT32 resent, cwnd; + IUINT32 rtomin; + struct IQUEUEHEAD *p; + int change = 0; + int lost = 0; + IKCPSEG seg; + + // 'ikcp_update' haven't been called. + if (kcp->updated == 0) return; + + seg.conv = kcp->conv; + seg.cmd = IKCP_CMD_ACK; + seg.frg = 0; + seg.wnd = ikcp_wnd_unused(kcp); + seg.una = kcp->rcv_nxt; + seg.len = 0; + seg.sn = 0; + seg.ts = 0; + + // flush acknowledges + count = kcp->ackcount; + for (i = 0; i < count; i++) { + size = (int)(ptr - buffer); + if (size + (int)IKCP_OVERHEAD > (int)kcp->mtu) { + ikcp_output(kcp, buffer, size); + ptr = buffer; + } + ikcp_ack_get(kcp, i, &seg.sn, &seg.ts); + ptr = ikcp_encode_seg(ptr, &seg); + } + + kcp->ackcount = 0; + + // probe window size (if remote window size equals zero) + if (kcp->rmt_wnd == 0) { + if (kcp->probe_wait == 0) { + kcp->probe_wait = IKCP_PROBE_INIT; + kcp->ts_probe = kcp->current + kcp->probe_wait; + } + else { + if (_itimediff(kcp->current, kcp->ts_probe) >= 0) { + if (kcp->probe_wait < IKCP_PROBE_INIT) + kcp->probe_wait = IKCP_PROBE_INIT; + kcp->probe_wait += kcp->probe_wait / 2; + if (kcp->probe_wait > IKCP_PROBE_LIMIT) + kcp->probe_wait = IKCP_PROBE_LIMIT; + kcp->ts_probe = kcp->current + kcp->probe_wait; + kcp->probe |= IKCP_ASK_SEND; + } + } + } else { + kcp->ts_probe = 0; + kcp->probe_wait = 0; + } + + // flush window probing commands + if (kcp->probe & IKCP_ASK_SEND) { + seg.cmd = IKCP_CMD_WASK; + size = (int)(ptr - buffer); + if (size + (int)IKCP_OVERHEAD > (int)kcp->mtu) { + ikcp_output(kcp, buffer, size); + ptr = buffer; + } + ptr = ikcp_encode_seg(ptr, &seg); + } + + // flush window probing commands + if (kcp->probe & IKCP_ASK_TELL) { + seg.cmd = IKCP_CMD_WINS; + size = (int)(ptr - buffer); + if (size + (int)IKCP_OVERHEAD > (int)kcp->mtu) { + ikcp_output(kcp, buffer, size); + ptr = buffer; + } + ptr = ikcp_encode_seg(ptr, &seg); + } + + kcp->probe = 0; + + // calculate window size + cwnd = _imin_(kcp->snd_wnd, kcp->rmt_wnd); + if (kcp->nocwnd == 0) cwnd = _imin_(kcp->cwnd, cwnd); + + // move data from snd_queue to snd_buf + while (_itimediff(kcp->snd_nxt, kcp->snd_una + cwnd) < 0) { + IKCPSEG *newseg; + if (iqueue_is_empty(&kcp->snd_queue)) break; + + newseg = iqueue_entry(kcp->snd_queue.next, IKCPSEG, node); + + iqueue_del(&newseg->node); + iqueue_add_tail(&newseg->node, &kcp->snd_buf); + kcp->nsnd_que--; + kcp->nsnd_buf++; + + newseg->conv = kcp->conv; + newseg->cmd = IKCP_CMD_PUSH; + newseg->wnd = seg.wnd; + newseg->ts = current; + newseg->sn = kcp->snd_nxt++; + newseg->una = kcp->rcv_nxt; + newseg->resendts = current; + newseg->rto = kcp->rx_rto; + newseg->fastack = 0; + newseg->xmit = 0; + } + + // calculate resent + resent = (kcp->fastresend > 0)? (IUINT32)kcp->fastresend : 0xffffffff; + rtomin = (kcp->nodelay == 0)? (kcp->rx_rto >> 3) : 0; + + // flush data segments + for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = p->next) { + IKCPSEG *segment = iqueue_entry(p, IKCPSEG, node); + int needsend = 0; + if (segment->xmit == 0) { + needsend = 1; + segment->xmit++; + segment->rto = kcp->rx_rto; + segment->resendts = current + segment->rto + rtomin; + } + else if (_itimediff(current, segment->resendts) >= 0) { + needsend = 1; + segment->xmit++; + kcp->xmit++; + if (kcp->nodelay == 0) { + segment->rto += _imax_(segment->rto, (IUINT32)kcp->rx_rto); + } else { + IINT32 step = (kcp->nodelay < 2)? + ((IINT32)(segment->rto)) : kcp->rx_rto; + segment->rto += step / 2; + } + segment->resendts = current + segment->rto; + lost = 1; + } + else if (segment->fastack >= resent) { + if ((int)segment->xmit <= kcp->fastlimit || + kcp->fastlimit <= 0) { + needsend = 1; + segment->xmit++; + segment->fastack = 0; + segment->resendts = current + segment->rto; + change++; + } + } + + if (needsend) { + int need; + segment->ts = current; + segment->wnd = seg.wnd; + segment->una = kcp->rcv_nxt; + + size = (int)(ptr - buffer); + need = IKCP_OVERHEAD + segment->len; + + if (size + need > (int)kcp->mtu) { + ikcp_output(kcp, buffer, size); + ptr = buffer; + } + + ptr = ikcp_encode_seg(ptr, segment); + + if (segment->len > 0) { + memcpy(ptr, segment->data, segment->len); + ptr += segment->len; + } + + if (segment->xmit >= kcp->dead_link) { + kcp->state = (IUINT32)-1; + } + } + } + + // flash remain segments + size = (int)(ptr - buffer); + if (size > 0) { + ikcp_output(kcp, buffer, size); + } + + // update ssthresh + if (change) { + IUINT32 inflight = kcp->snd_nxt - kcp->snd_una; + kcp->ssthresh = inflight / 2; + if (kcp->ssthresh < IKCP_THRESH_MIN) + kcp->ssthresh = IKCP_THRESH_MIN; + kcp->cwnd = kcp->ssthresh + resent; + kcp->incr = kcp->cwnd * kcp->mss; + } + + if (lost) { + kcp->ssthresh = cwnd / 2; + if (kcp->ssthresh < IKCP_THRESH_MIN) + kcp->ssthresh = IKCP_THRESH_MIN; + kcp->cwnd = 1; + kcp->incr = kcp->mss; + } + + if (kcp->cwnd < 1) { + kcp->cwnd = 1; + kcp->incr = kcp->mss; + } +} + + +//--------------------------------------------------------------------- +// update state (call it repeatedly, every 10ms-100ms), or you can ask +// ikcp_check when to call it again (without ikcp_input/_send calling). +// 'current' - current timestamp in millisec. +//--------------------------------------------------------------------- +void ikcp_update(ikcpcb *kcp, IUINT32 current) +{ + IINT32 slap; + + kcp->current = current; + + if (kcp->updated == 0) { + kcp->updated = 1; + kcp->ts_flush = kcp->current; + } + + slap = _itimediff(kcp->current, kcp->ts_flush); + + if (slap >= 10000 || slap < -10000) { + kcp->ts_flush = kcp->current; + slap = 0; + } + + if (slap >= 0) { + kcp->ts_flush += kcp->interval; + if (_itimediff(kcp->current, kcp->ts_flush) >= 0) { + kcp->ts_flush = kcp->current + kcp->interval; + } + ikcp_flush(kcp); + } +} + + +//--------------------------------------------------------------------- +// Determine when should you invoke ikcp_update: +// returns when you should invoke ikcp_update in millisec, if there +// is no ikcp_input/_send calling. you can call ikcp_update in that +// time, instead of call update repeatly. +// Important to reduce unnacessary ikcp_update invoking. use it to +// schedule ikcp_update (eg. implementing an epoll-like mechanism, +// or optimize ikcp_update when handling massive kcp connections) +//--------------------------------------------------------------------- +IUINT32 ikcp_check(const ikcpcb *kcp, IUINT32 current) +{ + IUINT32 ts_flush = kcp->ts_flush; + IINT32 tm_flush = 0x7fffffff; + IINT32 tm_packet = 0x7fffffff; + IUINT32 minimal = 0; + struct IQUEUEHEAD *p; + + if (kcp->updated == 0) { + return current; + } + + if (_itimediff(current, ts_flush) >= 10000 || + _itimediff(current, ts_flush) < -10000) { + ts_flush = current; + } + + if (_itimediff(current, ts_flush) >= 0) { + return current; + } + + tm_flush = _itimediff(ts_flush, current); + + for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = p->next) { + const IKCPSEG *seg = iqueue_entry(p, const IKCPSEG, node); + IINT32 diff = _itimediff(seg->resendts, current); + if (diff <= 0) { + return current; + } + if (diff < tm_packet) tm_packet = diff; + } + + minimal = (IUINT32)(tm_packet < tm_flush ? tm_packet : tm_flush); + if (minimal >= kcp->interval) minimal = kcp->interval; + + return current + minimal; +} + + + +int ikcp_setmtu(ikcpcb *kcp, int mtu) +{ + char *buffer; + if (mtu < 50 || mtu < (int)IKCP_OVERHEAD) + return -1; + buffer = (char*)ikcp_malloc((mtu + IKCP_OVERHEAD) * 3); + if (buffer == NULL) + return -2; + kcp->mtu = mtu; + kcp->mss = kcp->mtu - IKCP_OVERHEAD; + ikcp_free(kcp->buffer); + kcp->buffer = buffer; + return 0; +} + +int ikcp_interval(ikcpcb *kcp, int interval) +{ + if (interval > 5000) interval = 5000; + else if (interval < 10) interval = 10; + kcp->interval = interval; + return 0; +} + +int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc) +{ + if (nodelay >= 0) { + kcp->nodelay = nodelay; + if (nodelay) { + kcp->rx_minrto = IKCP_RTO_NDL; + } + else { + kcp->rx_minrto = IKCP_RTO_MIN; + } + } + if (interval >= 0) { + if (interval > 5000) interval = 5000; + else if (interval < 10) interval = 10; + kcp->interval = interval; + } + if (resend >= 0) { + kcp->fastresend = resend; + } + if (nc >= 0) { + kcp->nocwnd = nc; + } + return 0; +} + + +int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd) +{ + if (kcp) { + if (sndwnd > 0) { + kcp->snd_wnd = sndwnd; + } + if (rcvwnd > 0) { // must >= max fragment size + kcp->rcv_wnd = _imax_(rcvwnd, IKCP_WND_RCV); + } + } + return 0; +} + +int ikcp_waitsnd(const ikcpcb *kcp) +{ + return kcp->nsnd_buf + kcp->nsnd_que; +} + + +// read conv +IUINT32 ikcp_getconv(const void *ptr) +{ + IUINT32 conv; + ikcp_decode32u((const char*)ptr, &conv); + return conv; +} + + diff --git a/ww/libhv/event/kcp/ikcp.h b/ww/libhv/event/kcp/ikcp.h new file mode 100644 index 00000000..e525105c --- /dev/null +++ b/ww/libhv/event/kcp/ikcp.h @@ -0,0 +1,416 @@ +//===================================================================== +// +// KCP - A Better ARQ Protocol Implementation +// skywind3000 (at) gmail.com, 2010-2011 +// +// Features: +// + Average RTT reduce 30% - 40% vs traditional ARQ like tcp. +// + Maximum RTT reduce three times vs tcp. +// + Lightweight, distributed as a single source file. +// +//===================================================================== +#ifndef __IKCP_H__ +#define __IKCP_H__ + +#include +#include +#include + + +//===================================================================== +// 32BIT INTEGER DEFINITION +//===================================================================== +#ifndef __INTEGER_32_BITS__ +#define __INTEGER_32_BITS__ +#if defined(_WIN64) || defined(WIN64) || defined(__amd64__) || \ + defined(__x86_64) || defined(__x86_64__) || defined(_M_IA64) || \ + defined(_M_AMD64) + typedef unsigned int ISTDUINT32; + typedef int ISTDINT32; +#elif defined(_WIN32) || defined(WIN32) || defined(__i386__) || \ + defined(__i386) || defined(_M_X86) + typedef unsigned long ISTDUINT32; + typedef long ISTDINT32; +#elif defined(__MACOS__) + typedef UInt32 ISTDUINT32; + typedef SInt32 ISTDINT32; +#elif defined(__APPLE__) && defined(__MACH__) + #include + typedef u_int32_t ISTDUINT32; + typedef int32_t ISTDINT32; +#elif defined(__BEOS__) + #include + typedef u_int32_t ISTDUINT32; + typedef int32_t ISTDINT32; +#elif (defined(_MSC_VER) || defined(__BORLANDC__)) && (!defined(__MSDOS__)) + typedef unsigned __int32 ISTDUINT32; + typedef __int32 ISTDINT32; +#elif defined(__GNUC__) + #include + typedef uint32_t ISTDUINT32; + typedef int32_t ISTDINT32; +#else + typedef unsigned long ISTDUINT32; + typedef long ISTDINT32; +#endif +#endif + + +//===================================================================== +// Integer Definition +//===================================================================== +#ifndef __IINT8_DEFINED +#define __IINT8_DEFINED +typedef char IINT8; +#endif + +#ifndef __IUINT8_DEFINED +#define __IUINT8_DEFINED +typedef unsigned char IUINT8; +#endif + +#ifndef __IUINT16_DEFINED +#define __IUINT16_DEFINED +typedef unsigned short IUINT16; +#endif + +#ifndef __IINT16_DEFINED +#define __IINT16_DEFINED +typedef short IINT16; +#endif + +#ifndef __IINT32_DEFINED +#define __IINT32_DEFINED +typedef ISTDINT32 IINT32; +#endif + +#ifndef __IUINT32_DEFINED +#define __IUINT32_DEFINED +typedef ISTDUINT32 IUINT32; +#endif + +#ifndef __IINT64_DEFINED +#define __IINT64_DEFINED +#if defined(_MSC_VER) || defined(__BORLANDC__) +typedef __int64 IINT64; +#else +typedef long long IINT64; +#endif +#endif + +#ifndef __IUINT64_DEFINED +#define __IUINT64_DEFINED +#if defined(_MSC_VER) || defined(__BORLANDC__) +typedef unsigned __int64 IUINT64; +#else +typedef unsigned long long IUINT64; +#endif +#endif + +#ifndef INLINE +#if defined(__GNUC__) + +#if (__GNUC__ > 3) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1)) +#define INLINE __inline__ __attribute__((always_inline)) +#else +#define INLINE __inline__ +#endif + +#elif (defined(_MSC_VER) || defined(__BORLANDC__) || defined(__WATCOMC__)) +#define INLINE __inline +#else +#define INLINE +#endif +#endif + +#if (!defined(__cplusplus)) && (!defined(inline)) +#define inline INLINE +#endif + + +//===================================================================== +// QUEUE DEFINITION +//===================================================================== +#ifndef __IQUEUE_DEF__ +#define __IQUEUE_DEF__ + +struct IQUEUEHEAD { + struct IQUEUEHEAD *next, *prev; +}; + +typedef struct IQUEUEHEAD iqueue_head; + + +//--------------------------------------------------------------------- +// queue init +//--------------------------------------------------------------------- +#define IQUEUE_HEAD_INIT(name) { &(name), &(name) } +#define IQUEUE_HEAD(name) \ + struct IQUEUEHEAD name = IQUEUE_HEAD_INIT(name) + +#define IQUEUE_INIT(ptr) ( \ + (ptr)->next = (ptr), (ptr)->prev = (ptr)) + +#define IOFFSETOF(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) + +#define ICONTAINEROF(ptr, type, member) ( \ + (type*)( ((char*)((type*)ptr)) - IOFFSETOF(type, member)) ) + +#define IQUEUE_ENTRY(ptr, type, member) ICONTAINEROF(ptr, type, member) + + +//--------------------------------------------------------------------- +// queue operation +//--------------------------------------------------------------------- +#define IQUEUE_ADD(node, head) ( \ + (node)->prev = (head), (node)->next = (head)->next, \ + (head)->next->prev = (node), (head)->next = (node)) + +#define IQUEUE_ADD_TAIL(node, head) ( \ + (node)->prev = (head)->prev, (node)->next = (head), \ + (head)->prev->next = (node), (head)->prev = (node)) + +#define IQUEUE_DEL_BETWEEN(p, n) ((n)->prev = (p), (p)->next = (n)) + +#define IQUEUE_DEL(entry) (\ + (entry)->next->prev = (entry)->prev, \ + (entry)->prev->next = (entry)->next, \ + (entry)->next = 0, (entry)->prev = 0) + +#define IQUEUE_DEL_INIT(entry) do { \ + IQUEUE_DEL(entry); IQUEUE_INIT(entry); } while (0) + +#define IQUEUE_IS_EMPTY(entry) ((entry) == (entry)->next) + +#define iqueue_init IQUEUE_INIT +#define iqueue_entry IQUEUE_ENTRY +#define iqueue_add IQUEUE_ADD +#define iqueue_add_tail IQUEUE_ADD_TAIL +#define iqueue_del IQUEUE_DEL +#define iqueue_del_init IQUEUE_DEL_INIT +#define iqueue_is_empty IQUEUE_IS_EMPTY + +#define IQUEUE_FOREACH(iterator, head, TYPE, MEMBER) \ + for ((iterator) = iqueue_entry((head)->next, TYPE, MEMBER); \ + &((iterator)->MEMBER) != (head); \ + (iterator) = iqueue_entry((iterator)->MEMBER.next, TYPE, MEMBER)) + +#define iqueue_foreach(iterator, head, TYPE, MEMBER) \ + IQUEUE_FOREACH(iterator, head, TYPE, MEMBER) + +#define iqueue_foreach_entry(pos, head) \ + for( (pos) = (head)->next; (pos) != (head) ; (pos) = (pos)->next ) + + +#define __iqueue_splice(list, head) do { \ + iqueue_head *first = (list)->next, *last = (list)->prev; \ + iqueue_head *at = (head)->next; \ + (first)->prev = (head), (head)->next = (first); \ + (last)->next = (at), (at)->prev = (last); } while (0) + +#define iqueue_splice(list, head) do { \ + if (!iqueue_is_empty(list)) __iqueue_splice(list, head); } while (0) + +#define iqueue_splice_init(list, head) do { \ + iqueue_splice(list, head); iqueue_init(list); } while (0) + + +#ifdef _MSC_VER +#pragma warning(disable:4311) +#pragma warning(disable:4312) +#pragma warning(disable:4996) +#endif + +#endif + + +//--------------------------------------------------------------------- +// BYTE ORDER & ALIGNMENT +//--------------------------------------------------------------------- +#ifndef IWORDS_BIG_ENDIAN + #ifdef _BIG_ENDIAN_ + #if _BIG_ENDIAN_ + #define IWORDS_BIG_ENDIAN 1 + #endif + #endif + #ifndef IWORDS_BIG_ENDIAN + #if defined(__hppa__) || \ + defined(__m68k__) || defined(mc68000) || defined(_M_M68K) || \ + (defined(__MIPS__) && defined(__MIPSEB__)) || \ + defined(__ppc__) || defined(__POWERPC__) || defined(_M_PPC) || \ + defined(__sparc__) || defined(__powerpc__) || \ + defined(__mc68000__) || defined(__s390x__) || defined(__s390__) + #define IWORDS_BIG_ENDIAN 1 + #endif + #endif + #ifndef IWORDS_BIG_ENDIAN + #define IWORDS_BIG_ENDIAN 0 + #endif +#endif + +#ifndef IWORDS_MUST_ALIGN + #if defined(__i386__) || defined(__i386) || defined(_i386_) + #define IWORDS_MUST_ALIGN 0 + #elif defined(_M_IX86) || defined(_X86_) || defined(__x86_64__) + #define IWORDS_MUST_ALIGN 0 + #elif defined(__amd64) || defined(__amd64__) + #define IWORDS_MUST_ALIGN 0 + #else + #define IWORDS_MUST_ALIGN 1 + #endif +#endif + + +//===================================================================== +// SEGMENT +//===================================================================== +struct IKCPSEG +{ + struct IQUEUEHEAD node; + IUINT32 conv; + IUINT32 cmd; + IUINT32 frg; + IUINT32 wnd; + IUINT32 ts; + IUINT32 sn; + IUINT32 una; + IUINT32 len; + IUINT32 resendts; + IUINT32 rto; + IUINT32 fastack; + IUINT32 xmit; + char data[1]; +}; + + +//--------------------------------------------------------------------- +// IKCPCB +//--------------------------------------------------------------------- +struct IKCPCB +{ + IUINT32 conv, mtu, mss, state; + IUINT32 snd_una, snd_nxt, rcv_nxt; + IUINT32 ts_recent, ts_lastack, ssthresh; + IINT32 rx_rttval, rx_srtt, rx_rto, rx_minrto; + IUINT32 snd_wnd, rcv_wnd, rmt_wnd, cwnd, probe; + IUINT32 current, interval, ts_flush, xmit; + IUINT32 nrcv_buf, nsnd_buf; + IUINT32 nrcv_que, nsnd_que; + IUINT32 nodelay, updated; + IUINT32 ts_probe, probe_wait; + IUINT32 dead_link, incr; + struct IQUEUEHEAD snd_queue; + struct IQUEUEHEAD rcv_queue; + struct IQUEUEHEAD snd_buf; + struct IQUEUEHEAD rcv_buf; + IUINT32 *acklist; + IUINT32 ackcount; + IUINT32 ackblock; + void *user; + char *buffer; + int fastresend; + int fastlimit; + int nocwnd, stream; + int logmask; + int (*output)(const char *buf, int len, struct IKCPCB *kcp, void *user); + void (*writelog)(const char *log, struct IKCPCB *kcp, void *user); +}; + + +typedef struct IKCPCB ikcpcb; + +#define IKCP_LOG_OUTPUT 1 +#define IKCP_LOG_INPUT 2 +#define IKCP_LOG_SEND 4 +#define IKCP_LOG_RECV 8 +#define IKCP_LOG_IN_DATA 16 +#define IKCP_LOG_IN_ACK 32 +#define IKCP_LOG_IN_PROBE 64 +#define IKCP_LOG_IN_WINS 128 +#define IKCP_LOG_OUT_DATA 256 +#define IKCP_LOG_OUT_ACK 512 +#define IKCP_LOG_OUT_PROBE 1024 +#define IKCP_LOG_OUT_WINS 2048 + +#ifdef __cplusplus +extern "C" { +#endif + +//--------------------------------------------------------------------- +// interface +//--------------------------------------------------------------------- + +// create a new kcp control object, 'conv' must equal in two endpoint +// from the same connection. 'user' will be passed to the output callback +// output callback can be setup like this: 'kcp->output = my_udp_output' +ikcpcb* ikcp_create(IUINT32 conv, void *user); + +// release kcp control object +void ikcp_release(ikcpcb *kcp); + +// set output callback, which will be invoked by kcp +void ikcp_setoutput(ikcpcb *kcp, int (*output)(const char *buf, int len, + ikcpcb *kcp, void *user)); + +// user/upper level recv: returns size, returns below zero for EAGAIN +int ikcp_recv(ikcpcb *kcp, char *buffer, int len); + +// user/upper level send, returns below zero for error +int ikcp_send(ikcpcb *kcp, const char *buffer, int len); + +// update state (call it repeatedly, every 10ms-100ms), or you can ask +// ikcp_check when to call it again (without ikcp_input/_send calling). +// 'current' - current timestamp in millisec. +void ikcp_update(ikcpcb *kcp, IUINT32 current); + +// Determine when should you invoke ikcp_update: +// returns when you should invoke ikcp_update in millisec, if there +// is no ikcp_input/_send calling. you can call ikcp_update in that +// time, instead of call update repeatly. +// Important to reduce unnacessary ikcp_update invoking. use it to +// schedule ikcp_update (eg. implementing an epoll-like mechanism, +// or optimize ikcp_update when handling massive kcp connections) +IUINT32 ikcp_check(const ikcpcb *kcp, IUINT32 current); + +// when you received a low level packet (eg. UDP packet), call it +int ikcp_input(ikcpcb *kcp, const char *data, long size); + +// flush pending data +void ikcp_flush(ikcpcb *kcp); + +// check the size of next message in the recv queue +int ikcp_peeksize(const ikcpcb *kcp); + +// change MTU size, default is 1400 +int ikcp_setmtu(ikcpcb *kcp, int mtu); + +// set maximum window size: sndwnd=32, rcvwnd=32 by default +int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd); + +// get how many packet is waiting to be sent +int ikcp_waitsnd(const ikcpcb *kcp); + +// fastest: ikcp_nodelay(kcp, 1, 20, 2, 1) +// nodelay: 0:disable(default), 1:enable +// interval: internal update timer interval in millisec, default is 100ms +// resend: 0:disable fast resend(default), 1:enable fast resend +// nc: 0:normal congestion control(default), 1:disable congestion control +int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc); + + +void ikcp_log(ikcpcb *kcp, int mask, const char *fmt, ...); + +// setup allocator +void ikcp_allocator(void* (*new_malloc)(size_t), void (*new_free)(void*)); + +// read conv +IUINT32 ikcp_getconv(const void *ptr); + + +#ifdef __cplusplus +} +#endif + +#endif + + diff --git a/ww/libhv/event/kqueue.c b/ww/libhv/event/kqueue.c new file mode 100644 index 00000000..5416280a --- /dev/null +++ b/ww/libhv/event/kqueue.c @@ -0,0 +1,174 @@ +#include "iowatcher.h" + +#ifdef EVENT_KQUEUE +#include "hplatform.h" +#include "hdef.h" + +#include + +#include "hevent.h" + +#define EVENTS_INIT_SIZE 64 + +#define READ_INDEX 0 +#define WRITE_INDEX 1 +#define EVENT_INDEX(type) ((type == EVFILT_READ) ? READ_INDEX : WRITE_INDEX) + +typedef struct kqueue_ctx_s { + int kqfd; + int capacity; + int nchanges; + struct kevent* changes; + //int nevents; // nevents == nchanges + struct kevent* events; +} kqueue_ctx_t; + +static void kqueue_ctx_resize(kqueue_ctx_t* kqueue_ctx, int size) { + int bytes = sizeof(struct kevent) * size; + int oldbytes = sizeof(struct kevent) * kqueue_ctx->capacity; + kqueue_ctx->changes = (struct kevent*)hv_realloc(kqueue_ctx->changes, bytes, oldbytes); + kqueue_ctx->events = (struct kevent*)hv_realloc(kqueue_ctx->events, bytes, oldbytes); + kqueue_ctx->capacity = size; +} + +int iowatcher_init(hloop_t* loop) { + if (loop->iowatcher) return 0; + kqueue_ctx_t* kqueue_ctx; + HV_ALLOC_SIZEOF(kqueue_ctx); + kqueue_ctx->kqfd = kqueue(); + kqueue_ctx->capacity = EVENTS_INIT_SIZE; + kqueue_ctx->nchanges = 0; + int bytes = sizeof(struct kevent) * kqueue_ctx->capacity; + HV_ALLOC(kqueue_ctx->changes, bytes); + HV_ALLOC(kqueue_ctx->events, bytes); + loop->iowatcher = kqueue_ctx; + return 0; +} + +int iowatcher_cleanup(hloop_t* loop) { + if (loop->iowatcher == NULL) return 0; + kqueue_ctx_t* kqueue_ctx = (kqueue_ctx_t*)loop->iowatcher; + close(kqueue_ctx->kqfd); + HV_FREE(kqueue_ctx->changes); + HV_FREE(kqueue_ctx->events); + HV_FREE(loop->iowatcher); + return 0; +} + +static int __add_event(hloop_t* loop, int fd, int event) { + if (loop->iowatcher == NULL) { + iowatcher_init(loop); + } + kqueue_ctx_t* kqueue_ctx = (kqueue_ctx_t*)loop->iowatcher; + hio_t* io = loop->ios.ptr[fd]; + int idx = io->event_index[EVENT_INDEX(event)]; + if (idx < 0) { + io->event_index[EVENT_INDEX(event)] = idx = kqueue_ctx->nchanges; + kqueue_ctx->nchanges++; + if (idx == kqueue_ctx->capacity) { + kqueue_ctx_resize(kqueue_ctx, kqueue_ctx->capacity*2); + } + memset(kqueue_ctx->changes+idx, 0, sizeof(struct kevent)); + kqueue_ctx->changes[idx].ident = fd; + } + assert(kqueue_ctx->changes[idx].ident == fd); + kqueue_ctx->changes[idx].filter = event; + kqueue_ctx->changes[idx].flags = EV_ADD|EV_ENABLE; + struct timespec ts; + ts.tv_sec = 0; + ts.tv_nsec = 0; + kevent(kqueue_ctx->kqfd, kqueue_ctx->changes, kqueue_ctx->nchanges, NULL, 0, &ts); + return 0; +} + +int iowatcher_add_event(hloop_t* loop, int fd, int events) { + if (events & HV_READ) { + __add_event(loop, fd, EVFILT_READ); + } + if (events & HV_WRITE) { + __add_event(loop, fd, EVFILT_WRITE); + } + return 0; +} + +static int __del_event(hloop_t* loop, int fd, int event) { + kqueue_ctx_t* kqueue_ctx = (kqueue_ctx_t*)loop->iowatcher; + if (kqueue_ctx == NULL) return 0; + hio_t* io = loop->ios.ptr[fd]; + int idx = io->event_index[EVENT_INDEX(event)]; + if (idx < 0) return 0; + assert(kqueue_ctx->changes[idx].ident == fd); + kqueue_ctx->changes[idx].flags = EV_DELETE; + io->event_index[EVENT_INDEX(event)] = -1; + int lastidx = kqueue_ctx->nchanges - 1; + if (idx < lastidx) { + // swap + struct kevent tmp; + tmp = kqueue_ctx->changes[idx]; + kqueue_ctx->changes[idx] = kqueue_ctx->changes[lastidx]; + kqueue_ctx->changes[lastidx] = tmp; + hio_t* last = loop->ios.ptr[kqueue_ctx->changes[idx].ident]; + if (last) { + last->event_index[EVENT_INDEX(kqueue_ctx->changes[idx].filter)] = idx; + } + } + struct timespec ts; + ts.tv_sec = 0; + ts.tv_nsec = 0; + kevent(kqueue_ctx->kqfd, kqueue_ctx->changes, kqueue_ctx->nchanges, NULL, 0, &ts); + kqueue_ctx->nchanges--; + return 0; +} + +int iowatcher_del_event(hloop_t* loop, int fd, int events) { + if (events & HV_READ) { + __del_event(loop, fd, EVFILT_READ); + } + if (events & HV_WRITE) { + __del_event(loop, fd, EVFILT_WRITE); + } + return 0; +} + +int iowatcher_poll_events(hloop_t* loop, int timeout) { + kqueue_ctx_t* kqueue_ctx = (kqueue_ctx_t*)loop->iowatcher; + if (kqueue_ctx == NULL) return 0; + if (kqueue_ctx->nchanges == 0) return 0; + struct timespec ts, *tp; + if (timeout == INFINITE) { + tp = NULL; + } + else { + ts.tv_sec = timeout / 1000; + ts.tv_nsec = (timeout % 1000) * 1000000; + tp = &ts; + } + int nkqueue = kevent(kqueue_ctx->kqfd, kqueue_ctx->changes, kqueue_ctx->nchanges, kqueue_ctx->events, kqueue_ctx->nchanges, tp); + if (nkqueue < 0) { + perror("kevent"); + return nkqueue; + } + if (nkqueue == 0) return 0; + int nevents = 0; + for (int i = 0; i < nkqueue; ++i) { + if (kqueue_ctx->events[i].flags & EV_ERROR) { + continue; + } + ++nevents; + int fd = kqueue_ctx->events[i].ident; + int revents = kqueue_ctx->events[i].filter; + hio_t* io = loop->ios.ptr[fd]; + if (io) { + if (revents & EVFILT_READ) { + io->revents |= HV_READ; + } + if (revents & EVFILT_WRITE) { + io->revents |= HV_WRITE; + } + EVENT_PENDING(io); + } + if (nevents == nkqueue) break; + } + return nevents; +} +#endif diff --git a/ww/libhv/event/nio.c b/ww/libhv/event/nio.c new file mode 100644 index 00000000..3e2a8502 --- /dev/null +++ b/ww/libhv/event/nio.c @@ -0,0 +1,515 @@ +#include "iowatcher.h" +#ifndef EVENT_IOCP +#include "hevent.h" +#include "hsocket.h" +#include "hlog.h" +#include "herr.h" +#include "hthread.h" + +static void __connect_timeout_cb(htimer_t* timer) { + hio_t* io = (hio_t*)timer->privdata; + if (io) { + char localaddrstr[SOCKADDR_STRLEN] = {0}; + char peeraddrstr[SOCKADDR_STRLEN] = {0}; + hlogw("connect timeout [%s] <=> [%s]", SOCKADDR_STR(io->localaddr, localaddrstr), SOCKADDR_STR(io->peeraddr, peeraddrstr)); + io->error = ETIMEDOUT; + hio_close(io); + } +} + +static void __close_timeout_cb(htimer_t* timer) { + hio_t* io = (hio_t*)timer->privdata; + if (io) { + char localaddrstr[SOCKADDR_STRLEN] = {0}; + char peeraddrstr[SOCKADDR_STRLEN] = {0}; + hlogw("close timeout [%s] <=> [%s]", SOCKADDR_STR(io->localaddr, localaddrstr), SOCKADDR_STR(io->peeraddr, peeraddrstr)); + io->error = ETIMEDOUT; + hio_close(io); + } +} + +static void __accept_cb(hio_t* io) { + hio_accept_cb(io); +} + +static void __connect_cb(hio_t* io) { + hio_del_connect_timer(io); + hio_connect_cb(io); +} + +static void __read_cb(hio_t* io, void* buf, int readbytes) { + // printd("> %.*s\n", readbytes, buf); + io->last_read_hrtime = io->loop->cur_hrtime; + hio_handle_read(io, buf, readbytes); +} + +static void __write_cb(hio_t* io, const void* buf, int writebytes) { + // printd("< %.*s\n", writebytes, buf); + io->last_write_hrtime = io->loop->cur_hrtime; + hio_write_cb(io, buf, writebytes); +} + +static void __close_cb(hio_t* io) { + // printd("close fd=%d\n", io->fd); + hio_del_connect_timer(io); + hio_del_close_timer(io); + hio_del_read_timer(io); + hio_del_write_timer(io); + hio_del_keepalive_timer(io); + hio_del_heartbeat_timer(io); + hio_close_cb(io); +} + + + +static void nio_accept(hio_t* io) { + // printd("nio_accept listenfd=%d\n", io->fd); + int connfd = 0, err = 0, accept_cnt = 0; + socklen_t addrlen; + hio_t* connio = NULL; + while (accept_cnt++ < 3) { + addrlen = sizeof(sockaddr_u); + connfd = accept(io->fd, io->peeraddr, &addrlen); + if (connfd < 0) { + err = socket_errno(); + if (err == EAGAIN || err == EINTR) { + return; + } + else { + perror("accept"); + io->error = err; + goto accept_error; + } + } + addrlen = sizeof(sockaddr_u); + getsockname(connfd, io->localaddr, &addrlen); + connio = hio_get(io->loop, connfd); + // NOTE: inherit from listenio + connio->accept_cb = io->accept_cb; + connio->userdata = io->userdata; + + + __accept_cb(connio); + } + return; + +accept_error: + hloge("listenfd=%d accept error: %s:%d", io->fd, socket_strerror(io->error), io->error); + // NOTE: Don't close listen fd automatically anyway. + // hio_close(io); +} + +static void nio_connect(hio_t* io) { + // printd("nio_connect connfd=%d\n", io->fd); + socklen_t addrlen = sizeof(sockaddr_u); + int ret = getpeername(io->fd, io->peeraddr, &addrlen); + if (ret < 0) { + io->error = socket_errno(); + goto connect_error; + } + else { + addrlen = sizeof(sockaddr_u); + getsockname(io->fd, io->localaddr, &addrlen); + + __connect_cb(io); + + return; + } + +connect_error: + hlogw("connfd=%d connect error: %s:%d", io->fd, socket_strerror(io->error), io->error); + hio_close(io); +} + +static void nio_connect_event_cb(hevent_t* ev) { + hio_t* io = (hio_t*)ev->userdata; + uint32_t id = (uintptr_t)ev->privdata; + if (io->id != id) return; + nio_connect(io); +} + +static int nio_connect_async(hio_t* io) { + hevent_t ev; + memset(&ev, 0, sizeof(ev)); + ev.cb = nio_connect_event_cb; + ev.userdata = io; + ev.privdata = (void*)(uintptr_t)io->id; + hloop_post_event(io->loop, &ev); + return 0; +} + +static int __nio_read(hio_t* io, void* buf, int len) { + int nread = 0; + switch (io->io_type) { + + case HIO_TYPE_TCP: + // #if defined(OS_LINUX) && defined(HAVE_PIPE) + // if(io->pfd_w){ + // nread = splice(io->fd, NULL,io->pfd_w,0, len, SPLICE_F_NONBLOCK); + // }else + // #endif + nread = recv(io->fd, buf, len, 0); + break; + case HIO_TYPE_UDP: + case HIO_TYPE_KCP: + case HIO_TYPE_IP: { + socklen_t addrlen = sizeof(sockaddr_u); + nread = recvfrom(io->fd, buf, len, 0, io->peeraddr, &addrlen); + } break; + default: nread = read(io->fd, buf, len); break; + } + // hlogd("read retval=%d", nread); + return nread; +} + +static int __nio_write(hio_t* io, const void* buf, int len) { + int nwrite = 0; + switch (io->io_type) { + case HIO_TYPE_TCP: { + // #if defined(OS_LINUX) && defined(HAVE_PIPE) + // if(io->pfd_r){ + // nwrite = splice(io->pfd_r, NULL,io->fd,0, len, SPLICE_F_NONBLOCK); + // break; + // } + // #endif + int flag = 0; +#ifdef MSG_NOSIGNAL + flag |= MSG_NOSIGNAL; +#endif + nwrite = send(io->fd, buf, len, flag); + } break; + case HIO_TYPE_UDP: + case HIO_TYPE_KCP: + case HIO_TYPE_IP: nwrite = sendto(io->fd, buf, len, 0, io->peeraddr, SOCKADDR_LEN(io->peeraddr)); break; + default: nwrite = write(io->fd, buf, len); break; + } + // hlogd("write retval=%d", nwrite); + return nwrite; +} + +static void nio_read(hio_t* io) { + // printd("nio_read fd=%d\n", io->fd); + void* buf; + int len = 0, nread = 0, err = 0; +read: + buf = io->readbuf.base + io->readbuf.tail; + // #if defined(OS_LINUX) && defined(HAVE_PIPE) + // if(io->pfd_w){ + // len = (1U << 20); // 1 MB + // }else + // #endif + if (io->read_flags & HIO_READ_UNTIL_LENGTH) { + len = io->read_until_length - (io->readbuf.tail - io->readbuf.head); + } + else { + len = io->readbuf.len - io->readbuf.tail; + } + assert(len > 0); + nread = __nio_read(io, buf, len); + // printd("read retval=%d\n", nread); + if (nread < 0) { + err = socket_errno(); + if (err == EAGAIN || err == EINTR) { + // goto read_done; + return; + } + else if (err == EMSGSIZE) { + // ignore + return; + } + else { + // perror("read"); + io->error = err; + goto read_error; + } + } + if (nread == 0) { + goto disconnect; + } + // printf("%d \n",nread); + // #if defined(OS_LINUX) && defined(HAVE_PIPE) + // if(io->pfd_w == 0x0 && nread < len){ + // // NOTE: make string friendly + // ((char*)buf)[nread] = '\0'; + // } + // #else + if (nread < len) { + // NOTE: make string friendly + ((char*)buf)[nread] = '\0'; + } + // #endif + + io->readbuf.tail += nread; + __read_cb(io, buf, nread); + return; +read_error: +disconnect: + if (io->io_type & HIO_TYPE_SOCK_STREAM) { + hio_close(io); + } +} + +static void nio_write(hio_t* io) { + // printd("nio_write fd=%d\n", io->fd); + int nwrite = 0, err = 0; + // hrecursive_mutex_lock(&io->write_mutex); +write: + if (write_queue_empty(&io->write_queue)) { + // hrecursive_mutex_unlock(&io->write_mutex); + if (io->close) { + io->close = 0; + hio_close(io); + } + return; + } + offset_buf_t* pbuf = write_queue_front(&io->write_queue); + char* base = pbuf->base; + char* buf = base + pbuf->offset; + int len = pbuf->len - pbuf->offset; + nwrite = __nio_write(io, buf, len); + // printd("write retval=%d\n", nwrite); + if (nwrite < 0) { + err = socket_errno(); + if (err == EAGAIN || err == EINTR) { + // hrecursive_mutex_unlock(&io->write_mutex); + return; + } + else { + // perror("write"); + io->error = err; + goto write_error; + } + } + if (nwrite == 0) { + goto disconnect; + } + pbuf->offset += nwrite; + io->write_bufsize -= nwrite; + __write_cb(io, buf, nwrite); + if (nwrite == len) { + // NOTE: after write_cb, pbuf maybe invalid. + // HV_FREE(pbuf->base); + // #if defined(OS_LINUX) && defined(HAVE_PIPE) + // if(io->pfd_w == 0) + // HV_FREE(base); + // #else + HV_FREE(base); + // #endif + write_queue_pop_front(&io->write_queue); + if (!io->closed) { + // write continue + goto write; + } + } + // hrecursive_mutex_unlock(&io->write_mutex); + return; +write_error: +disconnect: + // hrecursive_mutex_unlock(&io->write_mutex); + if (io->io_type & HIO_TYPE_SOCK_STREAM) { + hio_close(io); + } +} + +static void hio_handle_events(hio_t* io) { + if ((io->events & HV_READ) && (io->revents & HV_READ)) { + if (io->accept) { + nio_accept(io); + } + else { + nio_read(io); + } + } + + if ((io->events & HV_WRITE) && (io->revents & HV_WRITE)) { + // NOTE: del HV_WRITE, if write_queue empty + // hrecursive_mutex_lock(&io->write_mutex); + if (write_queue_empty(&io->write_queue)) { + hio_del(io, HV_WRITE); + } + // hrecursive_mutex_unlock(&io->write_mutex); + if (io->connect) { + // NOTE: connect just do once + // ONESHOT + io->connect = 0; + + nio_connect(io); + } + else { + nio_write(io); + } + } + + io->revents = 0; +} + +int hio_accept(hio_t* io) { + io->accept = 1; + return hio_add(io, hio_handle_events, HV_READ); +} + +int hio_connect(hio_t* io) { + int ret = connect(io->fd, io->peeraddr, SOCKADDR_LEN(io->peeraddr)); +#ifdef OS_WIN + if (ret < 0 && socket_errno() != WSAEWOULDBLOCK) { +#else + if (ret < 0 && socket_errno() != EINPROGRESS) { +#endif + perror("connect"); + io->error = socket_errno(); + hio_close_async(io); + return ret; + } + if (ret == 0) { + // connect ok + nio_connect_async(io); + return 0; + } + int timeout = io->connect_timeout ? io->connect_timeout : HIO_DEFAULT_CONNECT_TIMEOUT; + io->connect_timer = htimer_add(io->loop, __connect_timeout_cb, timeout, 1); + io->connect_timer->privdata = io; + io->connect = 1; + return hio_add(io, hio_handle_events, HV_WRITE); +} + +int hio_read(hio_t* io) { + if (io->closed) { + hloge("hio_read called but fd[%d] already closed!", io->fd); + return -1; + } + hio_add(io, hio_handle_events, HV_READ); + if (io->readbuf.tail > io->readbuf.head && + // io->unpack_setting == NULL && + io->read_flags == 0) { + hio_read_remain(io); + } + return 0; +} + +int hio_write(hio_t* io, const void* buf, size_t len) { + if (io->closed) { + hloge("hio_write called but fd[%d] already closed!", io->fd); + return -1; + } + int nwrite = 0, err = 0; + // hrecursive_mutex_lock(&io->write_mutex); +#if WITH_KCP + if (io->io_type == HIO_TYPE_KCP) { + nwrite = hio_write_kcp(io, buf, len); + // if (nwrite < 0) goto write_error; + goto write_done; + } +#endif + if (write_queue_empty(&io->write_queue)) { + try_write: + nwrite = __nio_write(io, buf, len); + // printd("write retval=%d\n", nwrite); + if (nwrite < 0) { + err = socket_errno(); + if (err == EAGAIN || err == EINTR) { + nwrite = 0; + hlogw("try_write failed, enqueue!"); + goto enqueue; + } + else { + // perror("write"); + io->error = err; + goto write_error; + } + } + if (nwrite == 0) { + goto disconnect; + } + if (nwrite == len) { + goto write_done; + } + enqueue: + hio_add(io, hio_handle_events, HV_WRITE); + } + + if (nwrite < len) { + if (io->write_bufsize + len - nwrite > io->max_write_bufsize) { + hloge("write bufsize > %u, close it!", io->max_write_bufsize); + io->error = ERR_OVER_LIMIT; + goto write_error; + } + offset_buf_t remain; + remain.len = len - nwrite; + remain.offset = 0; + // #if defined(OS_LINUX) && defined(HAVE_PIPE) + // if(io->pfd_w != 0){ + // remain.base = 0X0; // skips free() + + // }else + // { + // // NOTE: free in nio_write + // HV_ALLOC(remain.base, remain.len); + // memcpy(remain.base, ((char*)buf) + nwrite, remain.len); + // } + // #else + // NOTE: free in nio_write + HV_ALLOC(remain.base, remain.len); + memcpy(remain.base, ((char*)buf) + nwrite, remain.len); + // #endif + if (io->write_queue.maxsize == 0) { + write_queue_init(&io->write_queue, 4); + } + write_queue_push_back(&io->write_queue, &remain); + io->write_bufsize += remain.len; + if (io->write_bufsize > WRITE_BUFSIZE_HIGH_WATER) { + hlogw("write len=%u enqueue %u, bufsize=%u over high water %u", (unsigned int)len, (unsigned int)(remain.len - remain.offset), + (unsigned int)io->write_bufsize, (unsigned int)WRITE_BUFSIZE_HIGH_WATER); + } + } +write_done: + // hrecursive_mutex_unlock(&io->write_mutex); + if (nwrite > 0) { + __write_cb(io, buf, nwrite); + } + return nwrite; +write_error: +disconnect: + // hrecursive_mutex_unlock(&io->write_mutex); + /* NOTE: + * We usually free resources in hclose_cb, + * if hio_close_sync, we have to be very careful to avoid using freed resources. + * But if hio_close_async, we do not have to worry about this. + */ + if (io->io_type & HIO_TYPE_SOCK_STREAM) { + hio_close_async(io); + } + return nwrite < 0 ? nwrite : -1; +} + +int hio_close(hio_t* io) { + if (io->closed) return 0; + if (io->destroy == 0 && hv_gettid() != io->loop->tid) { + return hio_close_async(io); + } + + // hrecursive_mutex_lock(&io->write_mutex); + if (io->closed) { + // hrecursive_mutex_unlock(&io->write_mutex); + return 0; + } + if (!write_queue_empty(&io->write_queue) && io->error == 0 && io->close == 0 && io->destroy == 0) { + io->close = 1; + // hrecursive_mutex_unlock(&io->write_mutex); + hlogw("write_queue not empty, close later."); + int timeout_ms = io->close_timeout ? io->close_timeout : HIO_DEFAULT_CLOSE_TIMEOUT; + io->close_timer = htimer_add(io->loop, __close_timeout_cb, timeout_ms, 1); + io->close_timer->privdata = io; + return 0; + } + io->closed = 1; + // hrecursive_mutex_unlock(&io->write_mutex); + + hio_done(io); + __close_cb(io); + // SAFE_FREE(io->hostname); + if (io->io_type & HIO_TYPE_SOCKET) { + closesocket(io->fd); + } + return 0; +} +#endif diff --git a/ww/libhv/event/nlog.c b/ww/libhv/event/nlog.c new file mode 100644 index 00000000..fb8ac45c --- /dev/null +++ b/ww/libhv/event/nlog.c @@ -0,0 +1,87 @@ +#include "nlog.h" + +#include "list.h" +#include "hdef.h" +#include "hbase.h" +#include "hsocket.h" +#include "hmutex.h" + +typedef struct network_logger_s { + hloop_t* loop; + hio_t* listenio; + struct list_head clients; +} network_logger_t; + +typedef struct nlog_client { + hio_t* io; + struct list_node node; +} nlog_client; + +static network_logger_t s_logger = {0}; +static hmutex_t s_mutex; + +static void on_close(hio_t* io) { + printd("on_close fd=%d error=%d\n", hio_fd(io), hio_error(io)); + + nlog_client* client = (nlog_client*)hevent_userdata(io); + if (client) { + hevent_set_userdata(io, NULL); + + hmutex_lock(&s_mutex); + list_del(&client->node); + hmutex_unlock(&s_mutex); + + HV_FREE(client); + } +} + +static void on_read(hio_t* io, void* buf, int readbytes) { + printd("on_read fd=%d readbytes=%d\n", hio_fd(io), readbytes); + printd("< %s\n", (char*)buf); + // nothing to do +} + +static void on_accept(hio_t* io) { + /* + printd("on_accept connfd=%d\n", hio_fd(io)); + char localaddrstr[SOCKADDR_STRLEN] = {0}; + char peeraddrstr[SOCKADDR_STRLEN] = {0}; + printd("accept connfd=%d [%s] <= [%s]\n", hio_fd(io), + SOCKADDR_STR(hio_localaddr(io), localaddrstr), + SOCKADDR_STR(hio_peeraddr(io), peeraddrstr)); + */ + + hio_setcb_read(io, on_read); + hio_setcb_close(io, on_close); + hio_read(io); + + // free on_close + nlog_client* client; + HV_ALLOC_SIZEOF(client); + client->io = io; + hevent_set_userdata(io, client); + + hmutex_lock(&s_mutex); + list_add(&client->node, &s_logger.clients); + hmutex_unlock(&s_mutex); +} + +void network_logger(int loglevel, const char* buf, int len) { + struct list_node* node; + nlog_client* client; + + hmutex_lock(&s_mutex); + list_for_each (node, &s_logger.clients) { + client = list_entry(node, nlog_client, node); + hio_write(client->io, buf, len); + } + hmutex_unlock(&s_mutex); +} + +hio_t* nlog_listen(hloop_t* loop, int port) { + s_logger.loop = loop; + s_logger.listenio = hloop_create_tcp_server(loop, "0.0.0.0", port, on_accept); + list_init(&s_logger.clients); + hmutex_init(&s_mutex); + return s_logger.listenio; +} diff --git a/ww/libhv/event/nlog.h b/ww/libhv/event/nlog.h new file mode 100644 index 00000000..568cbbd6 --- /dev/null +++ b/ww/libhv/event/nlog.h @@ -0,0 +1,44 @@ +#ifndef HV_NLOG_H_ +#define HV_NLOG_H_ + +// nlog: extend hlog use hloop + +/* you can recv log by: + * Windows: telnet ip port + * Linux: nc ip port + */ + +/* + * @see examples/hloop_test.c +#include "hlog.h" +#include "nlog.h" + +void timer_write_log(htimer_t* timer) { + static int cnt = 0; + hlogi("[%d] Do you recv me?", ++cnt); +} + +int main() { + hloop_t* loop = hloop_new(0); + hlog_set_handler(network_logger); + nlog_listen(loop, DEFAULT_LOG_PORT); + htimer_add(loop, timer_write_log, 1000, INFINITE); + hloop_run(loop); + hloop_free(&loop); +} + */ + + +#include "hexport.h" +#include "hloop.h" + +#define DEFAULT_LOG_PORT 10514 + +BEGIN_EXTERN_C + +HV_EXPORT void network_logger(int loglevel, const char* buf, int len); +HV_EXPORT hio_t* nlog_listen(hloop_t* loop, int port); + +END_EXTERN_C + +#endif // HV_NLOG_H_ diff --git a/ww/libhv/event/noevent.c b/ww/libhv/event/noevent.c new file mode 100644 index 00000000..a03eecd1 --- /dev/null +++ b/ww/libhv/event/noevent.c @@ -0,0 +1,25 @@ +#include "iowatcher.h" + +#ifdef EVENT_NOEVENT +int iowatcher_init(hloop_t* loop) { + return 0; +} + +int iowatcher_cleanup(hloop_t* loop) { + return 0; +} + +int iowatcher_add_event(hloop_t* loop, int fd, int events) { + return 0; +} + +int iowatcher_del_event(hloop_t* loop, int fd, int events) { + return 0; +} + +int iowatcher_poll_events(hloop_t* loop, int timeout) { + hv_delay(timeout); + return 0; +} + +#endif diff --git a/ww/libhv/event/overlapio.c b/ww/libhv/event/overlapio.c new file mode 100644 index 00000000..4fe2cb6f --- /dev/null +++ b/ww/libhv/event/overlapio.c @@ -0,0 +1,419 @@ +// WARN: overlapio maybe need MemoryPool to avoid alloc/free +#include "iowatcher.h" + +#ifdef EVENT_IOCP +#include "overlapio.h" +#include "hevent.h" + +#define ACCEPTEX_NUM 10 + +int post_acceptex(hio_t* listenio, hoverlapped_t* hovlp) { + LPFN_ACCEPTEX AcceptEx = NULL; + GUID guidAcceptEx = WSAID_ACCEPTEX; + DWORD dwbytes = 0; + if (WSAIoctl(listenio->fd, SIO_GET_EXTENSION_FUNCTION_POINTER, + &guidAcceptEx, sizeof(guidAcceptEx), + &AcceptEx, sizeof(AcceptEx), + &dwbytes, NULL, NULL) != 0) { + return WSAGetLastError(); + } + int connfd = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED); + if (connfd < 0) { + return WSAGetLastError(); + } + if (hovlp == NULL) { + HV_ALLOC_SIZEOF(hovlp); + hovlp->buf.len = 20 + sizeof(struct sockaddr_in6) * 2; + HV_ALLOC(hovlp->buf.buf, hovlp->buf.len); + } + hovlp->fd = connfd; + hovlp->event = HV_READ; + hovlp->io = listenio; + if (AcceptEx(listenio->fd, connfd, hovlp->buf.buf, 0, sizeof(struct sockaddr_in6), sizeof(struct sockaddr_in6), + &dwbytes, &hovlp->ovlp) != TRUE) { + int err = WSAGetLastError(); + if (err != ERROR_IO_PENDING) { + fprintf(stderr, "AcceptEx error: %d\n", err); + return err; + } + } + return 0; +} + +int post_recv(hio_t* io, hoverlapped_t* hovlp) { + if (hovlp == NULL) { + HV_ALLOC_SIZEOF(hovlp); + } + hovlp->fd = io->fd; + hovlp->event = HV_READ; + hovlp->io = io; + hovlp->buf.len = io->readbuf.len; + if (io->io_type == HIO_TYPE_UDP || io->io_type == HIO_TYPE_IP) { + HV_ALLOC(hovlp->buf.buf, hovlp->buf.len); + } + else { + hovlp->buf.buf = io->readbuf.base; + } + //memset(hovlp->buf.buf, 0, hovlp->buf.len); + DWORD dwbytes = 0; + DWORD flags = 0; + int ret = 0; + if (io->io_type == HIO_TYPE_TCP) { + ret = WSARecv(io->fd, &hovlp->buf, 1, &dwbytes, &flags, &hovlp->ovlp, NULL); + } + else if (io->io_type == HIO_TYPE_UDP || + io->io_type == HIO_TYPE_IP) { + if (hovlp->addr == NULL) { + hovlp->addrlen = sizeof(struct sockaddr_in6); + HV_ALLOC(hovlp->addr, sizeof(struct sockaddr_in6)); + } + ret = WSARecvFrom(io->fd, &hovlp->buf, 1, &dwbytes, &flags, hovlp->addr, &hovlp->addrlen, &hovlp->ovlp, NULL); + } + else { + ret = -1; + } + //printd("WSARecv ret=%d bytes=%u\n", ret, dwbytes); + if (ret != 0) { + int err = WSAGetLastError(); + if (err != ERROR_IO_PENDING) { + fprintf(stderr, "WSARecv error: %d\n", err); + return err; + } + } + return 0; +} + +static void on_acceptex_complete(hio_t* io) { + printd("on_acceptex_complete------\n"); + hoverlapped_t* hovlp = (hoverlapped_t*)io->hovlp; + int listenfd = io->fd; + int connfd = hovlp->fd; + LPFN_GETACCEPTEXSOCKADDRS GetAcceptExSockaddrs = NULL; + GUID guidGetAcceptExSockaddrs = WSAID_GETACCEPTEXSOCKADDRS; + DWORD dwbytes = 0; + if (WSAIoctl(connfd, SIO_GET_EXTENSION_FUNCTION_POINTER, + &guidGetAcceptExSockaddrs, sizeof(guidGetAcceptExSockaddrs), + &GetAcceptExSockaddrs, sizeof(GetAcceptExSockaddrs), + &dwbytes, NULL, NULL) != 0) { + return; + } + struct sockaddr* plocaladdr = NULL; + struct sockaddr* ppeeraddr = NULL; + socklen_t localaddrlen; + socklen_t peeraddrlen; + GetAcceptExSockaddrs(hovlp->buf.buf, 0, sizeof(struct sockaddr_in6), sizeof(struct sockaddr_in6), + &plocaladdr, &localaddrlen, &ppeeraddr, &peeraddrlen); + memcpy(io->localaddr, plocaladdr, localaddrlen); + memcpy(io->peeraddr, ppeeraddr, peeraddrlen); + if (io->accept_cb) { + setsockopt(connfd, SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, (const char*)&listenfd, sizeof(int)); + hio_t* connio = hio_get(io->loop, connfd); + connio->userdata = io->userdata; + memcpy(connio->localaddr, io->localaddr, localaddrlen); + memcpy(connio->peeraddr, io->peeraddr, peeraddrlen); + /* + char localaddrstr[SOCKADDR_STRLEN] = {0}; + char peeraddrstr[SOCKADDR_STRLEN] = {0}; + printd("accept listenfd=%d connfd=%d [%s] <= [%s]\n", listenfd, connfd, + SOCKADDR_STR(connio->localaddr, localaddrstr), + SOCKADDR_STR(connio->peeraddr, peeraddrstr)); + */ + //printd("accept_cb------\n"); + io->accept_cb(connio); + //printd("accept_cb======\n"); + } + post_acceptex(io, hovlp); +} + +static void on_connectex_complete(hio_t* io) { + printd("on_connectex_complete------\n"); + hoverlapped_t* hovlp = (hoverlapped_t*)io->hovlp; + io->error = hovlp->error; + HV_FREE(io->hovlp); + if (io->error != 0) { + hio_close(io); + return; + } + if (io->connect_cb) { + setsockopt(io->fd, SOL_SOCKET, SO_UPDATE_CONNECT_CONTEXT, NULL, 0); + socklen_t addrlen = sizeof(struct sockaddr_in6); + getsockname(io->fd, io->localaddr, &addrlen); + addrlen = sizeof(struct sockaddr_in6); + getpeername(io->fd, io->peeraddr, &addrlen); + /* + char localaddrstr[SOCKADDR_STRLEN] = {0}; + char peeraddrstr[SOCKADDR_STRLEN] = {0}; + printd("connect connfd=%d [%s] => [%s]\n", io->fd, + SOCKADDR_STR(io->localaddr, localaddrstr), + SOCKADDR_STR(io->peeraddr, peeraddrstr)); + */ + //printd("connect_cb------\n"); + io->connect_cb(io); + //printd("connect_cb======\n"); + } +} + +static void on_wsarecv_complete(hio_t* io) { + printd("on_recv_complete------\n"); + hoverlapped_t* hovlp = (hoverlapped_t*)io->hovlp; + if (hovlp->bytes == 0) { + io->error = WSAGetLastError(); + hio_close(io); + return; + } + + if (io->read_cb) { + if (io->io_type == HIO_TYPE_UDP || io->io_type == HIO_TYPE_IP) { + if (hovlp->addr && hovlp->addrlen) { + hio_set_peeraddr(io, hovlp->addr, hovlp->addrlen); + } + } + //printd("read_cb------\n"); + io->read_cb(io, hovlp->buf.buf, hovlp->bytes); + //printd("read_cb======\n"); + } + + if (io->io_type == HIO_TYPE_TCP) { + // reuse hovlp + if (!io->closed) { + post_recv(io, hovlp); + } + } + else if (io->io_type == HIO_TYPE_UDP || + io->io_type == HIO_TYPE_IP) { + HV_FREE(hovlp->buf.buf); + HV_FREE(hovlp->addr); + HV_FREE(io->hovlp); + } +} + +static void on_wsasend_complete(hio_t* io) { + printd("on_send_complete------\n"); + hoverlapped_t* hovlp = (hoverlapped_t*)io->hovlp; + if (hovlp->bytes == 0) { + io->error = WSAGetLastError(); + hio_close(io); + goto end; + } + if (io->write_cb) { + if (io->io_type == HIO_TYPE_UDP || io->io_type == HIO_TYPE_IP) { + if (hovlp->addr) { + hio_set_peeraddr(io, hovlp->addr, hovlp->addrlen); + } + } + //printd("write_cb------\n"); + io->write_cb(io, hovlp->buf.buf, hovlp->bytes); + //printd("write_cb======\n"); + } +end: + if (io->hovlp) { + HV_FREE(hovlp->buf.buf); + HV_FREE(io->hovlp); + } +} + +static void hio_handle_events(hio_t* io) { + if ((io->events & HV_READ) && (io->revents & HV_READ)) { + if (io->accept) { + on_acceptex_complete(io); + } + else { + on_wsarecv_complete(io); + } + } + + if ((io->events & HV_WRITE) && (io->revents & HV_WRITE)) { + // NOTE: HV_WRITE just do once + // ONESHOT + iowatcher_del_event(io->loop, io->fd, HV_WRITE); + io->events &= ~HV_WRITE; + if (io->connect) { + io->connect = 0; + + on_connectex_complete(io); + } + else { + on_wsasend_complete(io); + } + } + + io->revents = 0; +} + +int hio_accept (hio_t* io) { + for (int i = 0; i < ACCEPTEX_NUM; ++i) { + post_acceptex(io, NULL); + } + io->accept = 1; + return hio_add(io, hio_handle_events, HV_READ); +} + +int hio_connect (hio_t* io) { + // NOTE: ConnectEx must call bind + struct sockaddr_in localaddr; + socklen_t addrlen = sizeof(localaddr); + memset(&localaddr, 0, addrlen); + localaddr.sin_family = AF_INET; + localaddr.sin_addr.s_addr = htonl(INADDR_ANY); + localaddr.sin_port = htons(0); + if (bind(io->fd, (struct sockaddr*)&localaddr, addrlen) < 0) { + perror("bind"); + goto error; + } + // ConnectEx + io->connectex = 1; + LPFN_CONNECTEX ConnectEx = NULL; + GUID guidConnectEx = WSAID_CONNECTEX; + DWORD dwbytes; + if (WSAIoctl(io->fd, SIO_GET_EXTENSION_FUNCTION_POINTER, + &guidConnectEx, sizeof(guidConnectEx), + &ConnectEx, sizeof(ConnectEx), + &dwbytes, NULL, NULL) != 0) { + goto error; + } + // NOTE: free on_connectex_complete + hoverlapped_t* hovlp; + HV_ALLOC_SIZEOF(hovlp); + hovlp->fd = io->fd; + hovlp->event = HV_WRITE; + hovlp->io = io; + if (ConnectEx(io->fd, io->peeraddr, sizeof(struct sockaddr_in6), NULL, 0, &dwbytes, &hovlp->ovlp) != TRUE) { + int err = WSAGetLastError(); + if (err != ERROR_IO_PENDING) { + fprintf(stderr, "AcceptEx error: %d\n", err); + goto error; + } + } + io->connect = 1; + return hio_add(io, hio_handle_events, HV_WRITE); +error: + hio_close(io); + return 0; +} + +int hio_read (hio_t* io) { + post_recv(io, NULL); + return hio_add(io, hio_handle_events, HV_READ); +} + +int hio_write(hio_t* io, const void* buf, size_t len) { + int nwrite = 0; +try_send: + if (io->io_type == HIO_TYPE_TCP) { + nwrite = send(io->fd, buf, len, 0); + } + else if (io->io_type == HIO_TYPE_UDP) { + nwrite = sendto(io->fd, buf, len, 0, io->peeraddr, sizeof(struct sockaddr_in6)); + } + else if (io->io_type == HIO_TYPE_IP) { + goto WSASend; + } + else { + nwrite = -1; + } + //printd("write retval=%d\n", nwrite); + if (nwrite < 0) { + if (socket_errno() == EAGAIN) { + nwrite = 0; + goto WSASend; + } + else { + perror("write"); + io->error = socket_errno(); + goto write_error; + } + } + if (nwrite == 0) { + goto disconnect; + } + if (io->write_cb) { + //printd("try_write_cb------\n"); + io->write_cb(io, buf, nwrite); + //printd("try_write_cb======\n"); + } + if (nwrite == len) { + //goto write_done; + return nwrite; + } +WSASend: + { + hoverlapped_t* hovlp; + HV_ALLOC_SIZEOF(hovlp); + hovlp->fd = io->fd; + hovlp->event = HV_WRITE; + hovlp->buf.len = len - nwrite; + // NOTE: free on_send_complete + HV_ALLOC(hovlp->buf.buf, hovlp->buf.len); + memcpy(hovlp->buf.buf, ((char*)buf) + nwrite, hovlp->buf.len); + hovlp->io = io; + DWORD dwbytes = 0; + DWORD flags = 0; + int ret = 0; + if (io->io_type == HIO_TYPE_TCP) { + ret = WSASend(io->fd, &hovlp->buf, 1, &dwbytes, flags, &hovlp->ovlp, NULL); + } + else if (io->io_type == HIO_TYPE_UDP || + io->io_type == HIO_TYPE_IP) { + ret = WSASendTo(io->fd, &hovlp->buf, 1, &dwbytes, flags, io->peeraddr, sizeof(struct sockaddr_in6), &hovlp->ovlp, NULL); + } + else { + ret = -1; + } + //printd("WSASend ret=%d bytes=%u\n", ret, dwbytes); + if (ret != 0) { + int err = WSAGetLastError(); + if (err != ERROR_IO_PENDING) { + fprintf(stderr, "WSASend error: %d\n", err); + return ret; + } + } + return hio_add(io, hio_handle_events, HV_WRITE); + } +write_error: +disconnect: + hio_close(io); + return 0; +} + +int hio_close (hio_t* io) { + if (io->closed) return 0; + io->closed = 1; + hio_done(io); + if (io->hovlp) { + hoverlapped_t* hovlp = (hoverlapped_t*)io->hovlp; + // NOTE: hread buf provided by caller + if (hovlp->buf.buf != io->readbuf.base) { + HV_FREE(hovlp->buf.buf); + } + HV_FREE(hovlp->addr); + HV_FREE(io->hovlp); + } + if (io->close_cb) { + //printd("close_cb------\n"); + io->close_cb(io); + //printd("close_cb======\n"); + } + if (io->io_type & HIO_TYPE_SOCKET) { +#ifdef USE_DISCONNECTEX + // DisconnectEx reuse socket + if (io->connectex) { + io->connectex = 0; + LPFN_DISCONNECTEX DisconnectEx = NULL; + GUID guidDisconnectEx = WSAID_DISCONNECTEX; + DWORD dwbytes; + if (WSAIoctl(io->fd, SIO_GET_EXTENSION_FUNCTION_POINTER, + &guidDisconnectEx, sizeof(guidDisconnectEx), + &DisconnectEx, sizeof(DisconnectEx), + &dwbytes, NULL, NULL) != 0) { + return; + } + DisconnectEx(io->fd, NULL, 0, 0); + } +#else + closesocket(io->fd); +#endif + } + return 0; +} + +#endif diff --git a/ww/libhv/event/overlapio.h b/ww/libhv/event/overlapio.h new file mode 100644 index 00000000..69fb0267 --- /dev/null +++ b/ww/libhv/event/overlapio.h @@ -0,0 +1,33 @@ +#ifndef HV_OVERLAPPED_H_ +#define HV_OVERLAPPED_H_ + +#include "iowatcher.h" + +#ifdef EVENT_IOCP + +#include "hbuf.h" +#include "hsocket.h" +#include +#ifdef _MSC_VER +#pragma comment(lib, "mswsock.lib") +#endif + +typedef struct hoverlapped_s { + OVERLAPPED ovlp; + int fd; + int event; + WSABUF buf; + int bytes; + int error; + hio_t* io; + // for recvfrom + struct sockaddr* addr; + int addrlen; +} hoverlapped_t; + +int post_acceptex(hio_t* listenio, hoverlapped_t* hovlp); +int post_recv(hio_t* io, hoverlapped_t* hovlp); + +#endif + +#endif // HV_OVERLAPPED_H_ diff --git a/ww/libhv/event/poll.c b/ww/libhv/event/poll.c new file mode 100644 index 00000000..4c1073bf --- /dev/null +++ b/ww/libhv/event/poll.c @@ -0,0 +1,135 @@ +#include "iowatcher.h" + +#ifdef EVENT_POLL +#include "hplatform.h" +#include "hdef.h" +#include "hevent.h" + +#ifdef OS_WIN +#define poll WSAPoll +#endif + +#ifdef OS_LINUX +#include +#endif + +#include "array.h" +#define FDS_INIT_SIZE 64 +ARRAY_DECL(struct pollfd, pollfds); + +typedef struct poll_ctx_s { + int capacity; + struct pollfds fds; +} poll_ctx_t; + +int iowatcher_init(hloop_t* loop) { + if (loop->iowatcher) return 0; + poll_ctx_t* poll_ctx; + HV_ALLOC_SIZEOF(poll_ctx); + pollfds_init(&poll_ctx->fds, FDS_INIT_SIZE); + loop->iowatcher = poll_ctx; + return 0; +} + +int iowatcher_cleanup(hloop_t* loop) { + if (loop->iowatcher == NULL) return 0; + poll_ctx_t* poll_ctx = (poll_ctx_t*)loop->iowatcher; + pollfds_cleanup(&poll_ctx->fds); + HV_FREE(loop->iowatcher); + return 0; +} + +int iowatcher_add_event(hloop_t* loop, int fd, int events) { + if (loop->iowatcher == NULL) { + iowatcher_init(loop); + } + poll_ctx_t* poll_ctx = (poll_ctx_t*)loop->iowatcher; + hio_t* io = loop->ios.ptr[fd]; + int idx = io->event_index[0]; + struct pollfd* pfd = NULL; + if (idx < 0) { + io->event_index[0] = idx = poll_ctx->fds.size; + if (idx == poll_ctx->fds.maxsize) { + pollfds_double_resize(&poll_ctx->fds); + } + poll_ctx->fds.size++; + pfd = poll_ctx->fds.ptr + idx; + pfd->fd = fd; + pfd->events = 0; + pfd->revents = 0; + } + else { + pfd = poll_ctx->fds.ptr + idx; + assert(pfd->fd == fd); + } + if (events & HV_READ) { + pfd->events |= POLLIN; + } + if (events & HV_WRITE) { + pfd->events |= POLLOUT; + } + return 0; +} + +int iowatcher_del_event(hloop_t* loop, int fd, int events) { + poll_ctx_t* poll_ctx = (poll_ctx_t*)loop->iowatcher; + if (poll_ctx == NULL) return 0; + hio_t* io = loop->ios.ptr[fd]; + + int idx = io->event_index[0]; + if (idx < 0) return 0; + struct pollfd* pfd = poll_ctx->fds.ptr + idx; + assert(pfd->fd == fd); + if (events & HV_READ) { + pfd->events &= ~POLLIN; + } + if (events & HV_WRITE) { + pfd->events &= ~POLLOUT; + } + if (pfd->events == 0) { + pollfds_del_nomove(&poll_ctx->fds, idx); + // NOTE: correct event_index + if (idx < poll_ctx->fds.size) { + hio_t* last = loop->ios.ptr[poll_ctx->fds.ptr[idx].fd]; + last->event_index[0] = idx; + } + io->event_index[0] = -1; + } + return 0; +} + +int iowatcher_poll_events(hloop_t* loop, int timeout) { + poll_ctx_t* poll_ctx = (poll_ctx_t*)loop->iowatcher; + if (poll_ctx == NULL) return 0; + if (poll_ctx->fds.size == 0) return 0; + int npoll = poll(poll_ctx->fds.ptr, poll_ctx->fds.size, timeout); + if (npoll < 0) { + if (errno == EINTR) { + return 0; + } + perror("poll"); + return npoll; + } + if (npoll == 0) return 0; + int nevents = 0; + for (int i = 0; i < poll_ctx->fds.size; ++i) { + int fd = poll_ctx->fds.ptr[i].fd; + short revents = poll_ctx->fds.ptr[i].revents; + if (revents) { + ++nevents; + hio_t* io = loop->ios.ptr[fd]; + if (io) { + if (revents & (POLLIN | POLLHUP | POLLERR)) { + io->revents |= HV_READ; + } + if (revents & (POLLOUT | POLLHUP | POLLERR)) { + io->revents |= HV_WRITE; + } + EVENT_PENDING(io); + } + } + if (nevents == npoll) break; + } + return nevents; +} +#endif diff --git a/ww/libhv/event/rudp.c b/ww/libhv/event/rudp.c new file mode 100644 index 00000000..2a3df23b --- /dev/null +++ b/ww/libhv/event/rudp.c @@ -0,0 +1,167 @@ +#include "rudp.h" + +#if WITH_RUDP + +#include "hevent.h" + +void rudp_entry_free(rudp_entry_t* entry) { +#if WITH_KCP + kcp_release(&entry->kcp); +#endif + HV_FREE(entry); +} + +void rudp_init(rudp_t* rudp) { + // printf("rudp init\n"); + rudp->rb_root.rb_node = NULL; + hmutex_init(&rudp->mutex); +} + +void rudp_cleanup(rudp_t* rudp) { + // printf("rudp cleaup\n"); + struct rb_node* n = NULL; + rudp_entry_t* e = NULL; + while ((n = rudp->rb_root.rb_node)) { + e = rb_entry(n, rudp_entry_t, rb_node); + rb_erase(n, &rudp->rb_root); + rudp_entry_free(e); + } + hmutex_destroy(&rudp->mutex); +} + +bool rudp_insert(rudp_t* rudp, rudp_entry_t* entry) { + struct rb_node** n = &rudp->rb_root.rb_node; + struct rb_node* parent = NULL; + rudp_entry_t* e = NULL; + int cmp = 0; + bool exists = false; + while (*n) { + parent = *n; + e = rb_entry(*n, rudp_entry_t, rb_node); + cmp = memcmp(&entry->addr, &e->addr, sizeof(sockaddr_u)); + if (cmp < 0) { + n = &(*n)->rb_left; + } else if (cmp > 0) { + n = &(*n)->rb_right; + } else { + exists = true; + break; + } + } + + if (!exists) { + rb_link_node(&entry->rb_node, parent, n); + rb_insert_color(&entry->rb_node, &rudp->rb_root); + } + return !exists; +} + +rudp_entry_t* rudp_search(rudp_t* rudp, struct sockaddr* addr) { + struct rb_node* n = rudp->rb_root.rb_node; + rudp_entry_t* e = NULL; + int cmp = 0; + bool exists = false; + while (n) { + e = rb_entry(n, rudp_entry_t, rb_node); + cmp = memcmp(addr, &e->addr, sizeof(sockaddr_u)); + if (cmp < 0) { + n = n->rb_left; + } else if (cmp > 0) { + n = n->rb_right; + } else { + exists = true; + break; + } + } + return exists ? e : NULL; +} + +rudp_entry_t* rudp_remove(rudp_t* rudp, struct sockaddr* addr) { + hmutex_lock(&rudp->mutex); + rudp_entry_t* e = rudp_search(rudp, addr); + if (e) { + // printf("rudp_remove "); + // SOCKADDR_PRINT(addr); + rb_erase(&e->rb_node, &rudp->rb_root); + } + hmutex_unlock(&rudp->mutex); + return e; +} + +rudp_entry_t* rudp_get(rudp_t* rudp, struct sockaddr* addr) { + hmutex_lock(&rudp->mutex); + struct rb_node** n = &rudp->rb_root.rb_node; + struct rb_node* parent = NULL; + rudp_entry_t* e = NULL; + int cmp = 0; + bool exists = false; + // search + while (*n) { + parent = *n; + e = rb_entry(*n, rudp_entry_t, rb_node); + cmp = memcmp(addr, &e->addr, sizeof(sockaddr_u)); + if (cmp < 0) { + n = &(*n)->rb_left; + } else if (cmp > 0) { + n = &(*n)->rb_right; + } else { + exists = true; + break; + } + } + + if (!exists) { + // insert + // printf("rudp_insert "); + // SOCKADDR_PRINT(addr); + HV_ALLOC_SIZEOF(e); + memcpy(&e->addr, addr, SOCKADDR_LEN(addr)); + rb_link_node(&e->rb_node, parent, n); + rb_insert_color(&e->rb_node, &rudp->rb_root); + } + hmutex_unlock(&rudp->mutex); + return e; +} + +void rudp_del(rudp_t* rudp, struct sockaddr* addr) { + hmutex_lock(&rudp->mutex); + rudp_entry_t* e = rudp_search(rudp, addr); + if (e) { + // printf("rudp_remove "); + // SOCKADDR_PRINT(addr); + rb_erase(&e->rb_node, &rudp->rb_root); + rudp_entry_free(e); + } + hmutex_unlock(&rudp->mutex); +} + +rudp_entry_t* hio_get_rudp(hio_t* io) { + rudp_entry_t* rudp = rudp_get(&io->rudp, io->peeraddr); + rudp->io = io; + return rudp; +} + +static void hio_close_rudp_event_cb(hevent_t* ev) { + rudp_entry_t* entry = (rudp_entry_t*)ev->userdata; + rudp_del(&entry->io->rudp, (struct sockaddr*)&entry->addr); + // rudp_entry_free(entry); +} + +int hio_close_rudp(hio_t* io, struct sockaddr* peeraddr) { + if (peeraddr == NULL) peeraddr = io->peeraddr; + // NOTE: do rudp_del for thread-safe + rudp_entry_t* entry = rudp_get(&io->rudp, peeraddr); + // NOTE: just rudp_remove first, do rudp_entry_free async for safe. + // rudp_entry_t* entry = rudp_remove(&io->rudp, peeraddr); + if (entry) { + hevent_t ev; + memset(&ev, 0, sizeof(ev)); + ev.cb = hio_close_rudp_event_cb; + ev.userdata = entry; + ev.priority = HEVENT_HIGH_PRIORITY; + hloop_post_event(io->loop, &ev); + } + return 0; +} + +#endif diff --git a/ww/libhv/event/rudp.h b/ww/libhv/event/rudp.h new file mode 100644 index 00000000..0c30392a --- /dev/null +++ b/ww/libhv/event/rudp.h @@ -0,0 +1,52 @@ +#ifndef HV_RUDP_H_ +#define HV_RUDP_H_ + +#include "hloop.h" + +#if WITH_RUDP + +#include "rbtree.h" +#include "hsocket.h" +#include "hmutex.h" +#if WITH_KCP +#include "kcp/hkcp.h" +#endif + +typedef struct rudp_s { + struct rb_root rb_root; + hmutex_t mutex; +} rudp_t; + +typedef struct rudp_entry_s { + struct rb_node rb_node; + sockaddr_u addr; // key + // val + hio_t* io; +#if WITH_KCP + kcp_t kcp; +#endif +} rudp_entry_t; + +// NOTE: rudp_entry_t alloc when rudp_get +void rudp_entry_free(rudp_entry_t* entry); + +void rudp_init(rudp_t* rudp); +void rudp_cleanup(rudp_t* rudp); + +bool rudp_insert(rudp_t* rudp, rudp_entry_t* entry); +// NOTE: just rb_erase, not free +rudp_entry_t* rudp_remove(rudp_t* rudp, struct sockaddr* addr); +rudp_entry_t* rudp_search(rudp_t* rudp, struct sockaddr* addr); +#define rudp_has(rudp, addr) (rudp_search(rudp, addr) != NULL) + +// rudp_search + malloc + rudp_insert +rudp_entry_t* rudp_get(rudp_t* rudp, struct sockaddr* addr); +// rudp_remove + free +void rudp_del(rudp_t* rudp, struct sockaddr* addr); + +// rudp_get(&io->rudp, io->peeraddr) +rudp_entry_t* hio_get_rudp(hio_t* io); + +#endif // WITH_RUDP + +#endif // HV_RUDP_H_ diff --git a/ww/libhv/event/select.c b/ww/libhv/event/select.c new file mode 100644 index 00000000..8a35ea1e --- /dev/null +++ b/ww/libhv/event/select.c @@ -0,0 +1,169 @@ +#include "iowatcher.h" + +#ifdef EVENT_SELECT +#include "hplatform.h" +#include "hdef.h" +#include "hevent.h" +#include "hsocket.h" + +typedef struct select_ctx_s { + int max_fd; + fd_set readfds; + fd_set writefds; + int nread; + int nwrite; +} select_ctx_t; + +int iowatcher_init(hloop_t* loop) { + if (loop->iowatcher) return 0; + select_ctx_t* select_ctx; + HV_ALLOC_SIZEOF(select_ctx); + select_ctx->max_fd = -1; + FD_ZERO(&select_ctx->readfds); + FD_ZERO(&select_ctx->writefds); + select_ctx->nread = 0; + select_ctx->nwrite = 0; + loop->iowatcher = select_ctx; + return 0; +} + +int iowatcher_cleanup(hloop_t* loop) { + HV_FREE(loop->iowatcher); + return 0; +} + +int iowatcher_add_event(hloop_t* loop, int fd, int events) { + if (loop->iowatcher == NULL) { + iowatcher_init(loop); + } + select_ctx_t* select_ctx = (select_ctx_t*)loop->iowatcher; + if (fd > select_ctx->max_fd) { + select_ctx->max_fd = fd; + } + if (events & HV_READ) { + if (!FD_ISSET(fd, &select_ctx->readfds)) { + FD_SET(fd, &select_ctx->readfds); + select_ctx->nread++; + } + } + if (events & HV_WRITE) { + if (!FD_ISSET(fd, &select_ctx->writefds)) { + FD_SET(fd, &select_ctx->writefds); + select_ctx->nwrite++; + } + } + return 0; +} + +int iowatcher_del_event(hloop_t* loop, int fd, int events) { + select_ctx_t* select_ctx = (select_ctx_t*)loop->iowatcher; + if (select_ctx == NULL) return 0; + if (fd == select_ctx->max_fd) { + select_ctx->max_fd = -1; + } + if (events & HV_READ) { + if (FD_ISSET(fd, &select_ctx->readfds)) { + FD_CLR(fd, &select_ctx->readfds); + select_ctx->nread--; + } + } + if (events & HV_WRITE) { + if (FD_ISSET(fd, &select_ctx->writefds)) { + FD_CLR(fd, &select_ctx->writefds); + select_ctx->nwrite--; + } + } + return 0; +} + +static int find_max_active_fd(hloop_t* loop) { + hio_t* io = NULL; + for (int i = loop->ios.maxsize-1; i >= 0; --i) { + io = loop->ios.ptr[i]; + if (io && io->active && io->events) return i; + } + return -1; +} + +static int remove_bad_fds(hloop_t* loop) { + select_ctx_t* select_ctx = (select_ctx_t*)loop->iowatcher; + if (select_ctx == NULL) return 0; + int badfds = 0; + int error = 0; + socklen_t optlen = sizeof(error); + for (int fd = 0; fd <= select_ctx->max_fd; ++fd) { + if (FD_ISSET(fd, &select_ctx->readfds) || + FD_ISSET(fd, &select_ctx->writefds)) { + error = 0; + optlen = sizeof(int); + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (char*)&error, &optlen) < 0 || error != 0) { + ++badfds; + hio_t* io = loop->ios.ptr[fd]; + if (io) { + hio_del(io, HV_RDWR); + } + } + } + } + return badfds; +} + +int iowatcher_poll_events(hloop_t* loop, int timeout) { + select_ctx_t* select_ctx = (select_ctx_t*)loop->iowatcher; + if (select_ctx == NULL) return 0; + if (select_ctx->nread == 0 && select_ctx->nwrite == 0) { + return 0; + } + int max_fd = select_ctx->max_fd; + fd_set readfds = select_ctx->readfds; + fd_set writefds = select_ctx->writefds; + if (max_fd == -1) { + select_ctx->max_fd = max_fd = find_max_active_fd(loop); + } + struct timeval tv, *tp; + if (timeout == INFINITE) { + tp = NULL; + } + else { + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + tp = &tv; + } + int nselect = select(max_fd+1, &readfds, &writefds, NULL, tp); + if (nselect < 0) { +#ifdef OS_WIN + if (WSAGetLastError() == WSAENOTSOCK) { +#else + if (errno == EBADF) { + perror("select"); +#endif + remove_bad_fds(loop); + return -EBADF; + } + return nselect; + } + if (nselect == 0) return 0; + int nevents = 0; + int revents = 0; + for (int fd = 0; fd <= max_fd; ++fd) { + revents = 0; + if (FD_ISSET(fd, &readfds)) { + ++nevents; + revents |= HV_READ; + } + if (FD_ISSET(fd, &writefds)) { + ++nevents; + revents |= HV_WRITE; + } + if (revents) { + hio_t* io = loop->ios.ptr[fd]; + if (io) { + io->revents = revents; + EVENT_PENDING(io); + } + } + if (nevents == nselect) break; + } + return nevents; +} +#endif diff --git a/ww/libhv/event/unpack.c b/ww/libhv/event/unpack.c new file mode 100644 index 00000000..10048a5b --- /dev/null +++ b/ww/libhv/event/unpack.c @@ -0,0 +1,174 @@ +#include "unpack.h" +#include "hevent.h" +#include "herr.h" +#include "hlog.h" +#include "hmath.h" + +// int hio_unpack(hio_t* io, void* buf, int readbytes) { +// unpack_setting_t* setting = io->unpack_setting; +// switch(setting->mode) { +// case UNPACK_BY_FIXED_LENGTH: +// return hio_unpack_by_fixed_length(io, buf, readbytes); +// case UNPACK_BY_DELIMITER: +// return hio_unpack_by_delimiter(io, buf, readbytes); +// case UNPACK_BY_LENGTH_FIELD: +// return hio_unpack_by_length_field(io, buf, readbytes); +// default: +// hio_read_cb(io, buf, readbytes); +// return readbytes; +// } +// } + +// int hio_unpack_by_fixed_length(hio_t* io, void* buf, int readbytes) { +// const unsigned char* sp = (const unsigned char*)io->readbuf.base + io->readbuf.head; +// const unsigned char* ep = (const unsigned char*)buf + readbytes; +// unpack_setting_t* setting = io->unpack_setting; + +// int fixed_length = setting->fixed_length; +// assert(io->readbuf.len >= fixed_length); + +// const unsigned char* p = sp; +// int remain = ep - p; +// int handled = 0; +// while (remain >= fixed_length) { +// hio_read_cb(io, (void*)p, fixed_length); +// handled += fixed_length; +// p += fixed_length; +// remain -= fixed_length; +// } + +// io->readbuf.head = 0; +// io->readbuf.tail = remain; +// if (remain) { +// // [p, p+remain] => [base, base+remain] +// if (p != (unsigned char*)io->readbuf.base) { +// memmove(io->readbuf.base, p, remain); +// } +// } + +// return handled; +// } + +// int hio_unpack_by_delimiter(hio_t* io, void* buf, int readbytes) { +// const unsigned char* sp = (const unsigned char*)io->readbuf.base + io->readbuf.head; +// const unsigned char* ep = (const unsigned char*)buf + readbytes; +// unpack_setting_t* setting = io->unpack_setting; + +// unsigned char* delimiter = setting->delimiter; +// int delimiter_bytes = setting->delimiter_bytes; + +// const unsigned char* p = (const unsigned char*)buf - delimiter_bytes + 1; +// if (p < sp) p = sp; +// int remain = ep - p; +// int handled = 0; +// int i = 0; +// while (remain >= delimiter_bytes) { +// for (i = 0; i < delimiter_bytes; ++i) { +// if (p[i] != delimiter[i]) { +// goto not_match; +// } +// } +// match: +// p += delimiter_bytes; +// remain -= delimiter_bytes; +// hio_read_cb(io, (void*)sp, p - sp); +// handled += p - sp; +// sp = p; +// continue; +// not_match: +// ++p; +// --remain; +// } + +// remain = ep - sp; +// io->readbuf.head = 0; +// io->readbuf.tail = remain; +// if (remain) { +// // [sp, sp+remain] => [base, base+remain] +// if (sp != (unsigned char*)io->readbuf.base) { +// memmove(io->readbuf.base, sp, remain); +// } +// if (io->readbuf.tail == io->readbuf.len) { +// if (io->readbuf.len >= setting->package_max_length) { +// hloge("recv package over %d bytes!", (int)setting->package_max_length); +// io->error = ERR_OVER_LIMIT; +// hio_close(io); +// return -1; +// } +// int newsize = MIN(io->readbuf.len * 2, setting->package_max_length); +// hio_alloc_readbuf(io, newsize); +// } +// } + +// return handled; +// } + +// int hio_unpack_by_length_field(hio_t* io, void* buf, int readbytes) { +// const unsigned char* sp = (const unsigned char*)io->readbuf.base + io->readbuf.head; +// const unsigned char* ep = (const unsigned char*)buf + readbytes; +// unpack_setting_t* setting = io->unpack_setting; + +// const unsigned char* p = sp; +// int remain = ep - p; +// int handled = 0; +// unsigned int head_len = setting->body_offset; +// unsigned int body_len = 0; +// unsigned int package_len = head_len; +// const unsigned char* lp = NULL; +// while (remain >= setting->body_offset) { +// body_len = 0; +// lp = p + setting->length_field_offset; +// if (setting->length_field_coding == BIG_ENDIAN) { +// for (int i = 0; i < setting->length_field_bytes; ++i) { +// body_len = (body_len << 8) | (unsigned int)*lp++; +// } +// } +// else if (setting->length_field_coding == LITTLE_ENDIAN) { +// for (int i = 0; i < setting->length_field_bytes; ++i) { +// body_len |= ((unsigned int)*lp++) << (i * 8); +// } +// } +// else if (setting->length_field_coding == ENCODE_BY_VARINT) { +// int varint_bytes = ep - lp; +// body_len = varint_decode(lp, &varint_bytes); +// if (varint_bytes == 0) break; +// if (varint_bytes == -1) { +// hloge("varint is too big!"); +// io->error = ERR_OVER_LIMIT; +// hio_close(io); +// return -1; +// } +// head_len = setting->body_offset + varint_bytes - setting->length_field_bytes; +// } +// package_len = head_len + body_len + setting->length_adjustment; +// if (remain >= package_len) { +// hio_read_cb(io, (void*)p, package_len); +// handled += package_len; +// p += package_len; +// remain -= package_len; +// } else { +// break; +// } +// } + +// io->readbuf.head = 0; +// io->readbuf.tail = remain; +// if (remain) { +// // [p, p+remain] => [base, base+remain] +// if (p != (unsigned char*)io->readbuf.base) { +// memmove(io->readbuf.base, p, remain); +// } +// if (package_len > io->readbuf.len) { +// if (package_len > setting->package_max_length) { +// hloge("package length over %d bytes!", (int)setting->package_max_length); +// io->error = ERR_OVER_LIMIT; +// hio_close(io); +// return -1; +// } +// int newsize = LIMIT(package_len, io->readbuf.len * 2, setting->package_max_length); +// hio_alloc_readbuf(io, newsize); +// } +// } + +// return handled; +// } diff --git a/ww/libhv/event/unpack.h b/ww/libhv/event/unpack.h new file mode 100644 index 00000000..3738587e --- /dev/null +++ b/ww/libhv/event/unpack.h @@ -0,0 +1,11 @@ +#ifndef HV_UNPACK_H_ +#define HV_UNPACK_H_ + +#include "hloop.h" + +int hio_unpack(hio_t* io, void* buf, int readbytes); +int hio_unpack_by_fixed_length(hio_t* io, void* buf, int readbytes); +int hio_unpack_by_delimiter(hio_t* io, void* buf, int readbytes); +int hio_unpack_by_length_field(hio_t* io, void* buf, int readbytes); + +#endif // HV_UNPACK_H_ diff --git a/ww/libhv/event/wepoll/LICENSE b/ww/libhv/event/wepoll/LICENSE new file mode 100644 index 00000000..d7fc4b11 --- /dev/null +++ b/ww/libhv/event/wepoll/LICENSE @@ -0,0 +1,28 @@ +wepoll - epoll for Windows +https://github.com/piscisaureus/wepoll + +Copyright 2012-2020, Bert Belder +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/ww/libhv/event/wepoll/README.md b/ww/libhv/event/wepoll/README.md new file mode 100644 index 00000000..d334d083 --- /dev/null +++ b/ww/libhv/event/wepoll/README.md @@ -0,0 +1,202 @@ +# wepoll - epoll for windows + +[![][ci status badge]][ci status link] + +This library implements the [epoll][man epoll] API for Windows +applications. It is fast and scalable, and it closely resembles the API +and behavior of Linux' epoll. + +## Rationale + +Unlike Linux, OS X, and many other operating systems, Windows doesn't +have a good API for receiving socket state notifications. It only +supports the `select` and `WSAPoll` APIs, but they +[don't scale][select scale] and suffer from +[other issues][wsapoll broken]. + +Using I/O completion ports isn't always practical when software is +designed to be cross-platform. Wepoll offers an alternative that is +much closer to a drop-in replacement for software that was designed +to run on Linux. + +## Features + +* Can poll 100000s of sockets efficiently. +* Fully thread-safe. +* Multiple threads can poll the same epoll port. +* Sockets can be added to multiple epoll sets. +* All epoll events (`EPOLLIN`, `EPOLLOUT`, `EPOLLPRI`, `EPOLLRDHUP`) + are supported. +* Level-triggered and one-shot (`EPOLLONESTHOT`) modes are supported +* Trivial to embed: you need [only two files][dist]. + +## Limitations + +* Only works with sockets. +* Edge-triggered (`EPOLLET`) mode isn't supported. + +## How to use + +The library is [distributed][dist] as a single source file +([wepoll.c][wepoll.c]) and a single header file ([wepoll.h][wepoll.h]).
+Compile the .c file as part of your project, and include the header wherever +needed. + +## Compatibility + +* Requires Windows Vista or higher. +* Can be compiled with recent versions of MSVC, Clang, and GCC. + +## API + +### General remarks + +* The epoll port is a `HANDLE`, not a file descriptor. +* All functions set both `errno` and `GetLastError()` on failure. +* For more extensive documentation, see the [epoll(7) man page][man epoll], + and the per-function man pages that are linked below. + +### epoll_create/epoll_create1 + +```c +HANDLE epoll_create(int size); +HANDLE epoll_create1(int flags); +``` + +* Create a new epoll instance (port). +* `size` is ignored but most be greater than zero. +* `flags` must be zero as there are no supported flags. +* Returns `NULL` on failure. +* [Linux man page][man epoll_create] + +### epoll_close + +```c +int epoll_close(HANDLE ephnd); +``` + +* Close an epoll port. +* Do not attempt to close the epoll port with `close()`, + `CloseHandle()` or `closesocket()`. + +### epoll_ctl + +```c +int epoll_ctl(HANDLE ephnd, + int op, + SOCKET sock, + struct epoll_event* event); +``` + +* Control which socket events are monitored by an epoll port. +* `ephnd` must be a HANDLE created by + [`epoll_create()`](#epoll_createepoll_create1) or + [`epoll_create1()`](#epoll_createepoll_create1). +* `op` must be one of `EPOLL_CTL_ADD`, `EPOLL_CTL_MOD`, `EPOLL_CTL_DEL`. +* `sock` must be a valid socket created by [`socket()`][msdn socket], + [`WSASocket()`][msdn wsasocket], or [`accept()`][msdn accept]. +* `event` should be a pointer to a [`struct epoll_event`](#struct-epoll_event).
+ If `op` is `EPOLL_CTL_DEL` then the `event` parameter is ignored, and it + may be `NULL`. +* Returns 0 on success, -1 on failure. +* It is recommended to always explicitly remove a socket from its epoll + set using `EPOLL_CTL_DEL` *before* closing it.
+ As on Linux, closed sockets are automatically removed from the epoll set, but + wepoll may not be able to detect that a socket was closed until the next call + to [`epoll_wait()`](#epoll_wait). +* [Linux man page][man epoll_ctl] + +### epoll_wait + +```c +int epoll_wait(HANDLE ephnd, + struct epoll_event* events, + int maxevents, + int timeout); +``` + +* Receive socket events from an epoll port. +* `events` should point to a caller-allocated array of + [`epoll_event`](#struct-epoll_event) structs, which will receive the + reported events. +* `maxevents` is the maximum number of events that will be written to the + `events` array, and must be greater than zero. +* `timeout` specifies whether to block when no events are immediately available. + - `<0` block indefinitely + - `0` report any events that are already waiting, but don't block + - `≥1` block for at most N milliseconds +* Return value: + - `-1` an error occurred + - `0` timed out without any events to report + - `≥1` the number of events stored in the `events` buffer +* [Linux man page][man epoll_wait] + +### struct epoll_event + +```c +typedef union epoll_data { + void* ptr; + int fd; + uint32_t u32; + uint64_t u64; + SOCKET sock; /* Windows specific */ + HANDLE hnd; /* Windows specific */ +} epoll_data_t; +``` + +```c +struct epoll_event { + uint32_t events; /* Epoll events and flags */ + epoll_data_t data; /* User data variable */ +}; +``` + +* The `events` field is a bit mask containing the events being + monitored/reported, and optional flags.
+ Flags are accepted by [`epoll_ctl()`](#epoll_ctl), but they are not reported + back by [`epoll_wait()`](#epoll_wait). +* The `data` field can be used to associate application-specific information + with a socket; its value will be returned unmodified by + [`epoll_wait()`](#epoll_wait). +* [Linux man page][man epoll_ctl] + +| Event | Description | +|---------------|----------------------------------------------------------------------| +| `EPOLLIN` | incoming data available, or incoming connection ready to be accepted | +| `EPOLLOUT` | ready to send data, or outgoing connection successfully established | +| `EPOLLRDHUP` | remote peer initiated graceful socket shutdown | +| `EPOLLPRI` | out-of-band data available for reading | +| `EPOLLERR` | socket error1 | +| `EPOLLHUP` | socket hang-up1 | +| `EPOLLRDNORM` | same as `EPOLLIN` | +| `EPOLLRDBAND` | same as `EPOLLPRI` | +| `EPOLLWRNORM` | same as `EPOLLOUT` | +| `EPOLLWRBAND` | same as `EPOLLOUT` | +| `EPOLLMSG` | never reported | + +| Flag | Description | +|------------------|---------------------------| +| `EPOLLONESHOT` | report event(s) only once | +| `EPOLLET` | not supported by wepoll | +| `EPOLLEXCLUSIVE` | not supported by wepoll | +| `EPOLLWAKEUP` | not supported by wepoll | + +1: the `EPOLLERR` and `EPOLLHUP` events may always be reported by +[`epoll_wait()`](#epoll_wait), regardless of the event mask that was passed to +[`epoll_ctl()`](#epoll_ctl). + + +[ci status badge]: https://ci.appveyor.com/api/projects/status/github/piscisaureus/wepoll?branch=master&svg=true +[ci status link]: https://ci.appveyor.com/project/piscisaureus/wepoll/branch/master +[dist]: https://github.com/piscisaureus/wepoll/tree/dist +[man epoll]: http://man7.org/linux/man-pages/man7/epoll.7.html +[man epoll_create]: http://man7.org/linux/man-pages/man2/epoll_create.2.html +[man epoll_ctl]: http://man7.org/linux/man-pages/man2/epoll_ctl.2.html +[man epoll_wait]: http://man7.org/linux/man-pages/man2/epoll_wait.2.html +[msdn accept]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms737526(v=vs.85).aspx +[msdn socket]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms740506(v=vs.85).aspx +[msdn wsasocket]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms742212(v=vs.85).aspx +[select scale]: https://daniel.haxx.se/docs/poll-vs-select.html +[wsapoll broken]: https://daniel.haxx.se/blog/2012/10/10/wsapoll-is-broken/ +[wepoll.c]: https://github.com/piscisaureus/wepoll/blob/dist/wepoll.c +[wepoll.h]: https://github.com/piscisaureus/wepoll/blob/dist/wepoll.h diff --git a/ww/libhv/event/wepoll/wepoll.c b/ww/libhv/event/wepoll/wepoll.c new file mode 100644 index 00000000..186d3f2d --- /dev/null +++ b/ww/libhv/event/wepoll/wepoll.c @@ -0,0 +1,2253 @@ +/* + * wepoll - epoll for Windows + * https://github.com/piscisaureus/wepoll + * + * Copyright 2012-2020, Bert Belder + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef WEPOLL_EXPORT +#define WEPOLL_EXPORT +#endif + +#include + +enum EPOLL_EVENTS { + EPOLLIN = (int) (1U << 0), + EPOLLPRI = (int) (1U << 1), + EPOLLOUT = (int) (1U << 2), + EPOLLERR = (int) (1U << 3), + EPOLLHUP = (int) (1U << 4), + EPOLLRDNORM = (int) (1U << 6), + EPOLLRDBAND = (int) (1U << 7), + EPOLLWRNORM = (int) (1U << 8), + EPOLLWRBAND = (int) (1U << 9), + EPOLLMSG = (int) (1U << 10), /* Never reported. */ + EPOLLRDHUP = (int) (1U << 13), + EPOLLONESHOT = (int) (1U << 31) +}; + +#define EPOLLIN (1U << 0) +#define EPOLLPRI (1U << 1) +#define EPOLLOUT (1U << 2) +#define EPOLLERR (1U << 3) +#define EPOLLHUP (1U << 4) +#define EPOLLRDNORM (1U << 6) +#define EPOLLRDBAND (1U << 7) +#define EPOLLWRNORM (1U << 8) +#define EPOLLWRBAND (1U << 9) +#define EPOLLMSG (1U << 10) +#define EPOLLRDHUP (1U << 13) +#define EPOLLONESHOT (1U << 31) + +#define EPOLL_CTL_ADD 1 +#define EPOLL_CTL_MOD 2 +#define EPOLL_CTL_DEL 3 + +typedef void* HANDLE; +typedef uintptr_t SOCKET; + +typedef union epoll_data { + void* ptr; + int fd; + uint32_t u32; + uint64_t u64; + SOCKET sock; /* Windows specific */ + HANDLE hnd; /* Windows specific */ +} epoll_data_t; + +struct epoll_event { + uint32_t events; /* Epoll events and flags */ + epoll_data_t data; /* User data variable */ +}; + +#ifdef __cplusplus +extern "C" { +#endif + +WEPOLL_EXPORT HANDLE epoll_create(int size); +WEPOLL_EXPORT HANDLE epoll_create1(int flags); + +WEPOLL_EXPORT int epoll_close(HANDLE ephnd); + +WEPOLL_EXPORT int epoll_ctl(HANDLE ephnd, + int op, + SOCKET sock, + struct epoll_event* event); + +WEPOLL_EXPORT int epoll_wait(HANDLE ephnd, + struct epoll_event* events, + int maxevents, + int timeout); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#include + +#include + +#define WEPOLL_INTERNAL static +#define WEPOLL_INTERNAL_EXTERN static + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonportable-system-include-path" +#pragma clang diagnostic ignored "-Wreserved-id-macro" +#elif defined(_MSC_VER) +#pragma warning(push, 1) +#endif + +#undef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN + +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x0600 + +#include +#include +#include + +#if defined(__clang__) +#pragma clang diagnostic pop +#elif defined(_MSC_VER) +#pragma warning(pop) +#endif + +WEPOLL_INTERNAL int nt_global_init(void); + +typedef LONG NTSTATUS; +typedef NTSTATUS* PNTSTATUS; + +#ifndef NT_SUCCESS +#define NT_SUCCESS(status) (((NTSTATUS)(status)) >= 0) +#endif + +#ifndef STATUS_SUCCESS +#define STATUS_SUCCESS ((NTSTATUS) 0x00000000L) +#endif + +#ifndef STATUS_PENDING +#define STATUS_PENDING ((NTSTATUS) 0x00000103L) +#endif + +#ifndef STATUS_CANCELLED +#define STATUS_CANCELLED ((NTSTATUS) 0xC0000120L) +#endif + +#ifndef STATUS_NOT_FOUND +#define STATUS_NOT_FOUND ((NTSTATUS) 0xC0000225L) +#endif + +typedef struct _IO_STATUS_BLOCK { + NTSTATUS Status; + ULONG_PTR Information; +} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK; + +typedef VOID(NTAPI* PIO_APC_ROUTINE)(PVOID ApcContext, + PIO_STATUS_BLOCK IoStatusBlock, + ULONG Reserved); + +typedef struct _UNICODE_STRING { + USHORT Length; + USHORT MaximumLength; + PWSTR Buffer; +} UNICODE_STRING, *PUNICODE_STRING; + +#define RTL_CONSTANT_STRING(s) \ + { sizeof(s) - sizeof((s)[0]), sizeof(s), s } + +typedef struct _OBJECT_ATTRIBUTES { + ULONG Length; + HANDLE RootDirectory; + PUNICODE_STRING ObjectName; + ULONG Attributes; + PVOID SecurityDescriptor; + PVOID SecurityQualityOfService; +} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES; + +#define RTL_CONSTANT_OBJECT_ATTRIBUTES(ObjectName, Attributes) \ + { sizeof(OBJECT_ATTRIBUTES), NULL, ObjectName, Attributes, NULL, NULL } + +#ifndef FILE_OPEN +#define FILE_OPEN 0x00000001UL +#endif + +#define KEYEDEVENT_WAIT 0x00000001UL +#define KEYEDEVENT_WAKE 0x00000002UL +#define KEYEDEVENT_ALL_ACCESS \ + (STANDARD_RIGHTS_REQUIRED | KEYEDEVENT_WAIT | KEYEDEVENT_WAKE) + +#define NT_NTDLL_IMPORT_LIST(X) \ + X(NTSTATUS, \ + NTAPI, \ + NtCancelIoFileEx, \ + (HANDLE FileHandle, \ + PIO_STATUS_BLOCK IoRequestToCancel, \ + PIO_STATUS_BLOCK IoStatusBlock)) \ + \ + X(NTSTATUS, \ + NTAPI, \ + NtCreateFile, \ + (PHANDLE FileHandle, \ + ACCESS_MASK DesiredAccess, \ + POBJECT_ATTRIBUTES ObjectAttributes, \ + PIO_STATUS_BLOCK IoStatusBlock, \ + PLARGE_INTEGER AllocationSize, \ + ULONG FileAttributes, \ + ULONG ShareAccess, \ + ULONG CreateDisposition, \ + ULONG CreateOptions, \ + PVOID EaBuffer, \ + ULONG EaLength)) \ + \ + X(NTSTATUS, \ + NTAPI, \ + NtCreateKeyedEvent, \ + (PHANDLE KeyedEventHandle, \ + ACCESS_MASK DesiredAccess, \ + POBJECT_ATTRIBUTES ObjectAttributes, \ + ULONG Flags)) \ + \ + X(NTSTATUS, \ + NTAPI, \ + NtDeviceIoControlFile, \ + (HANDLE FileHandle, \ + HANDLE Event, \ + PIO_APC_ROUTINE ApcRoutine, \ + PVOID ApcContext, \ + PIO_STATUS_BLOCK IoStatusBlock, \ + ULONG IoControlCode, \ + PVOID InputBuffer, \ + ULONG InputBufferLength, \ + PVOID OutputBuffer, \ + ULONG OutputBufferLength)) \ + \ + X(NTSTATUS, \ + NTAPI, \ + NtReleaseKeyedEvent, \ + (HANDLE KeyedEventHandle, \ + PVOID KeyValue, \ + BOOLEAN Alertable, \ + PLARGE_INTEGER Timeout)) \ + \ + X(NTSTATUS, \ + NTAPI, \ + NtWaitForKeyedEvent, \ + (HANDLE KeyedEventHandle, \ + PVOID KeyValue, \ + BOOLEAN Alertable, \ + PLARGE_INTEGER Timeout)) \ + \ + X(ULONG, WINAPI, RtlNtStatusToDosError, (NTSTATUS Status)) + +#define X(return_type, attributes, name, parameters) \ + WEPOLL_INTERNAL_EXTERN return_type(attributes* name) parameters; +NT_NTDLL_IMPORT_LIST(X) +#undef X + +#define AFD_POLL_RECEIVE 0x0001 +#define AFD_POLL_RECEIVE_EXPEDITED 0x0002 +#define AFD_POLL_SEND 0x0004 +#define AFD_POLL_DISCONNECT 0x0008 +#define AFD_POLL_ABORT 0x0010 +#define AFD_POLL_LOCAL_CLOSE 0x0020 +#define AFD_POLL_ACCEPT 0x0080 +#define AFD_POLL_CONNECT_FAIL 0x0100 + +typedef struct _AFD_POLL_HANDLE_INFO { + HANDLE Handle; + ULONG Events; + NTSTATUS Status; +} AFD_POLL_HANDLE_INFO, *PAFD_POLL_HANDLE_INFO; + +typedef struct _AFD_POLL_INFO { + LARGE_INTEGER Timeout; + ULONG NumberOfHandles; + ULONG Exclusive; + AFD_POLL_HANDLE_INFO Handles[1]; +} AFD_POLL_INFO, *PAFD_POLL_INFO; + +WEPOLL_INTERNAL int afd_create_device_handle(HANDLE iocp_handle, + HANDLE* afd_device_handle_out); + +WEPOLL_INTERNAL int afd_poll(HANDLE afd_device_handle, + AFD_POLL_INFO* poll_info, + IO_STATUS_BLOCK* io_status_block); +WEPOLL_INTERNAL int afd_cancel_poll(HANDLE afd_device_handle, + IO_STATUS_BLOCK* io_status_block); + +#define return_map_error(value) \ + do { \ + err_map_win_error(); \ + return (value); \ + } while (0) + +#define return_set_error(value, error) \ + do { \ + err_set_win_error(error); \ + return (value); \ + } while (0) + +WEPOLL_INTERNAL void err_map_win_error(void); +WEPOLL_INTERNAL void err_set_win_error(DWORD error); +WEPOLL_INTERNAL int err_check_handle(HANDLE handle); + +#define IOCTL_AFD_POLL 0x00012024 + +static UNICODE_STRING afd__device_name = + RTL_CONSTANT_STRING(L"\\Device\\Afd\\Wepoll"); + +static OBJECT_ATTRIBUTES afd__device_attributes = + RTL_CONSTANT_OBJECT_ATTRIBUTES(&afd__device_name, 0); + +int afd_create_device_handle(HANDLE iocp_handle, + HANDLE* afd_device_handle_out) { + HANDLE afd_device_handle; + IO_STATUS_BLOCK iosb; + NTSTATUS status; + + /* By opening \Device\Afd without specifying any extended attributes, we'll + * get a handle that lets us talk to the AFD driver, but that doesn't have an + * associated endpoint (so it's not a socket). */ + status = NtCreateFile(&afd_device_handle, + SYNCHRONIZE, + &afd__device_attributes, + &iosb, + NULL, + 0, + FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_OPEN, + 0, + NULL, + 0); + if (status != STATUS_SUCCESS) + return_set_error(-1, RtlNtStatusToDosError(status)); + + if (CreateIoCompletionPort(afd_device_handle, iocp_handle, 0, 0) == NULL) + goto error; + + if (!SetFileCompletionNotificationModes(afd_device_handle, + FILE_SKIP_SET_EVENT_ON_HANDLE)) + goto error; + + *afd_device_handle_out = afd_device_handle; + return 0; + +error: + CloseHandle(afd_device_handle); + return_map_error(-1); +} + +int afd_poll(HANDLE afd_device_handle, + AFD_POLL_INFO* poll_info, + IO_STATUS_BLOCK* io_status_block) { + NTSTATUS status; + + /* Blocking operation is not supported. */ + assert(io_status_block != NULL); + + io_status_block->Status = STATUS_PENDING; + status = NtDeviceIoControlFile(afd_device_handle, + NULL, + NULL, + io_status_block, + io_status_block, + IOCTL_AFD_POLL, + poll_info, + sizeof *poll_info, + poll_info, + sizeof *poll_info); + + if (status == STATUS_SUCCESS) + return 0; + else if (status == STATUS_PENDING) + return_set_error(-1, ERROR_IO_PENDING); + else + return_set_error(-1, RtlNtStatusToDosError(status)); +} + +int afd_cancel_poll(HANDLE afd_device_handle, + IO_STATUS_BLOCK* io_status_block) { + NTSTATUS cancel_status; + IO_STATUS_BLOCK cancel_iosb; + + /* If the poll operation has already completed or has been cancelled earlier, + * there's nothing left for us to do. */ + if (io_status_block->Status != STATUS_PENDING) + return 0; + + cancel_status = + NtCancelIoFileEx(afd_device_handle, io_status_block, &cancel_iosb); + + /* NtCancelIoFileEx() may return STATUS_NOT_FOUND if the operation completed + * just before calling NtCancelIoFileEx(). This is not an error. */ + if (cancel_status == STATUS_SUCCESS || cancel_status == STATUS_NOT_FOUND) + return 0; + else + return_set_error(-1, RtlNtStatusToDosError(cancel_status)); +} + +WEPOLL_INTERNAL int epoll_global_init(void); + +WEPOLL_INTERNAL int init(void); + +typedef struct port_state port_state_t; +typedef struct queue queue_t; +typedef struct sock_state sock_state_t; +typedef struct ts_tree_node ts_tree_node_t; + +WEPOLL_INTERNAL port_state_t* port_new(HANDLE* iocp_handle_out); +WEPOLL_INTERNAL int port_close(port_state_t* port_state); +WEPOLL_INTERNAL int port_delete(port_state_t* port_state); + +WEPOLL_INTERNAL int port_wait(port_state_t* port_state, + struct epoll_event* events, + int maxevents, + int timeout); + +WEPOLL_INTERNAL int port_ctl(port_state_t* port_state, + int op, + SOCKET sock, + struct epoll_event* ev); + +WEPOLL_INTERNAL int port_register_socket(port_state_t* port_state, + sock_state_t* sock_state, + SOCKET socket); +WEPOLL_INTERNAL void port_unregister_socket(port_state_t* port_state, + sock_state_t* sock_state); +WEPOLL_INTERNAL sock_state_t* port_find_socket(port_state_t* port_state, + SOCKET socket); + +WEPOLL_INTERNAL void port_request_socket_update(port_state_t* port_state, + sock_state_t* sock_state); +WEPOLL_INTERNAL void port_cancel_socket_update(port_state_t* port_state, + sock_state_t* sock_state); + +WEPOLL_INTERNAL void port_add_deleted_socket(port_state_t* port_state, + sock_state_t* sock_state); +WEPOLL_INTERNAL void port_remove_deleted_socket(port_state_t* port_state, + sock_state_t* sock_state); + +WEPOLL_INTERNAL HANDLE port_get_iocp_handle(port_state_t* port_state); +WEPOLL_INTERNAL queue_t* port_get_poll_group_queue(port_state_t* port_state); + +WEPOLL_INTERNAL port_state_t* port_state_from_handle_tree_node( + ts_tree_node_t* tree_node); +WEPOLL_INTERNAL ts_tree_node_t* port_state_to_handle_tree_node( + port_state_t* port_state); + +/* The reflock is a special kind of lock that normally prevents a chunk of + * memory from being freed, but does allow the chunk of memory to eventually be + * released in a coordinated fashion. + * + * Under normal operation, threads increase and decrease the reference count, + * which are wait-free operations. + * + * Exactly once during the reflock's lifecycle, a thread holding a reference to + * the lock may "destroy" the lock; this operation blocks until all other + * threads holding a reference to the lock have dereferenced it. After + * "destroy" returns, the calling thread may assume that no other threads have + * a reference to the lock. + * + * Attemmpting to lock or destroy a lock after reflock_unref_and_destroy() has + * been called is invalid and results in undefined behavior. Therefore the user + * should use another lock to guarantee that this can't happen. + */ + +typedef struct reflock { + volatile long state; /* 32-bit Interlocked APIs operate on `long` values. */ +} reflock_t; + +WEPOLL_INTERNAL int reflock_global_init(void); + +WEPOLL_INTERNAL void reflock_init(reflock_t* reflock); +WEPOLL_INTERNAL void reflock_ref(reflock_t* reflock); +WEPOLL_INTERNAL void reflock_unref(reflock_t* reflock); +WEPOLL_INTERNAL void reflock_unref_and_destroy(reflock_t* reflock); + +#include + +/* N.b.: the tree functions do not set errno or LastError when they fail. Each + * of the API functions has at most one failure mode. It is up to the caller to + * set an appropriate error code when necessary. */ + +typedef struct tree tree_t; +typedef struct tree_node tree_node_t; + +typedef struct tree { + tree_node_t* root; +} tree_t; + +typedef struct tree_node { + tree_node_t* left; + tree_node_t* right; + tree_node_t* parent; + uintptr_t key; + bool red; +} tree_node_t; + +WEPOLL_INTERNAL void tree_init(tree_t* tree); +WEPOLL_INTERNAL void tree_node_init(tree_node_t* node); + +WEPOLL_INTERNAL int tree_add(tree_t* tree, tree_node_t* node, uintptr_t key); +WEPOLL_INTERNAL void tree_del(tree_t* tree, tree_node_t* node); + +WEPOLL_INTERNAL tree_node_t* tree_find(const tree_t* tree, uintptr_t key); +WEPOLL_INTERNAL tree_node_t* tree_root(const tree_t* tree); + +typedef struct ts_tree { + tree_t tree; + SRWLOCK lock; +} ts_tree_t; + +typedef struct ts_tree_node { + tree_node_t tree_node; + reflock_t reflock; +} ts_tree_node_t; + +WEPOLL_INTERNAL void ts_tree_init(ts_tree_t* rtl); +WEPOLL_INTERNAL void ts_tree_node_init(ts_tree_node_t* node); + +WEPOLL_INTERNAL int ts_tree_add(ts_tree_t* ts_tree, + ts_tree_node_t* node, + uintptr_t key); + +WEPOLL_INTERNAL ts_tree_node_t* ts_tree_del_and_ref(ts_tree_t* ts_tree, + uintptr_t key); +WEPOLL_INTERNAL ts_tree_node_t* ts_tree_find_and_ref(ts_tree_t* ts_tree, + uintptr_t key); + +WEPOLL_INTERNAL void ts_tree_node_unref(ts_tree_node_t* node); +WEPOLL_INTERNAL void ts_tree_node_unref_and_destroy(ts_tree_node_t* node); + +static ts_tree_t epoll__handle_tree; + +int epoll_global_init(void) { + ts_tree_init(&epoll__handle_tree); + return 0; +} + +static HANDLE epoll__create(void) { + port_state_t* port_state; + HANDLE ephnd; + ts_tree_node_t* tree_node; + + if (init() < 0) + return NULL; + + port_state = port_new(&ephnd); + if (port_state == NULL) + return NULL; + + tree_node = port_state_to_handle_tree_node(port_state); + if (ts_tree_add(&epoll__handle_tree, tree_node, (uintptr_t) ephnd) < 0) { + /* This should never happen. */ + port_delete(port_state); + return_set_error(NULL, ERROR_ALREADY_EXISTS); + } + + return ephnd; +} + +HANDLE epoll_create(int size) { + if (size <= 0) + return_set_error(NULL, ERROR_INVALID_PARAMETER); + + return epoll__create(); +} + +HANDLE epoll_create1(int flags) { + if (flags != 0) + return_set_error(NULL, ERROR_INVALID_PARAMETER); + + return epoll__create(); +} + +int epoll_close(HANDLE ephnd) { + ts_tree_node_t* tree_node; + port_state_t* port_state; + + if (init() < 0) + return -1; + + tree_node = ts_tree_del_and_ref(&epoll__handle_tree, (uintptr_t) ephnd); + if (tree_node == NULL) { + err_set_win_error(ERROR_INVALID_PARAMETER); + goto err; + } + + port_state = port_state_from_handle_tree_node(tree_node); + port_close(port_state); + + ts_tree_node_unref_and_destroy(tree_node); + + return port_delete(port_state); + +err: + err_check_handle(ephnd); + return -1; +} + +int epoll_ctl(HANDLE ephnd, int op, SOCKET sock, struct epoll_event* ev) { + ts_tree_node_t* tree_node; + port_state_t* port_state; + int r; + + if (init() < 0) + return -1; + + tree_node = ts_tree_find_and_ref(&epoll__handle_tree, (uintptr_t) ephnd); + if (tree_node == NULL) { + err_set_win_error(ERROR_INVALID_PARAMETER); + goto err; + } + + port_state = port_state_from_handle_tree_node(tree_node); + r = port_ctl(port_state, op, sock, ev); + + ts_tree_node_unref(tree_node); + + if (r < 0) + goto err; + + return 0; + +err: + /* On Linux, in the case of epoll_ctl(), EBADF takes priority over other + * errors. Wepoll mimics this behavior. */ + err_check_handle(ephnd); + err_check_handle((HANDLE) sock); + return -1; +} + +int epoll_wait(HANDLE ephnd, + struct epoll_event* events, + int maxevents, + int timeout) { + ts_tree_node_t* tree_node; + port_state_t* port_state; + int num_events; + + if (maxevents <= 0) + return_set_error(-1, ERROR_INVALID_PARAMETER); + + if (init() < 0) + return -1; + + tree_node = ts_tree_find_and_ref(&epoll__handle_tree, (uintptr_t) ephnd); + if (tree_node == NULL) { + err_set_win_error(ERROR_INVALID_PARAMETER); + goto err; + } + + port_state = port_state_from_handle_tree_node(tree_node); + num_events = port_wait(port_state, events, maxevents, timeout); + + ts_tree_node_unref(tree_node); + + if (num_events < 0) + goto err; + + return num_events; + +err: + err_check_handle(ephnd); + return -1; +} + +#include + +#define ERR__ERRNO_MAPPINGS(X) \ + X(ERROR_ACCESS_DENIED, EACCES) \ + X(ERROR_ALREADY_EXISTS, EEXIST) \ + X(ERROR_BAD_COMMAND, EACCES) \ + X(ERROR_BAD_EXE_FORMAT, ENOEXEC) \ + X(ERROR_BAD_LENGTH, EACCES) \ + X(ERROR_BAD_NETPATH, ENOENT) \ + X(ERROR_BAD_NET_NAME, ENOENT) \ + X(ERROR_BAD_NET_RESP, ENETDOWN) \ + X(ERROR_BAD_PATHNAME, ENOENT) \ + X(ERROR_BROKEN_PIPE, EPIPE) \ + X(ERROR_CANNOT_MAKE, EACCES) \ + X(ERROR_COMMITMENT_LIMIT, ENOMEM) \ + X(ERROR_CONNECTION_ABORTED, ECONNABORTED) \ + X(ERROR_CONNECTION_ACTIVE, EISCONN) \ + X(ERROR_CONNECTION_REFUSED, ECONNREFUSED) \ + X(ERROR_CRC, EACCES) \ + X(ERROR_DIR_NOT_EMPTY, ENOTEMPTY) \ + X(ERROR_DISK_FULL, ENOSPC) \ + X(ERROR_DUP_NAME, EADDRINUSE) \ + X(ERROR_FILENAME_EXCED_RANGE, ENOENT) \ + X(ERROR_FILE_NOT_FOUND, ENOENT) \ + X(ERROR_GEN_FAILURE, EACCES) \ + X(ERROR_GRACEFUL_DISCONNECT, EPIPE) \ + X(ERROR_HOST_DOWN, EHOSTUNREACH) \ + X(ERROR_HOST_UNREACHABLE, EHOSTUNREACH) \ + X(ERROR_INSUFFICIENT_BUFFER, EFAULT) \ + X(ERROR_INVALID_ADDRESS, EADDRNOTAVAIL) \ + X(ERROR_INVALID_FUNCTION, EINVAL) \ + X(ERROR_INVALID_HANDLE, EBADF) \ + X(ERROR_INVALID_NETNAME, EADDRNOTAVAIL) \ + X(ERROR_INVALID_PARAMETER, EINVAL) \ + X(ERROR_INVALID_USER_BUFFER, EMSGSIZE) \ + X(ERROR_IO_PENDING, EINPROGRESS) \ + X(ERROR_LOCK_VIOLATION, EACCES) \ + X(ERROR_MORE_DATA, EMSGSIZE) \ + X(ERROR_NETNAME_DELETED, ECONNABORTED) \ + X(ERROR_NETWORK_ACCESS_DENIED, EACCES) \ + X(ERROR_NETWORK_BUSY, ENETDOWN) \ + X(ERROR_NETWORK_UNREACHABLE, ENETUNREACH) \ + X(ERROR_NOACCESS, EFAULT) \ + X(ERROR_NONPAGED_SYSTEM_RESOURCES, ENOMEM) \ + X(ERROR_NOT_ENOUGH_MEMORY, ENOMEM) \ + X(ERROR_NOT_ENOUGH_QUOTA, ENOMEM) \ + X(ERROR_NOT_FOUND, ENOENT) \ + X(ERROR_NOT_LOCKED, EACCES) \ + X(ERROR_NOT_READY, EACCES) \ + X(ERROR_NOT_SAME_DEVICE, EXDEV) \ + X(ERROR_NOT_SUPPORTED, ENOTSUP) \ + X(ERROR_NO_MORE_FILES, ENOENT) \ + X(ERROR_NO_SYSTEM_RESOURCES, ENOMEM) \ + X(ERROR_OPERATION_ABORTED, EINTR) \ + X(ERROR_OUT_OF_PAPER, EACCES) \ + X(ERROR_PAGED_SYSTEM_RESOURCES, ENOMEM) \ + X(ERROR_PAGEFILE_QUOTA, ENOMEM) \ + X(ERROR_PATH_NOT_FOUND, ENOENT) \ + X(ERROR_PIPE_NOT_CONNECTED, EPIPE) \ + X(ERROR_PORT_UNREACHABLE, ECONNRESET) \ + X(ERROR_PROTOCOL_UNREACHABLE, ENETUNREACH) \ + X(ERROR_REM_NOT_LIST, ECONNREFUSED) \ + X(ERROR_REQUEST_ABORTED, EINTR) \ + X(ERROR_REQ_NOT_ACCEP, EWOULDBLOCK) \ + X(ERROR_SECTOR_NOT_FOUND, EACCES) \ + X(ERROR_SEM_TIMEOUT, ETIMEDOUT) \ + X(ERROR_SHARING_VIOLATION, EACCES) \ + X(ERROR_TOO_MANY_NAMES, ENOMEM) \ + X(ERROR_TOO_MANY_OPEN_FILES, EMFILE) \ + X(ERROR_UNEXP_NET_ERR, ECONNABORTED) \ + X(ERROR_WAIT_NO_CHILDREN, ECHILD) \ + X(ERROR_WORKING_SET_QUOTA, ENOMEM) \ + X(ERROR_WRITE_PROTECT, EACCES) \ + X(ERROR_WRONG_DISK, EACCES) \ + X(WSAEACCES, EACCES) \ + X(WSAEADDRINUSE, EADDRINUSE) \ + X(WSAEADDRNOTAVAIL, EADDRNOTAVAIL) \ + X(WSAEAFNOSUPPORT, EAFNOSUPPORT) \ + X(WSAECONNABORTED, ECONNABORTED) \ + X(WSAECONNREFUSED, ECONNREFUSED) \ + X(WSAECONNRESET, ECONNRESET) \ + X(WSAEDISCON, EPIPE) \ + X(WSAEFAULT, EFAULT) \ + X(WSAEHOSTDOWN, EHOSTUNREACH) \ + X(WSAEHOSTUNREACH, EHOSTUNREACH) \ + X(WSAEINPROGRESS, EBUSY) \ + X(WSAEINTR, EINTR) \ + X(WSAEINVAL, EINVAL) \ + X(WSAEISCONN, EISCONN) \ + X(WSAEMSGSIZE, EMSGSIZE) \ + X(WSAENETDOWN, ENETDOWN) \ + X(WSAENETRESET, EHOSTUNREACH) \ + X(WSAENETUNREACH, ENETUNREACH) \ + X(WSAENOBUFS, ENOMEM) \ + X(WSAENOTCONN, ENOTCONN) \ + X(WSAENOTSOCK, ENOTSOCK) \ + X(WSAEOPNOTSUPP, EOPNOTSUPP) \ + X(WSAEPROCLIM, ENOMEM) \ + X(WSAESHUTDOWN, EPIPE) \ + X(WSAETIMEDOUT, ETIMEDOUT) \ + X(WSAEWOULDBLOCK, EWOULDBLOCK) \ + X(WSANOTINITIALISED, ENETDOWN) \ + X(WSASYSNOTREADY, ENETDOWN) \ + X(WSAVERNOTSUPPORTED, ENOSYS) + +static errno_t err__map_win_error_to_errno(DWORD error) { + switch (error) { +#define X(error_sym, errno_sym) \ + case error_sym: \ + return errno_sym; + ERR__ERRNO_MAPPINGS(X) +#undef X + } + return EINVAL; +} + +void err_map_win_error(void) { + errno = err__map_win_error_to_errno(GetLastError()); +} + +void err_set_win_error(DWORD error) { + SetLastError(error); + errno = err__map_win_error_to_errno(error); +} + +int err_check_handle(HANDLE handle) { + DWORD flags; + + /* GetHandleInformation() succeeds when passed INVALID_HANDLE_VALUE, so check + * for this condition explicitly. */ + if (handle == INVALID_HANDLE_VALUE) + return_set_error(-1, ERROR_INVALID_HANDLE); + + if (!GetHandleInformation(handle, &flags)) + return_map_error(-1); + + return 0; +} + +#include + +#define array_count(a) (sizeof(a) / (sizeof((a)[0]))) + +#define container_of(ptr, type, member) \ + ((type*) ((uintptr_t) (ptr) - offsetof(type, member))) + +#define unused_var(v) ((void) (v)) + +/* Polyfill `inline` for older versions of msvc (up to Visual Studio 2013) */ +#if defined(_MSC_VER) && _MSC_VER < 1900 +#define inline __inline +#endif + +WEPOLL_INTERNAL int ws_global_init(void); +WEPOLL_INTERNAL SOCKET ws_get_base_socket(SOCKET socket); + +static bool init__done = false; +static INIT_ONCE init__once = INIT_ONCE_STATIC_INIT; + +static BOOL CALLBACK init__once_callback(INIT_ONCE* once, + void* parameter, + void** context) { + unused_var(once); + unused_var(parameter); + unused_var(context); + + /* N.b. that initialization order matters here. */ + if (ws_global_init() < 0 || nt_global_init() < 0 || + reflock_global_init() < 0 || epoll_global_init() < 0) + return FALSE; + + init__done = true; + return TRUE; +} + +int init(void) { + if (!init__done && + !InitOnceExecuteOnce(&init__once, init__once_callback, NULL, NULL)) + /* `InitOnceExecuteOnce()` itself is infallible, and it doesn't set any + * error code when the once-callback returns FALSE. We return -1 here to + * indicate that global initialization failed; the failing init function is + * resposible for setting `errno` and calling `SetLastError()`. */ + return -1; + + return 0; +} + +/* Set up a workaround for the following problem: + * FARPROC addr = GetProcAddress(...); + * MY_FUNC func = (MY_FUNC) addr; <-- GCC 8 warning/error. + * MY_FUNC func = (MY_FUNC) (void*) addr; <-- MSVC warning/error. + * To compile cleanly with either compiler, do casts with this "bridge" type: + * MY_FUNC func = (MY_FUNC) (nt__fn_ptr_cast_t) addr; */ +#ifdef __GNUC__ +typedef void* nt__fn_ptr_cast_t; +#else +typedef FARPROC nt__fn_ptr_cast_t; +#endif + +#define X(return_type, attributes, name, parameters) \ + WEPOLL_INTERNAL return_type(attributes* name) parameters = NULL; +NT_NTDLL_IMPORT_LIST(X) +#undef X + +int nt_global_init(void) { + HMODULE ntdll; + FARPROC fn_ptr; + + ntdll = GetModuleHandleW(L"ntdll.dll"); + if (ntdll == NULL) + return -1; + +#define X(return_type, attributes, name, parameters) \ + fn_ptr = GetProcAddress(ntdll, #name); \ + if (fn_ptr == NULL) \ + return -1; \ + name = (return_type(attributes*) parameters)(nt__fn_ptr_cast_t) fn_ptr; + NT_NTDLL_IMPORT_LIST(X) +#undef X + + return 0; +} + +#include + +typedef struct poll_group poll_group_t; + +typedef struct queue_node queue_node_t; + +WEPOLL_INTERNAL poll_group_t* poll_group_acquire(port_state_t* port); +WEPOLL_INTERNAL void poll_group_release(poll_group_t* poll_group); + +WEPOLL_INTERNAL void poll_group_delete(poll_group_t* poll_group); + +WEPOLL_INTERNAL poll_group_t* poll_group_from_queue_node( + queue_node_t* queue_node); +WEPOLL_INTERNAL HANDLE + poll_group_get_afd_device_handle(poll_group_t* poll_group); + +typedef struct queue_node { + queue_node_t* prev; + queue_node_t* next; +} queue_node_t; + +typedef struct queue { + queue_node_t head; +} queue_t; + +WEPOLL_INTERNAL void queue_init(queue_t* queue); +WEPOLL_INTERNAL void queue_node_init(queue_node_t* node); + +WEPOLL_INTERNAL queue_node_t* queue_first(const queue_t* queue); +WEPOLL_INTERNAL queue_node_t* queue_last(const queue_t* queue); + +WEPOLL_INTERNAL void queue_prepend(queue_t* queue, queue_node_t* node); +WEPOLL_INTERNAL void queue_append(queue_t* queue, queue_node_t* node); +WEPOLL_INTERNAL void queue_move_to_start(queue_t* queue, queue_node_t* node); +WEPOLL_INTERNAL void queue_move_to_end(queue_t* queue, queue_node_t* node); +WEPOLL_INTERNAL void queue_remove(queue_node_t* node); + +WEPOLL_INTERNAL bool queue_is_empty(const queue_t* queue); +WEPOLL_INTERNAL bool queue_is_enqueued(const queue_node_t* node); + +#define POLL_GROUP__MAX_GROUP_SIZE 32 + +typedef struct poll_group { + port_state_t* port_state; + queue_node_t queue_node; + HANDLE afd_device_handle; + size_t group_size; +} poll_group_t; + +static poll_group_t* poll_group__new(port_state_t* port_state) { + HANDLE iocp_handle = port_get_iocp_handle(port_state); + queue_t* poll_group_queue = port_get_poll_group_queue(port_state); + + poll_group_t* poll_group = malloc(sizeof *poll_group); + if (poll_group == NULL) + return_set_error(NULL, ERROR_NOT_ENOUGH_MEMORY); + + memset(poll_group, 0, sizeof *poll_group); + + queue_node_init(&poll_group->queue_node); + poll_group->port_state = port_state; + + if (afd_create_device_handle(iocp_handle, &poll_group->afd_device_handle) < + 0) { + free(poll_group); + return NULL; + } + + queue_append(poll_group_queue, &poll_group->queue_node); + + return poll_group; +} + +void poll_group_delete(poll_group_t* poll_group) { + assert(poll_group->group_size == 0); + CloseHandle(poll_group->afd_device_handle); + queue_remove(&poll_group->queue_node); + free(poll_group); +} + +poll_group_t* poll_group_from_queue_node(queue_node_t* queue_node) { + return container_of(queue_node, poll_group_t, queue_node); +} + +HANDLE poll_group_get_afd_device_handle(poll_group_t* poll_group) { + return poll_group->afd_device_handle; +} + +poll_group_t* poll_group_acquire(port_state_t* port_state) { + queue_t* poll_group_queue = port_get_poll_group_queue(port_state); + poll_group_t* poll_group = + !queue_is_empty(poll_group_queue) + ? container_of( + queue_last(poll_group_queue), poll_group_t, queue_node) + : NULL; + + if (poll_group == NULL || + poll_group->group_size >= POLL_GROUP__MAX_GROUP_SIZE) + poll_group = poll_group__new(port_state); + if (poll_group == NULL) + return NULL; + + if (++poll_group->group_size == POLL_GROUP__MAX_GROUP_SIZE) + queue_move_to_start(poll_group_queue, &poll_group->queue_node); + + return poll_group; +} + +void poll_group_release(poll_group_t* poll_group) { + port_state_t* port_state = poll_group->port_state; + queue_t* poll_group_queue = port_get_poll_group_queue(port_state); + + poll_group->group_size--; + assert(poll_group->group_size < POLL_GROUP__MAX_GROUP_SIZE); + + queue_move_to_end(poll_group_queue, &poll_group->queue_node); + + /* Poll groups are currently only freed when the epoll port is closed. */ +} + +WEPOLL_INTERNAL sock_state_t* sock_new(port_state_t* port_state, + SOCKET socket); +WEPOLL_INTERNAL void sock_delete(port_state_t* port_state, + sock_state_t* sock_state); +WEPOLL_INTERNAL void sock_force_delete(port_state_t* port_state, + sock_state_t* sock_state); + +WEPOLL_INTERNAL int sock_set_event(port_state_t* port_state, + sock_state_t* sock_state, + const struct epoll_event* ev); + +WEPOLL_INTERNAL int sock_update(port_state_t* port_state, + sock_state_t* sock_state); +WEPOLL_INTERNAL int sock_feed_event(port_state_t* port_state, + IO_STATUS_BLOCK* io_status_block, + struct epoll_event* ev); + +WEPOLL_INTERNAL sock_state_t* sock_state_from_queue_node( + queue_node_t* queue_node); +WEPOLL_INTERNAL queue_node_t* sock_state_to_queue_node( + sock_state_t* sock_state); +WEPOLL_INTERNAL sock_state_t* sock_state_from_tree_node( + tree_node_t* tree_node); +WEPOLL_INTERNAL tree_node_t* sock_state_to_tree_node(sock_state_t* sock_state); + +#define PORT__MAX_ON_STACK_COMPLETIONS 256 + +typedef struct port_state { + HANDLE iocp_handle; + tree_t sock_tree; + queue_t sock_update_queue; + queue_t sock_deleted_queue; + queue_t poll_group_queue; + ts_tree_node_t handle_tree_node; + CRITICAL_SECTION lock; + size_t active_poll_count; +} port_state_t; + +static inline port_state_t* port__alloc(void) { + port_state_t* port_state = malloc(sizeof *port_state); + if (port_state == NULL) + return_set_error(NULL, ERROR_NOT_ENOUGH_MEMORY); + + return port_state; +} + +static inline void port__free(port_state_t* port) { + assert(port != NULL); + free(port); +} + +static inline HANDLE port__create_iocp(void) { + HANDLE iocp_handle = + CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); + if (iocp_handle == NULL) + return_map_error(NULL); + + return iocp_handle; +} + +port_state_t* port_new(HANDLE* iocp_handle_out) { + port_state_t* port_state; + HANDLE iocp_handle; + + port_state = port__alloc(); + if (port_state == NULL) + goto err1; + + iocp_handle = port__create_iocp(); + if (iocp_handle == NULL) + goto err2; + + memset(port_state, 0, sizeof *port_state); + + port_state->iocp_handle = iocp_handle; + tree_init(&port_state->sock_tree); + queue_init(&port_state->sock_update_queue); + queue_init(&port_state->sock_deleted_queue); + queue_init(&port_state->poll_group_queue); + ts_tree_node_init(&port_state->handle_tree_node); + InitializeCriticalSection(&port_state->lock); + + *iocp_handle_out = iocp_handle; + return port_state; + +err2: + port__free(port_state); +err1: + return NULL; +} + +static inline int port__close_iocp(port_state_t* port_state) { + HANDLE iocp_handle = port_state->iocp_handle; + port_state->iocp_handle = NULL; + + if (!CloseHandle(iocp_handle)) + return_map_error(-1); + + return 0; +} + +int port_close(port_state_t* port_state) { + int result; + + EnterCriticalSection(&port_state->lock); + result = port__close_iocp(port_state); + LeaveCriticalSection(&port_state->lock); + + return result; +} + +int port_delete(port_state_t* port_state) { + tree_node_t* tree_node; + queue_node_t* queue_node; + + /* At this point the IOCP port should have been closed. */ + assert(port_state->iocp_handle == NULL); + + while ((tree_node = tree_root(&port_state->sock_tree)) != NULL) { + sock_state_t* sock_state = sock_state_from_tree_node(tree_node); + sock_force_delete(port_state, sock_state); + } + + while ((queue_node = queue_first(&port_state->sock_deleted_queue)) != NULL) { + sock_state_t* sock_state = sock_state_from_queue_node(queue_node); + sock_force_delete(port_state, sock_state); + } + + while ((queue_node = queue_first(&port_state->poll_group_queue)) != NULL) { + poll_group_t* poll_group = poll_group_from_queue_node(queue_node); + poll_group_delete(poll_group); + } + + assert(queue_is_empty(&port_state->sock_update_queue)); + + DeleteCriticalSection(&port_state->lock); + + port__free(port_state); + + return 0; +} + +static int port__update_events(port_state_t* port_state) { + queue_t* sock_update_queue = &port_state->sock_update_queue; + + /* Walk the queue, submitting new poll requests for every socket that needs + * it. */ + while (!queue_is_empty(sock_update_queue)) { + queue_node_t* queue_node = queue_first(sock_update_queue); + sock_state_t* sock_state = sock_state_from_queue_node(queue_node); + + if (sock_update(port_state, sock_state) < 0) + return -1; + + /* sock_update() removes the socket from the update queue. */ + } + + return 0; +} + +static inline void port__update_events_if_polling(port_state_t* port_state) { + if (port_state->active_poll_count > 0) + port__update_events(port_state); +} + +static inline int port__feed_events(port_state_t* port_state, + struct epoll_event* epoll_events, + OVERLAPPED_ENTRY* iocp_events, + DWORD iocp_event_count) { + int epoll_event_count = 0; + DWORD i; + + for (i = 0; i < iocp_event_count; i++) { + IO_STATUS_BLOCK* io_status_block = + (IO_STATUS_BLOCK*) iocp_events[i].lpOverlapped; + struct epoll_event* ev = &epoll_events[epoll_event_count]; + + epoll_event_count += sock_feed_event(port_state, io_status_block, ev); + } + + return epoll_event_count; +} + +static inline int port__poll(port_state_t* port_state, + struct epoll_event* epoll_events, + OVERLAPPED_ENTRY* iocp_events, + DWORD maxevents, + DWORD timeout) { + DWORD completion_count; + + if (port__update_events(port_state) < 0) + return -1; + + port_state->active_poll_count++; + + LeaveCriticalSection(&port_state->lock); + + BOOL r = GetQueuedCompletionStatusEx(port_state->iocp_handle, + iocp_events, + maxevents, + &completion_count, + timeout, + FALSE); + + EnterCriticalSection(&port_state->lock); + + port_state->active_poll_count--; + + if (!r) + return_map_error(-1); + + return port__feed_events( + port_state, epoll_events, iocp_events, completion_count); +} + +int port_wait(port_state_t* port_state, + struct epoll_event* events, + int maxevents, + int timeout) { + OVERLAPPED_ENTRY stack_iocp_events[PORT__MAX_ON_STACK_COMPLETIONS]; + OVERLAPPED_ENTRY* iocp_events; + uint64_t due = 0; + DWORD gqcs_timeout; + int result; + + /* Check whether `maxevents` is in range. */ + if (maxevents <= 0) + return_set_error(-1, ERROR_INVALID_PARAMETER); + + /* Decide whether the IOCP completion list can live on the stack, or allocate + * memory for it on the heap. */ + if ((size_t) maxevents <= array_count(stack_iocp_events)) { + iocp_events = stack_iocp_events; + } else if ((iocp_events = + malloc((size_t) maxevents * sizeof *iocp_events)) == NULL) { + iocp_events = stack_iocp_events; + maxevents = array_count(stack_iocp_events); + } + + /* Compute the timeout for GetQueuedCompletionStatus, and the wait end + * time, if the user specified a timeout other than zero or infinite. */ + if (timeout > 0) { + due = GetTickCount64() + (uint64_t) timeout; + gqcs_timeout = (DWORD) timeout; + } else if (timeout == 0) { + gqcs_timeout = 0; + } else { + gqcs_timeout = INFINITE; + } + + EnterCriticalSection(&port_state->lock); + + /* Dequeue completion packets until either at least one interesting event + * has been discovered, or the timeout is reached. */ + for (;;) { + uint64_t now; + + result = port__poll( + port_state, events, iocp_events, (DWORD) maxevents, gqcs_timeout); + if (result < 0 || result > 0) + break; /* Result, error, or time-out. */ + + if (timeout < 0) + continue; /* When timeout is negative, never time out. */ + + /* Update time. */ + now = GetTickCount64(); + + /* Do not allow the due time to be in the past. */ + if (now >= due) { + SetLastError(WAIT_TIMEOUT); + break; + } + + /* Recompute time-out argument for GetQueuedCompletionStatus. */ + gqcs_timeout = (DWORD)(due - now); + } + + port__update_events_if_polling(port_state); + + LeaveCriticalSection(&port_state->lock); + + if (iocp_events != stack_iocp_events) + free(iocp_events); + + if (result >= 0) + return result; + else if (GetLastError() == WAIT_TIMEOUT) + return 0; + else + return -1; +} + +static inline int port__ctl_add(port_state_t* port_state, + SOCKET sock, + struct epoll_event* ev) { + sock_state_t* sock_state = sock_new(port_state, sock); + if (sock_state == NULL) + return -1; + + if (sock_set_event(port_state, sock_state, ev) < 0) { + sock_delete(port_state, sock_state); + return -1; + } + + port__update_events_if_polling(port_state); + + return 0; +} + +static inline int port__ctl_mod(port_state_t* port_state, + SOCKET sock, + struct epoll_event* ev) { + sock_state_t* sock_state = port_find_socket(port_state, sock); + if (sock_state == NULL) + return -1; + + if (sock_set_event(port_state, sock_state, ev) < 0) + return -1; + + port__update_events_if_polling(port_state); + + return 0; +} + +static inline int port__ctl_del(port_state_t* port_state, SOCKET sock) { + sock_state_t* sock_state = port_find_socket(port_state, sock); + if (sock_state == NULL) + return -1; + + sock_delete(port_state, sock_state); + + return 0; +} + +static inline int port__ctl_op(port_state_t* port_state, + int op, + SOCKET sock, + struct epoll_event* ev) { + switch (op) { + case EPOLL_CTL_ADD: + return port__ctl_add(port_state, sock, ev); + case EPOLL_CTL_MOD: + return port__ctl_mod(port_state, sock, ev); + case EPOLL_CTL_DEL: + return port__ctl_del(port_state, sock); + default: + return_set_error(-1, ERROR_INVALID_PARAMETER); + } +} + +int port_ctl(port_state_t* port_state, + int op, + SOCKET sock, + struct epoll_event* ev) { + int result; + + EnterCriticalSection(&port_state->lock); + result = port__ctl_op(port_state, op, sock, ev); + LeaveCriticalSection(&port_state->lock); + + return result; +} + +int port_register_socket(port_state_t* port_state, + sock_state_t* sock_state, + SOCKET socket) { + if (tree_add(&port_state->sock_tree, + sock_state_to_tree_node(sock_state), + socket) < 0) + return_set_error(-1, ERROR_ALREADY_EXISTS); + return 0; +} + +void port_unregister_socket(port_state_t* port_state, + sock_state_t* sock_state) { + tree_del(&port_state->sock_tree, sock_state_to_tree_node(sock_state)); +} + +sock_state_t* port_find_socket(port_state_t* port_state, SOCKET socket) { + tree_node_t* tree_node = tree_find(&port_state->sock_tree, socket); + if (tree_node == NULL) + return_set_error(NULL, ERROR_NOT_FOUND); + return sock_state_from_tree_node(tree_node); +} + +void port_request_socket_update(port_state_t* port_state, + sock_state_t* sock_state) { + if (queue_is_enqueued(sock_state_to_queue_node(sock_state))) + return; + queue_append(&port_state->sock_update_queue, + sock_state_to_queue_node(sock_state)); +} + +void port_cancel_socket_update(port_state_t* port_state, + sock_state_t* sock_state) { + unused_var(port_state); + if (!queue_is_enqueued(sock_state_to_queue_node(sock_state))) + return; + queue_remove(sock_state_to_queue_node(sock_state)); +} + +void port_add_deleted_socket(port_state_t* port_state, + sock_state_t* sock_state) { + if (queue_is_enqueued(sock_state_to_queue_node(sock_state))) + return; + queue_append(&port_state->sock_deleted_queue, + sock_state_to_queue_node(sock_state)); +} + +void port_remove_deleted_socket(port_state_t* port_state, + sock_state_t* sock_state) { + unused_var(port_state); + if (!queue_is_enqueued(sock_state_to_queue_node(sock_state))) + return; + queue_remove(sock_state_to_queue_node(sock_state)); +} + +HANDLE port_get_iocp_handle(port_state_t* port_state) { + assert(port_state->iocp_handle != NULL); + return port_state->iocp_handle; +} + +queue_t* port_get_poll_group_queue(port_state_t* port_state) { + return &port_state->poll_group_queue; +} + +port_state_t* port_state_from_handle_tree_node(ts_tree_node_t* tree_node) { + return container_of(tree_node, port_state_t, handle_tree_node); +} + +ts_tree_node_t* port_state_to_handle_tree_node(port_state_t* port_state) { + return &port_state->handle_tree_node; +} + +void queue_init(queue_t* queue) { + queue_node_init(&queue->head); +} + +void queue_node_init(queue_node_t* node) { + node->prev = node; + node->next = node; +} + +static inline void queue__detach_node(queue_node_t* node) { + node->prev->next = node->next; + node->next->prev = node->prev; +} + +queue_node_t* queue_first(const queue_t* queue) { + return !queue_is_empty(queue) ? queue->head.next : NULL; +} + +queue_node_t* queue_last(const queue_t* queue) { + return !queue_is_empty(queue) ? queue->head.prev : NULL; +} + +void queue_prepend(queue_t* queue, queue_node_t* node) { + node->next = queue->head.next; + node->prev = &queue->head; + node->next->prev = node; + queue->head.next = node; +} + +void queue_append(queue_t* queue, queue_node_t* node) { + node->next = &queue->head; + node->prev = queue->head.prev; + node->prev->next = node; + queue->head.prev = node; +} + +void queue_move_to_start(queue_t* queue, queue_node_t* node) { + queue__detach_node(node); + queue_prepend(queue, node); +} + +void queue_move_to_end(queue_t* queue, queue_node_t* node) { + queue__detach_node(node); + queue_append(queue, node); +} + +void queue_remove(queue_node_t* node) { + queue__detach_node(node); + queue_node_init(node); +} + +bool queue_is_empty(const queue_t* queue) { + return !queue_is_enqueued(&queue->head); +} + +bool queue_is_enqueued(const queue_node_t* node) { + return node->prev != node; +} + +#define REFLOCK__REF ((long) 0x00000001UL) +#define REFLOCK__REF_MASK ((long) 0x0fffffffUL) +#define REFLOCK__DESTROY ((long) 0x10000000UL) +#define REFLOCK__DESTROY_MASK ((long) 0xf0000000UL) +#define REFLOCK__POISON ((long) 0x300dead0UL) + +static HANDLE reflock__keyed_event = NULL; + +int reflock_global_init(void) { + NTSTATUS status = NtCreateKeyedEvent( + &reflock__keyed_event, KEYEDEVENT_ALL_ACCESS, NULL, 0); + if (status != STATUS_SUCCESS) + return_set_error(-1, RtlNtStatusToDosError(status)); + return 0; +} + +void reflock_init(reflock_t* reflock) { + reflock->state = 0; +} + +static void reflock__signal_event(void* address) { + NTSTATUS status = + NtReleaseKeyedEvent(reflock__keyed_event, address, FALSE, NULL); + if (status != STATUS_SUCCESS) + abort(); +} + +static void reflock__await_event(void* address) { + NTSTATUS status = + NtWaitForKeyedEvent(reflock__keyed_event, address, FALSE, NULL); + if (status != STATUS_SUCCESS) + abort(); +} + +void reflock_ref(reflock_t* reflock) { + long state = InterlockedAdd(&reflock->state, REFLOCK__REF); + + /* Verify that the counter didn't overflow and the lock isn't destroyed. */ + assert((state & REFLOCK__DESTROY_MASK) == 0); + unused_var(state); +} + +void reflock_unref(reflock_t* reflock) { + long state = InterlockedAdd(&reflock->state, -REFLOCK__REF); + + /* Verify that the lock was referenced and not already destroyed. */ + assert((state & REFLOCK__DESTROY_MASK & ~REFLOCK__DESTROY) == 0); + + if (state == REFLOCK__DESTROY) + reflock__signal_event(reflock); +} + +void reflock_unref_and_destroy(reflock_t* reflock) { + long state = + InterlockedAdd(&reflock->state, REFLOCK__DESTROY - REFLOCK__REF); + long ref_count = state & REFLOCK__REF_MASK; + + /* Verify that the lock was referenced and not already destroyed. */ + assert((state & REFLOCK__DESTROY_MASK) == REFLOCK__DESTROY); + + if (ref_count != 0) + reflock__await_event(reflock); + + state = InterlockedExchange(&reflock->state, REFLOCK__POISON); + assert(state == REFLOCK__DESTROY); +} + +#define SOCK__KNOWN_EPOLL_EVENTS \ + (EPOLLIN | EPOLLPRI | EPOLLOUT | EPOLLERR | EPOLLHUP | EPOLLRDNORM | \ + EPOLLRDBAND | EPOLLWRNORM | EPOLLWRBAND | EPOLLMSG | EPOLLRDHUP) + +typedef enum sock__poll_status { + SOCK__POLL_IDLE = 0, + SOCK__POLL_PENDING, + SOCK__POLL_CANCELLED +} sock__poll_status_t; + +typedef struct sock_state { + IO_STATUS_BLOCK io_status_block; + AFD_POLL_INFO poll_info; + queue_node_t queue_node; + tree_node_t tree_node; + poll_group_t* poll_group; + SOCKET base_socket; + epoll_data_t user_data; + uint32_t user_events; + uint32_t pending_events; + sock__poll_status_t poll_status; + bool delete_pending; +} sock_state_t; + +static inline sock_state_t* sock__alloc(void) { + sock_state_t* sock_state = malloc(sizeof *sock_state); + if (sock_state == NULL) + return_set_error(NULL, ERROR_NOT_ENOUGH_MEMORY); + return sock_state; +} + +static inline void sock__free(sock_state_t* sock_state) { + assert(sock_state != NULL); + free(sock_state); +} + +static inline int sock__cancel_poll(sock_state_t* sock_state) { + assert(sock_state->poll_status == SOCK__POLL_PENDING); + + if (afd_cancel_poll(poll_group_get_afd_device_handle(sock_state->poll_group), + &sock_state->io_status_block) < 0) + return -1; + + sock_state->poll_status = SOCK__POLL_CANCELLED; + sock_state->pending_events = 0; + return 0; +} + +sock_state_t* sock_new(port_state_t* port_state, SOCKET socket) { + SOCKET base_socket; + poll_group_t* poll_group; + sock_state_t* sock_state; + + if (socket == 0 || socket == INVALID_SOCKET) + return_set_error(NULL, ERROR_INVALID_HANDLE); + + base_socket = ws_get_base_socket(socket); + if (base_socket == INVALID_SOCKET) + return NULL; + + poll_group = poll_group_acquire(port_state); + if (poll_group == NULL) + return NULL; + + sock_state = sock__alloc(); + if (sock_state == NULL) + goto err1; + + memset(sock_state, 0, sizeof *sock_state); + + sock_state->base_socket = base_socket; + sock_state->poll_group = poll_group; + + tree_node_init(&sock_state->tree_node); + queue_node_init(&sock_state->queue_node); + + if (port_register_socket(port_state, sock_state, socket) < 0) + goto err2; + + return sock_state; + +err2: + sock__free(sock_state); +err1: + poll_group_release(poll_group); + + return NULL; +} + +static int sock__delete(port_state_t* port_state, + sock_state_t* sock_state, + bool force) { + if (!sock_state->delete_pending) { + if (sock_state->poll_status == SOCK__POLL_PENDING) + sock__cancel_poll(sock_state); + + port_cancel_socket_update(port_state, sock_state); + port_unregister_socket(port_state, sock_state); + + sock_state->delete_pending = true; + } + + /* If the poll request still needs to complete, the sock_state object can't + * be free()d yet. `sock_feed_event()` or `port_close()` will take care + * of this later. */ + if (force || sock_state->poll_status == SOCK__POLL_IDLE) { + /* Free the sock_state now. */ + port_remove_deleted_socket(port_state, sock_state); + poll_group_release(sock_state->poll_group); + sock__free(sock_state); + } else { + /* Free the socket later. */ + port_add_deleted_socket(port_state, sock_state); + } + + return 0; +} + +void sock_delete(port_state_t* port_state, sock_state_t* sock_state) { + sock__delete(port_state, sock_state, false); +} + +void sock_force_delete(port_state_t* port_state, sock_state_t* sock_state) { + sock__delete(port_state, sock_state, true); +} + +int sock_set_event(port_state_t* port_state, + sock_state_t* sock_state, + const struct epoll_event* ev) { + /* EPOLLERR and EPOLLHUP are always reported, even when not requested by the + * caller. However they are disabled after a event has been reported for a + * socket for which the EPOLLONESHOT flag was set. */ + uint32_t events = ev->events | EPOLLERR | EPOLLHUP; + + sock_state->user_events = events; + sock_state->user_data = ev->data; + + if ((events & SOCK__KNOWN_EPOLL_EVENTS & ~sock_state->pending_events) != 0) + port_request_socket_update(port_state, sock_state); + + return 0; +} + +static inline DWORD sock__epoll_events_to_afd_events(uint32_t epoll_events) { + /* Always monitor for AFD_POLL_LOCAL_CLOSE, which is triggered when the + * socket is closed with closesocket() or CloseHandle(). */ + DWORD afd_events = AFD_POLL_LOCAL_CLOSE; + + if (epoll_events & (EPOLLIN | EPOLLRDNORM)) + afd_events |= AFD_POLL_RECEIVE | AFD_POLL_ACCEPT; + if (epoll_events & (EPOLLPRI | EPOLLRDBAND)) + afd_events |= AFD_POLL_RECEIVE_EXPEDITED; + if (epoll_events & (EPOLLOUT | EPOLLWRNORM | EPOLLWRBAND)) + afd_events |= AFD_POLL_SEND; + if (epoll_events & (EPOLLIN | EPOLLRDNORM | EPOLLRDHUP)) + afd_events |= AFD_POLL_DISCONNECT; + if (epoll_events & EPOLLHUP) + afd_events |= AFD_POLL_ABORT; + if (epoll_events & EPOLLERR) + afd_events |= AFD_POLL_CONNECT_FAIL; + + return afd_events; +} + +static inline uint32_t sock__afd_events_to_epoll_events(DWORD afd_events) { + uint32_t epoll_events = 0; + + if (afd_events & (AFD_POLL_RECEIVE | AFD_POLL_ACCEPT)) + epoll_events |= EPOLLIN | EPOLLRDNORM; + if (afd_events & AFD_POLL_RECEIVE_EXPEDITED) + epoll_events |= EPOLLPRI | EPOLLRDBAND; + if (afd_events & AFD_POLL_SEND) + epoll_events |= EPOLLOUT | EPOLLWRNORM | EPOLLWRBAND; + if (afd_events & AFD_POLL_DISCONNECT) + epoll_events |= EPOLLIN | EPOLLRDNORM | EPOLLRDHUP; + if (afd_events & AFD_POLL_ABORT) + epoll_events |= EPOLLHUP; + if (afd_events & AFD_POLL_CONNECT_FAIL) + /* Linux reports all these events after connect() has failed. */ + epoll_events |= + EPOLLIN | EPOLLOUT | EPOLLERR | EPOLLRDNORM | EPOLLWRNORM | EPOLLRDHUP; + + return epoll_events; +} + +int sock_update(port_state_t* port_state, sock_state_t* sock_state) { + assert(!sock_state->delete_pending); + + if ((sock_state->poll_status == SOCK__POLL_PENDING) && + (sock_state->user_events & SOCK__KNOWN_EPOLL_EVENTS & + ~sock_state->pending_events) == 0) { + /* All the events the user is interested in are already being monitored by + * the pending poll operation. It might spuriously complete because of an + * event that we're no longer interested in; when that happens we'll submit + * a new poll operation with the updated event mask. */ + + } else if (sock_state->poll_status == SOCK__POLL_PENDING) { + /* A poll operation is already pending, but it's not monitoring for all the + * events that the user is interested in. Therefore, cancel the pending + * poll operation; when we receive it's completion package, a new poll + * operation will be submitted with the correct event mask. */ + if (sock__cancel_poll(sock_state) < 0) + return -1; + + } else if (sock_state->poll_status == SOCK__POLL_CANCELLED) { + /* The poll operation has already been cancelled, we're still waiting for + * it to return. For now, there's nothing that needs to be done. */ + + } else if (sock_state->poll_status == SOCK__POLL_IDLE) { + /* No poll operation is pending; start one. */ + sock_state->poll_info.Exclusive = FALSE; + sock_state->poll_info.NumberOfHandles = 1; + sock_state->poll_info.Timeout.QuadPart = INT64_MAX; + sock_state->poll_info.Handles[0].Handle = (HANDLE) sock_state->base_socket; + sock_state->poll_info.Handles[0].Status = 0; + sock_state->poll_info.Handles[0].Events = + sock__epoll_events_to_afd_events(sock_state->user_events); + + if (afd_poll(poll_group_get_afd_device_handle(sock_state->poll_group), + &sock_state->poll_info, + &sock_state->io_status_block) < 0) { + switch (GetLastError()) { + case ERROR_IO_PENDING: + /* Overlapped poll operation in progress; this is expected. */ + break; + case ERROR_INVALID_HANDLE: + /* Socket closed; it'll be dropped from the epoll set. */ + return sock__delete(port_state, sock_state, false); + default: + /* Other errors are propagated to the caller. */ + return_map_error(-1); + } + } + + /* The poll request was successfully submitted. */ + sock_state->poll_status = SOCK__POLL_PENDING; + sock_state->pending_events = sock_state->user_events; + + } else { + /* Unreachable. */ + assert(false); + } + + port_cancel_socket_update(port_state, sock_state); + return 0; +} + +int sock_feed_event(port_state_t* port_state, + IO_STATUS_BLOCK* io_status_block, + struct epoll_event* ev) { + sock_state_t* sock_state = + container_of(io_status_block, sock_state_t, io_status_block); + AFD_POLL_INFO* poll_info = &sock_state->poll_info; + uint32_t epoll_events = 0; + + sock_state->poll_status = SOCK__POLL_IDLE; + sock_state->pending_events = 0; + + if (sock_state->delete_pending) { + /* Socket has been deleted earlier and can now be freed. */ + return sock__delete(port_state, sock_state, false); + + } else if (io_status_block->Status == STATUS_CANCELLED) { + /* The poll request was cancelled by CancelIoEx. */ + + } else if (!NT_SUCCESS(io_status_block->Status)) { + /* The overlapped request itself failed in an unexpected way. */ + epoll_events = EPOLLERR; + + } else if (poll_info->NumberOfHandles < 1) { + /* This poll operation succeeded but didn't report any socket events. */ + + } else if (poll_info->Handles[0].Events & AFD_POLL_LOCAL_CLOSE) { + /* The poll operation reported that the socket was closed. */ + return sock__delete(port_state, sock_state, false); + + } else { + /* Events related to our socket were reported. */ + epoll_events = + sock__afd_events_to_epoll_events(poll_info->Handles[0].Events); + } + + /* Requeue the socket so a new poll request will be submitted. */ + port_request_socket_update(port_state, sock_state); + + /* Filter out events that the user didn't ask for. */ + epoll_events &= sock_state->user_events; + + /* Return if there are no epoll events to report. */ + if (epoll_events == 0) + return 0; + + /* If the the socket has the EPOLLONESHOT flag set, unmonitor all events, + * even EPOLLERR and EPOLLHUP. But always keep looking for closed sockets. */ + if (sock_state->user_events & EPOLLONESHOT) + sock_state->user_events = 0; + + ev->data = sock_state->user_data; + ev->events = epoll_events; + return 1; +} + +sock_state_t* sock_state_from_queue_node(queue_node_t* queue_node) { + return container_of(queue_node, sock_state_t, queue_node); +} + +queue_node_t* sock_state_to_queue_node(sock_state_t* sock_state) { + return &sock_state->queue_node; +} + +sock_state_t* sock_state_from_tree_node(tree_node_t* tree_node) { + return container_of(tree_node, sock_state_t, tree_node); +} + +tree_node_t* sock_state_to_tree_node(sock_state_t* sock_state) { + return &sock_state->tree_node; +} + +void ts_tree_init(ts_tree_t* ts_tree) { + tree_init(&ts_tree->tree); + InitializeSRWLock(&ts_tree->lock); +} + +void ts_tree_node_init(ts_tree_node_t* node) { + tree_node_init(&node->tree_node); + reflock_init(&node->reflock); +} + +int ts_tree_add(ts_tree_t* ts_tree, ts_tree_node_t* node, uintptr_t key) { + int r; + + AcquireSRWLockExclusive(&ts_tree->lock); + r = tree_add(&ts_tree->tree, &node->tree_node, key); + ReleaseSRWLockExclusive(&ts_tree->lock); + + return r; +} + +static inline ts_tree_node_t* ts_tree__find_node(ts_tree_t* ts_tree, + uintptr_t key) { + tree_node_t* tree_node = tree_find(&ts_tree->tree, key); + if (tree_node == NULL) + return NULL; + + return container_of(tree_node, ts_tree_node_t, tree_node); +} + +ts_tree_node_t* ts_tree_del_and_ref(ts_tree_t* ts_tree, uintptr_t key) { + ts_tree_node_t* ts_tree_node; + + AcquireSRWLockExclusive(&ts_tree->lock); + + ts_tree_node = ts_tree__find_node(ts_tree, key); + if (ts_tree_node != NULL) { + tree_del(&ts_tree->tree, &ts_tree_node->tree_node); + reflock_ref(&ts_tree_node->reflock); + } + + ReleaseSRWLockExclusive(&ts_tree->lock); + + return ts_tree_node; +} + +ts_tree_node_t* ts_tree_find_and_ref(ts_tree_t* ts_tree, uintptr_t key) { + ts_tree_node_t* ts_tree_node; + + AcquireSRWLockShared(&ts_tree->lock); + + ts_tree_node = ts_tree__find_node(ts_tree, key); + if (ts_tree_node != NULL) + reflock_ref(&ts_tree_node->reflock); + + ReleaseSRWLockShared(&ts_tree->lock); + + return ts_tree_node; +} + +void ts_tree_node_unref(ts_tree_node_t* node) { + reflock_unref(&node->reflock); +} + +void ts_tree_node_unref_and_destroy(ts_tree_node_t* node) { + reflock_unref_and_destroy(&node->reflock); +} + +void tree_init(tree_t* tree) { + memset(tree, 0, sizeof *tree); +} + +void tree_node_init(tree_node_t* node) { + memset(node, 0, sizeof *node); +} + +#define TREE__ROTATE(cis, trans) \ + tree_node_t* p = node; \ + tree_node_t* q = node->trans; \ + tree_node_t* parent = p->parent; \ + \ + if (parent) { \ + if (parent->left == p) \ + parent->left = q; \ + else \ + parent->right = q; \ + } else { \ + tree->root = q; \ + } \ + \ + q->parent = parent; \ + p->parent = q; \ + p->trans = q->cis; \ + if (p->trans) \ + p->trans->parent = p; \ + q->cis = p; + +static inline void tree__rotate_left(tree_t* tree, tree_node_t* node) { + TREE__ROTATE(left, right) +} + +static inline void tree__rotate_right(tree_t* tree, tree_node_t* node) { + TREE__ROTATE(right, left) +} + +#define TREE__INSERT_OR_DESCEND(side) \ + if (parent->side) { \ + parent = parent->side; \ + } else { \ + parent->side = node; \ + break; \ + } + +#define TREE__REBALANCE_AFTER_INSERT(cis, trans) \ + tree_node_t* grandparent = parent->parent; \ + tree_node_t* uncle = grandparent->trans; \ + \ + if (uncle && uncle->red) { \ + parent->red = uncle->red = false; \ + grandparent->red = true; \ + node = grandparent; \ + } else { \ + if (node == parent->trans) { \ + tree__rotate_##cis(tree, parent); \ + node = parent; \ + parent = node->parent; \ + } \ + parent->red = false; \ + grandparent->red = true; \ + tree__rotate_##trans(tree, grandparent); \ + } + +int tree_add(tree_t* tree, tree_node_t* node, uintptr_t key) { + tree_node_t* parent; + + parent = tree->root; + if (parent) { + for (;;) { + if (key < parent->key) { + TREE__INSERT_OR_DESCEND(left) + } else if (key > parent->key) { + TREE__INSERT_OR_DESCEND(right) + } else { + return -1; + } + } + } else { + tree->root = node; + } + + node->key = key; + node->left = node->right = NULL; + node->parent = parent; + node->red = true; + + for (; parent && parent->red; parent = node->parent) { + if (parent == parent->parent->left) { + TREE__REBALANCE_AFTER_INSERT(left, right) + } else { + TREE__REBALANCE_AFTER_INSERT(right, left) + } + } + tree->root->red = false; + + return 0; +} + +#define TREE__REBALANCE_AFTER_REMOVE(cis, trans) \ + tree_node_t* sibling = parent->trans; \ + \ + if (sibling->red) { \ + sibling->red = false; \ + parent->red = true; \ + tree__rotate_##cis(tree, parent); \ + sibling = parent->trans; \ + } \ + if ((sibling->left && sibling->left->red) || \ + (sibling->right && sibling->right->red)) { \ + if (!sibling->trans || !sibling->trans->red) { \ + sibling->cis->red = false; \ + sibling->red = true; \ + tree__rotate_##trans(tree, sibling); \ + sibling = parent->trans; \ + } \ + sibling->red = parent->red; \ + parent->red = sibling->trans->red = false; \ + tree__rotate_##cis(tree, parent); \ + node = tree->root; \ + break; \ + } \ + sibling->red = true; + +void tree_del(tree_t* tree, tree_node_t* node) { + tree_node_t* parent = node->parent; + tree_node_t* left = node->left; + tree_node_t* right = node->right; + tree_node_t* next; + bool red; + + if (!left) { + next = right; + } else if (!right) { + next = left; + } else { + next = right; + while (next->left) + next = next->left; + } + + if (parent) { + if (parent->left == node) + parent->left = next; + else + parent->right = next; + } else { + tree->root = next; + } + + if (left && right) { + red = next->red; + next->red = node->red; + next->left = left; + left->parent = next; + if (next != right) { + parent = next->parent; + next->parent = node->parent; + node = next->right; + parent->left = node; + next->right = right; + right->parent = next; + } else { + next->parent = parent; + parent = next; + node = next->right; + } + } else { + red = node->red; + node = next; + } + + if (node) + node->parent = parent; + if (red) + return; + if (node && node->red) { + node->red = false; + return; + } + + do { + if (node == tree->root) + break; + if (node == parent->left) { + TREE__REBALANCE_AFTER_REMOVE(left, right) + } else { + TREE__REBALANCE_AFTER_REMOVE(right, left) + } + node = parent; + parent = parent->parent; + } while (!node->red); + + if (node) + node->red = false; +} + +tree_node_t* tree_find(const tree_t* tree, uintptr_t key) { + tree_node_t* node = tree->root; + while (node) { + if (key < node->key) + node = node->left; + else if (key > node->key) + node = node->right; + else + return node; + } + return NULL; +} + +tree_node_t* tree_root(const tree_t* tree) { + return tree->root; +} + +#ifndef SIO_BSP_HANDLE_POLL +#define SIO_BSP_HANDLE_POLL 0x4800001D +#endif + +#ifndef SIO_BASE_HANDLE +#define SIO_BASE_HANDLE 0x48000022 +#endif + +int ws_global_init(void) { + int r; + WSADATA wsa_data; + + r = WSAStartup(MAKEWORD(2, 2), &wsa_data); + if (r != 0) + return_set_error(-1, (DWORD) r); + + return 0; +} + +static inline SOCKET ws__ioctl_get_bsp_socket(SOCKET socket, DWORD ioctl) { + SOCKET bsp_socket; + DWORD bytes; + + if (WSAIoctl(socket, + ioctl, + NULL, + 0, + &bsp_socket, + sizeof bsp_socket, + &bytes, + NULL, + NULL) != SOCKET_ERROR) + return bsp_socket; + else + return INVALID_SOCKET; +} + +SOCKET ws_get_base_socket(SOCKET socket) { + SOCKET base_socket; + DWORD error; + + for (;;) { + base_socket = ws__ioctl_get_bsp_socket(socket, SIO_BASE_HANDLE); + if (base_socket != INVALID_SOCKET) + return base_socket; + + error = GetLastError(); + if (error == WSAENOTSOCK) + return_set_error(INVALID_SOCKET, error); + + /* Even though Microsoft documentation clearly states that LSPs should + * never intercept the `SIO_BASE_HANDLE` ioctl [1], Komodia based LSPs do + * so anyway, breaking it, with the apparent intention of preventing LSP + * bypass [2]. Fortunately they don't handle `SIO_BSP_HANDLE_POLL`, which + * will at least let us obtain the socket associated with the next winsock + * protocol chain entry. If this succeeds, loop around and call + * `SIO_BASE_HANDLE` again with the returned BSP socket, to make sure that + * we unwrap all layers and retrieve the actual base socket. + * [1] https://docs.microsoft.com/en-us/windows/win32/winsock/winsock-ioctls + * [2] https://www.komodia.com/newwiki/index.php?title=Komodia%27s_Redirector_bug_fixes#Version_2.2.2.6 + */ + base_socket = ws__ioctl_get_bsp_socket(socket, SIO_BSP_HANDLE_POLL); + if (base_socket != INVALID_SOCKET && base_socket != socket) + socket = base_socket; + else + return_set_error(INVALID_SOCKET, error); + } +} diff --git a/ww/libhv/event/wepoll/wepoll.h b/ww/libhv/event/wepoll/wepoll.h new file mode 100644 index 00000000..daf6bdb0 --- /dev/null +++ b/ww/libhv/event/wepoll/wepoll.h @@ -0,0 +1,113 @@ +/* + * wepoll - epoll for Windows + * https://github.com/piscisaureus/wepoll + * + * Copyright 2012-2020, Bert Belder + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef WEPOLL_H_ +#define WEPOLL_H_ + +#ifndef WEPOLL_EXPORT +#define WEPOLL_EXPORT +#endif + +#include + +enum EPOLL_EVENTS { + EPOLLIN = (int) (1U << 0), + EPOLLPRI = (int) (1U << 1), + EPOLLOUT = (int) (1U << 2), + EPOLLERR = (int) (1U << 3), + EPOLLHUP = (int) (1U << 4), + EPOLLRDNORM = (int) (1U << 6), + EPOLLRDBAND = (int) (1U << 7), + EPOLLWRNORM = (int) (1U << 8), + EPOLLWRBAND = (int) (1U << 9), + EPOLLMSG = (int) (1U << 10), /* Never reported. */ + EPOLLRDHUP = (int) (1U << 13), + EPOLLONESHOT = (int) (1U << 31) +}; + +#define EPOLLIN (1U << 0) +#define EPOLLPRI (1U << 1) +#define EPOLLOUT (1U << 2) +#define EPOLLERR (1U << 3) +#define EPOLLHUP (1U << 4) +#define EPOLLRDNORM (1U << 6) +#define EPOLLRDBAND (1U << 7) +#define EPOLLWRNORM (1U << 8) +#define EPOLLWRBAND (1U << 9) +#define EPOLLMSG (1U << 10) +#define EPOLLRDHUP (1U << 13) +#define EPOLLONESHOT (1U << 31) + +#define EPOLL_CTL_ADD 1 +#define EPOLL_CTL_MOD 2 +#define EPOLL_CTL_DEL 3 + +typedef void* HANDLE; +typedef uintptr_t SOCKET; + +typedef union epoll_data { + void* ptr; + int fd; + uint32_t u32; + uint64_t u64; + SOCKET sock; /* Windows specific */ + HANDLE hnd; /* Windows specific */ +} epoll_data_t; + +struct epoll_event { + uint32_t events; /* Epoll events and flags */ + epoll_data_t data; /* User data variable */ +}; + +#ifdef __cplusplus +extern "C" { +#endif + +WEPOLL_EXPORT HANDLE epoll_create(int size); +WEPOLL_EXPORT HANDLE epoll_create1(int flags); + +WEPOLL_EXPORT int epoll_close(HANDLE ephnd); + +WEPOLL_EXPORT int epoll_ctl(HANDLE ephnd, + int op, + SOCKET sock, + struct epoll_event* event); + +WEPOLL_EXPORT int epoll_wait(HANDLE ephnd, + struct epoll_event* events, + int maxevents, + int timeout); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* WEPOLL_H_ */ diff --git a/ww/libhv/hexport.h b/ww/libhv/hexport.h new file mode 100644 index 00000000..7d370169 --- /dev/null +++ b/ww/libhv/hexport.h @@ -0,0 +1,157 @@ +#ifndef HV_EXPORT_H_ +#define HV_EXPORT_H_ + +// HV_EXPORT +#if defined(HV_STATICLIB) || defined(HV_SOURCE) + #define HV_EXPORT +#elif defined(_MSC_VER) + #if defined(HV_DYNAMICLIB) || defined(HV_EXPORTS) || defined(hv_EXPORTS) + #define HV_EXPORT __declspec(dllexport) + #else + #define HV_EXPORT __declspec(dllimport) + #endif +#elif defined(__GNUC__) + #define HV_EXPORT __attribute__((visibility("default"))) +#else + #define HV_EXPORT +#endif + +// HV_INLINE +#define HV_INLINE static inline + +// HV_DEPRECATED +#if defined(HV_NO_DEPRECATED) +#define HV_DEPRECATED +#elif defined(__GNUC__) || defined(__clang__) +#define HV_DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) +#define HV_DEPRECATED __declspec(deprecated) +#else +#define HV_DEPRECATED +#endif + +// HV_UNUSED +#if defined(__GNUC__) + #define HV_UNUSED __attribute__((visibility("unused"))) +#else + #define HV_UNUSED +#endif + +// @param[IN | OUT | INOUT] +#ifndef IN +#define IN +#endif + +#ifndef OUT +#define OUT +#endif + +#ifndef INOUT +#define INOUT +#endif + +// @field[OPTIONAL | REQUIRED | REPEATED] +#ifndef OPTIONAL +#define OPTIONAL +#endif + +#ifndef REQUIRED +#define REQUIRED +#endif + +#ifndef REPEATED +#define REPEATED +#endif + +#ifdef __cplusplus + +#ifndef EXTERN_C +#define EXTERN_C extern "C" +#endif + +#ifndef BEGIN_EXTERN_C +#define BEGIN_EXTERN_C extern "C" { +#endif + +#ifndef END_EXTERN_C +#define END_EXTERN_C } // extern "C" +#endif + +#ifndef BEGIN_NAMESPACE +#define BEGIN_NAMESPACE(ns) namespace ns { +#endif + +#ifndef END_NAMESPACE +#define END_NAMESPACE(ns) } // namespace ns +#endif + +#ifndef USING_NAMESPACE +#define USING_NAMESPACE(ns) using namespace ns; +#endif + +#ifndef DEFAULT +#define DEFAULT(x) = x +#endif + +#ifndef ENUM +#define ENUM(e) enum e +#endif + +#ifndef STRUCT +#define STRUCT(s) struct s +#endif + +#else + +#define EXTERN_C extern +#define BEGIN_EXTERN_C +#define END_EXTERN_C + +#define BEGIN_NAMESPACE(ns) +#define END_NAMESPACE(ns) +#define USING_NAMESPACE(ns) + +#ifndef DEFAULT +#define DEFAULT(x) +#endif + +#ifndef ENUM +#define ENUM(e)\ +typedef enum e e;\ +enum e +#endif + +#ifndef STRUCT +#define STRUCT(s)\ +typedef struct s s;\ +struct s +#endif + +#endif // __cplusplus + +#define BEGIN_NAMESPACE_HV BEGIN_NAMESPACE(hv) +#define END_NAMESPACE_HV END_NAMESPACE(hv) +#define USING_NAMESPACE_HV USING_NAMESPACE(hv) + +// MSVC ports +#ifdef _MSC_VER + +#pragma warning (disable: 4251) // STL dll +#pragma warning (disable: 4275) // dll-interface + +#if _MSC_VER < 1900 // < VS2015 + +#ifndef __cplusplus +#ifndef inline +#define inline __inline +#endif +#endif + +#ifndef snprintf +#define snprintf _snprintf +#endif + +#endif +#endif + +#endif // HV_EXPORT_H_ diff --git a/ww/libhv/hv.h b/ww/libhv/hv.h new file mode 100644 index 00000000..ef6fb2ba --- /dev/null +++ b/ww/libhv/hv.h @@ -0,0 +1,41 @@ +#ifndef HV_H_ +#define HV_H_ + +/** + * @copyright 2018 HeWei, all rights reserved. + */ + +// platform +#include "hconfig.h" +#include "hexport.h" +#include "hplatform.h" + +// c +#include "hdef.h" // +#include "hatomic.h"// +#include "herr.h" // +#include "htime.h" // +#include "hmath.h" // + +#include "hbase.h" +#include "hversion.h" +#include "hsysinfo.h" +#include "hproc.h" +#include "hthread.h" +#include "hmutex.h" +#include "hsocket.h" + +#include "hlog.h" +#include "hbuf.h" + +// cpp +#ifdef __cplusplus +#include "hmap.h" // +#include "hstring.h" // +#include "hfile.h" +#include "hpath.h" +#include "hdir.h" +#include "hurl.h" +#endif + +#endif // HV_H_ diff --git a/ww/libhv/mqtt/mqtt_client.c b/ww/libhv/mqtt/mqtt_client.c new file mode 100644 index 00000000..2a62d38b --- /dev/null +++ b/ww/libhv/mqtt/mqtt_client.c @@ -0,0 +1,602 @@ +#include "mqtt_client.h" +#include "hbase.h" +#include "hlog.h" +#include "herr.h" +#include "hendian.h" + +static unsigned short mqtt_next_mid() { + static unsigned short s_mid = 0; + return ++s_mid; +} + +static int mqtt_client_send(mqtt_client_t* cli, const void* buf, int len) { + // thread-safe + hmutex_lock(&cli->mutex_); + int nwrite = hio_write(cli->io, buf, len); + hmutex_unlock(&cli->mutex_); + return nwrite; +} + +static int mqtt_send_head(hio_t* io, int type, int length) { + mqtt_client_t* cli = (mqtt_client_t*)hevent_userdata(io); + mqtt_head_t head; + memset(&head, 0, sizeof(head)); + head.type = type; + head.length = length; + unsigned char headbuf[8] = { 0 }; + int headlen = mqtt_head_pack(&head, headbuf); + return mqtt_client_send(cli, headbuf, headlen); +} + +static int mqtt_send_head_with_mid(hio_t* io, int type, unsigned short mid) { + mqtt_client_t* cli = (mqtt_client_t*)hevent_userdata(io); + mqtt_head_t head; + memset(&head, 0, sizeof(head)); + head.type = type; + if (head.type == MQTT_TYPE_PUBREL) { + head.qos = 1; + } + head.length = 2; + unsigned char headbuf[8] = { 0 }; + unsigned char* p = headbuf; + int headlen = mqtt_head_pack(&head, p); + p += headlen; + PUSH16(p, mid); + return mqtt_client_send(cli, headbuf, headlen + 2); +} + +static void mqtt_send_ping(hio_t* io) { + mqtt_client_t* cli = (mqtt_client_t*)hevent_userdata(io); + if (cli->ping_cnt++ == 3) { + hloge("mqtt no pong!"); + hio_close(io); + return; + } + mqtt_send_head(io, MQTT_TYPE_PINGREQ, 0); +} + +static void mqtt_send_pong(hio_t* io) { + mqtt_send_head(io, MQTT_TYPE_PINGRESP, 0); +} + +static void mqtt_send_disconnect(hio_t* io) { + mqtt_send_head(io, MQTT_TYPE_DISCONNECT, 0); +} + +/* + * MQTT_TYPE_CONNECT + * 2 + protocol_name + 1 protocol_version + 1 conn_flags + 2 keepalive + 2 + [client_id] + + * [2 + will_topic + 2 + will_payload] + + * [2 + username] + [2 + password] + */ +static int mqtt_client_login(mqtt_client_t* cli) { + int len = 2 + 1 + 1 + 2 + 2; + unsigned short cid_len = 0, + will_topic_len = 0, + will_payload_len = 0, + username_len = 0, + password_len = 0; + unsigned char conn_flags = 0; + + // protocol_name_len + len += cli->protocol_version == MQTT_PROTOCOL_V31 ? 6 : 4; + if (*cli->client_id) { + cid_len = strlen(cli->client_id); + } else { + cid_len = 20; + hv_random_string(cli->client_id, cid_len); + hlogi("MQTT client_id: %.*s", (int)cid_len, cli->client_id); + } + len += cid_len; + if (cid_len == 0) cli->clean_session = 1; + if (cli->clean_session) { + conn_flags |= MQTT_CONN_CLEAN_SESSION; + } + if (cli->will && cli->will->topic && cli->will->payload) { + will_topic_len = cli->will->topic_len ? cli->will->topic_len : strlen(cli->will->topic); + will_payload_len = cli->will->payload_len ? cli->will->payload_len : strlen(cli->will->payload); + if (will_topic_len && will_payload_len) { + conn_flags |= MQTT_CONN_HAS_WILL; + conn_flags |= ((cli->will->qos & 3) << 3); + if (cli->will->retain) { + conn_flags |= MQTT_CONN_WILL_RETAIN; + } + len += 2 + will_topic_len; + len += 2 + will_payload_len; + } + } + if (*cli->username) { + username_len = strlen(cli->username); + if (username_len) { + conn_flags |= MQTT_CONN_HAS_USERNAME; + len += 2 + username_len; + } + } + if (*cli->password) { + password_len = strlen(cli->password); + if (password_len) { + conn_flags |= MQTT_CONN_HAS_PASSWORD; + len += 2 + password_len; + } + } + + mqtt_head_t head; + memset(&head, 0, sizeof(head)); + head.type = MQTT_TYPE_CONNECT; + head.length = len; + int buflen = mqtt_estimate_length(&head); + unsigned char* buf = NULL; + HV_STACK_ALLOC(buf, buflen); + unsigned char* p = buf; + int headlen = mqtt_head_pack(&head, p); + p += headlen; + // TODO: Not implement MQTT_PROTOCOL_V5 + if (cli->protocol_version == MQTT_PROTOCOL_V31) { + PUSH16(p, 6); + PUSH_N(p, MQTT_PROTOCOL_NAME_v31, 6); + } else { + PUSH16(p, 4); + PUSH_N(p, MQTT_PROTOCOL_NAME, 4); + } + PUSH8(p, cli->protocol_version); + PUSH8(p, conn_flags); + PUSH16(p, cli->keepalive); + PUSH16(p, cid_len); + if (cid_len > 0) { + PUSH_N(p, cli->client_id, cid_len); + } + if (conn_flags & MQTT_CONN_HAS_WILL) { + PUSH16(p, will_topic_len); + PUSH_N(p, cli->will->topic, will_topic_len); + PUSH16(p, will_payload_len); + PUSH_N(p, cli->will->payload, will_payload_len); + } + if (conn_flags & MQTT_CONN_HAS_USERNAME) { + PUSH16(p, username_len); + PUSH_N(p, cli->username, username_len); + } + if (conn_flags & MQTT_CONN_HAS_PASSWORD) { + PUSH16(p, password_len); + PUSH_N(p, cli->password, password_len); + } + + int nwrite = mqtt_client_send(cli, buf, p - buf); + HV_STACK_FREE(buf); + return nwrite < 0 ? nwrite : 0; +} + +static void reconnect_timer_cb(htimer_t* timer) { + mqtt_client_t* cli = (mqtt_client_t*)hevent_userdata(timer); + if (cli == NULL) return; + cli->reconn_timer = NULL; + mqtt_client_reconnect(cli); +} + +static void on_close(hio_t* io) { + mqtt_client_t* cli = (mqtt_client_t*)hevent_userdata(io); + cli->connected = 0; + if (cli->cb) { + cli->head.type = MQTT_TYPE_DISCONNECT; + cli->cb(cli, cli->head.type); + } + // reconnect + if (cli->reconn_setting && reconn_setting_can_retry(cli->reconn_setting)) { + uint32_t delay = reconn_setting_calc_delay(cli->reconn_setting); + cli->reconn_timer = htimer_add(cli->loop, reconnect_timer_cb, delay, 1); + hevent_set_userdata(cli->reconn_timer, cli); + } +} + +static void on_packet(hio_t* io, void* buf, int len) { + mqtt_client_t* cli = (mqtt_client_t*)hevent_userdata(io); + unsigned char* p = (unsigned char*)buf; + unsigned char* end = p + len; + memset(&cli->head, 0, sizeof(mqtt_head_t)); + int headlen = mqtt_head_unpack(&cli->head, p, len); + if (headlen <= 0) return; + p += headlen; + switch (cli->head.type) { + // case MQTT_TYPE_CONNECT: + case MQTT_TYPE_CONNACK: + { + if (cli->head.length < 2) { + hloge("MQTT CONNACK malformed!"); + hio_close(io); + return; + } + unsigned char conn_flags = 0, rc = 0; + POP8(p, conn_flags); + POP8(p, rc); + if (rc != MQTT_CONNACK_ACCEPTED) { + cli->error = rc; + hloge("MQTT CONNACK error=%d", cli->error); + hio_close(io); + return; + } + cli->connected = 1; + if (cli->keepalive) { + cli->ping_cnt = 0; + hio_set_heartbeat(io, cli->keepalive * 1000, mqtt_send_ping); + } + } + break; + case MQTT_TYPE_PUBLISH: + { + if (cli->head.length < 2) { + hloge("MQTT PUBLISH malformed!"); + hio_close(io); + return; + } + memset(&cli->message, 0, sizeof(mqtt_message_t)); + POP16(p, cli->message.topic_len); + if (end - p < cli->message.topic_len) { + hloge("MQTT PUBLISH malformed!"); + hio_close(io); + return; + } + // NOTE: Not deep copy + cli->message.topic = (char*)p; + p += cli->message.topic_len; + if (cli->head.qos > 0) { + if (end - p < 2) { + hloge("MQTT PUBLISH malformed!"); + hio_close(io); + return; + } + POP16(p, cli->mid); + } + cli->message.payload_len = end - p; + if (cli->message.payload_len > 0) { + // NOTE: Not deep copy + cli->message.payload = (char*)p; + } + cli->message.qos = cli->head.qos; + if (cli->message.qos == 0) { + // Do nothing + } else if (cli->message.qos == 1) { + mqtt_send_head_with_mid(io, MQTT_TYPE_PUBACK, cli->mid); + } else if (cli->message.qos == 2) { + mqtt_send_head_with_mid(io, MQTT_TYPE_PUBREC, cli->mid); + } + } + break; + case MQTT_TYPE_PUBACK: + case MQTT_TYPE_PUBREC: + case MQTT_TYPE_PUBREL: + case MQTT_TYPE_PUBCOMP: + { + if (cli->head.length < 2) { + hloge("MQTT PUBACK malformed!"); + hio_close(io); + return; + } + POP16(p, cli->mid); + if (cli->head.type == MQTT_TYPE_PUBREC) { + mqtt_send_head_with_mid(io, MQTT_TYPE_PUBREL, cli->mid); + } else if (cli->head.type == MQTT_TYPE_PUBREL) { + mqtt_send_head_with_mid(io, MQTT_TYPE_PUBCOMP, cli->mid); + } + } + break; + // case MQTT_TYPE_SUBSCRIBE: + // break; + case MQTT_TYPE_SUBACK: + { + if (cli->head.length < 2) { + hloge("MQTT SUBACK malformed!"); + hio_close(io); + return; + } + POP16(p, cli->mid); + } + break; + // case MQTT_TYPE_UNSUBSCRIBE: + // break; + case MQTT_TYPE_UNSUBACK: + { + if (cli->head.length < 2) { + hloge("MQTT UNSUBACK malformed!"); + hio_close(io); + return; + } + POP16(p, cli->mid); + } + break; + case MQTT_TYPE_PINGREQ: + // printf("recv ping\n"); + // printf("send pong\n"); + mqtt_send_pong(io); + return; + case MQTT_TYPE_PINGRESP: + // printf("recv pong\n"); + cli->ping_cnt = 0; + return; + case MQTT_TYPE_DISCONNECT: + hio_close(io); + return; + default: + hloge("MQTT client received wrong type=%d", (int)cli->head.type); + hio_close(io); + return; + } + + if (cli->cb) { + cli->cb(cli, cli->head.type); + } +} + +static void on_connect(hio_t* io) { + mqtt_client_t* cli = (mqtt_client_t*)hevent_userdata(io); + if (cli->cb) { + cli->head.type = MQTT_TYPE_CONNECT; + cli->cb(cli, cli->head.type); + } + if (cli->reconn_setting) { + reconn_setting_reset(cli->reconn_setting); + } + + static unpack_setting_t mqtt_unpack_setting; + mqtt_unpack_setting.mode = UNPACK_BY_LENGTH_FIELD; + mqtt_unpack_setting.package_max_length = DEFAULT_MQTT_PACKAGE_MAX_LENGTH; + mqtt_unpack_setting.body_offset = 2; + mqtt_unpack_setting.length_field_offset = 1; + mqtt_unpack_setting.length_field_bytes = 1; + mqtt_unpack_setting.length_field_coding = ENCODE_BY_VARINT; + hio_set_unpack(io, &mqtt_unpack_setting); + + // start recv packet + hio_setcb_read(io, on_packet); + hio_read(io); + + mqtt_client_login(cli); +} + +mqtt_client_t* mqtt_client_new(hloop_t* loop) { + if (loop == NULL) { + loop = hloop_new(HLOOP_FLAG_AUTO_FREE); + if (loop == NULL) return NULL; + } + mqtt_client_t* cli = NULL; + HV_ALLOC_SIZEOF(cli); + if (cli == NULL) return NULL; + cli->loop = loop; + cli->protocol_version = MQTT_PROTOCOL_V311; + cli->keepalive = DEFAULT_MQTT_KEEPALIVE; + hmutex_init(&cli->mutex_); + return cli; +} + +void mqtt_client_free(mqtt_client_t* cli) { + if (!cli) return; + hmutex_destroy(&cli->mutex_); + if (cli->ssl_ctx && cli->alloced_ssl_ctx) { + hssl_ctx_free(cli->ssl_ctx); + cli->ssl_ctx = NULL; + } + HV_FREE(cli->reconn_setting); + HV_FREE(cli->will); + HV_FREE(cli); +} + +void mqtt_client_run (mqtt_client_t* cli) { + if (!cli || !cli->loop) return; + hloop_run(cli->loop); +} + +void mqtt_client_stop(mqtt_client_t* cli) { + if (!cli || !cli->loop) return; + hloop_stop(cli->loop); +} + +void mqtt_client_set_id(mqtt_client_t* cli, const char* id) { + if (!cli || !id) return; + hv_strncpy(cli->client_id, id, sizeof(cli->client_id)); +} + +void mqtt_client_set_will(mqtt_client_t* cli, mqtt_message_t* will) { + if (!cli || !will) return; + if (cli->will == NULL) { + HV_ALLOC_SIZEOF(cli->will); + } + memcpy(cli->will, will, sizeof(mqtt_message_t)); +} + +void mqtt_client_set_auth(mqtt_client_t* cli, const char* username, const char* password) { + if (!cli) return; + if (username) { + hv_strncpy(cli->username, username, sizeof(cli->username)); + } + if (password) { + hv_strncpy(cli->password, password, sizeof(cli->password)); + } +} + +void mqtt_client_set_callback(mqtt_client_t* cli, mqtt_client_cb cb) { + if (!cli) return; + cli->cb = cb; +} + +void mqtt_client_set_userdata(mqtt_client_t* cli, void* userdata) { + if (!cli) return; + cli->userdata = userdata; +} + +void* mqtt_client_get_userdata(mqtt_client_t* cli) { + if (!cli) return NULL; + return cli->userdata; +} + +int mqtt_client_get_last_error(mqtt_client_t* cli) { + if (!cli) return -1; + return cli->error; +} + +int mqtt_client_set_ssl_ctx(mqtt_client_t* cli, hssl_ctx_t ssl_ctx) { + cli->ssl_ctx = ssl_ctx; + return 0; +} + +int mqtt_client_new_ssl_ctx(mqtt_client_t* cli, hssl_ctx_opt_t* opt) { + opt->endpoint = HSSL_CLIENT; + hssl_ctx_t ssl_ctx = hssl_ctx_new(opt); + if (ssl_ctx == NULL) return ERR_NEW_SSL_CTX; + cli->alloced_ssl_ctx = true; + return mqtt_client_set_ssl_ctx(cli, ssl_ctx); +} + +int mqtt_client_set_reconnect(mqtt_client_t* cli, reconn_setting_t* reconn) { + if (reconn == NULL) { + HV_FREE(cli->reconn_setting); + return 0; + } + if (cli->reconn_setting == NULL) { + HV_ALLOC_SIZEOF(cli->reconn_setting); + } + *cli->reconn_setting = *reconn; + return 0; +} + +int mqtt_client_reconnect(mqtt_client_t* cli) { + mqtt_client_connect(cli, cli->host, cli->port, cli->ssl); + return 0; +} + +void mqtt_client_set_connect_timeout(mqtt_client_t* cli, int ms) { + cli->connect_timeout = ms; +} + +int mqtt_client_connect(mqtt_client_t* cli, const char* host, int port, int ssl) { + if (!cli) return -1; + hv_strncpy(cli->host, host, sizeof(cli->host)); + cli->port = port; + cli->ssl = ssl; + hio_t* io = hio_create_socket(cli->loop, host, port, HIO_TYPE_TCP, HIO_CLIENT_SIDE); + if (io == NULL) return -1; + if (ssl) { + if (cli->ssl_ctx) { + hio_set_ssl_ctx(io, cli->ssl_ctx); + } + hio_enable_ssl(io); + } + if (cli->connect_timeout > 0) { + hio_set_connect_timeout(io, cli->connect_timeout); + } + cli->io = io; + hevent_set_userdata(io, cli); + hio_setcb_connect(io, on_connect); + hio_setcb_close(io, on_close); + return hio_connect(io); +} + +bool mqtt_client_is_connected(mqtt_client_t* cli) { + return cli && cli->connected; +} + +int mqtt_client_disconnect(mqtt_client_t* cli) { + if (!cli || !cli->io) return -1; + // cancel reconnect first + mqtt_client_set_reconnect(cli, NULL); + mqtt_send_disconnect(cli->io); + return hio_close(cli->io); +} + +int mqtt_client_publish(mqtt_client_t* cli, mqtt_message_t* msg) { + if (!cli || !cli->io || !msg) return -1; + if (!cli->connected) return -2; + int topic_len = msg->topic_len ? msg->topic_len : strlen(msg->topic); + int payload_len = msg->payload_len ? msg->payload_len : strlen(msg->payload); + int len = 2 + topic_len + payload_len; + if (msg->qos > 0) len += 2; // mid + unsigned short mid = 0; + + mqtt_head_t head; + memset(&head, 0, sizeof(head)); + head.type = MQTT_TYPE_PUBLISH; + head.qos = msg->qos & 3; + head.retain = msg->retain; + head.length = len; + int buflen = mqtt_estimate_length(&head); + // NOTE: send payload alone + buflen -= payload_len; + unsigned char* buf = NULL; + HV_STACK_ALLOC(buf, buflen); + unsigned char* p = buf; + int headlen = mqtt_head_pack(&head, p); + p += headlen; + PUSH16(p, topic_len); + PUSH_N(p, msg->topic, topic_len); + if (msg->qos) { + mid = mqtt_next_mid(); + PUSH16(p, mid); + } + + hmutex_lock(&cli->mutex_); + // send head + topic + mid + int nwrite = hio_write(cli->io, buf, p - buf); + HV_STACK_FREE(buf); + if (nwrite < 0) { + goto unlock; + } + + // send payload + nwrite = hio_write(cli->io, msg->payload, payload_len); + +unlock: + hmutex_unlock(&cli->mutex_); + return nwrite < 0 ? nwrite : mid; +} + +int mqtt_client_subscribe(mqtt_client_t* cli, const char* topic, int qos) { + if (!cli || !cli->io || !topic) return -1; + if (!cli->connected) return -2; + int topic_len = strlen(topic); + int len = 2 + 2 + topic_len + 1; + + mqtt_head_t head; + memset(&head, 0, sizeof(head)); + head.type = MQTT_TYPE_SUBSCRIBE; + head.qos = 1; + head.length = len; + int buflen = mqtt_estimate_length(&head); + unsigned char* buf = NULL; + HV_STACK_ALLOC(buf, buflen); + unsigned char* p = buf; + int headlen = mqtt_head_pack(&head, p); + p += headlen; + unsigned short mid = mqtt_next_mid(); + PUSH16(p, mid); + PUSH16(p, topic_len); + PUSH_N(p, topic, topic_len); + PUSH8(p, qos & 3); + // send head + mid + topic + qos + int nwrite = mqtt_client_send(cli, buf, p - buf); + HV_STACK_FREE(buf); + return nwrite < 0 ? nwrite : mid; +} + +int mqtt_client_unsubscribe(mqtt_client_t* cli, const char* topic) { + if (!cli || !cli->io || !topic) return -1; + if (!cli->connected) return -2; + int topic_len = strlen(topic); + int len = 2 + 2 + topic_len; + + mqtt_head_t head; + memset(&head, 0, sizeof(head)); + head.type = MQTT_TYPE_UNSUBSCRIBE; + head.qos = 1; + head.length = len; + int buflen = mqtt_estimate_length(&head); + unsigned char* buf = NULL; + HV_STACK_ALLOC(buf, buflen); + unsigned char* p = buf; + int headlen = mqtt_head_pack(&head, p); + p += headlen; + unsigned short mid = mqtt_next_mid(); + PUSH16(p, mid); + PUSH16(p, topic_len); + PUSH_N(p, topic, topic_len); + // send head + mid + topic + int nwrite = mqtt_client_send(cli, buf, p - buf); + HV_STACK_FREE(buf); + return nwrite < 0 ? nwrite : mid; +} diff --git a/ww/libhv/mqtt/mqtt_client.h b/ww/libhv/mqtt/mqtt_client.h new file mode 100644 index 00000000..8001dc50 --- /dev/null +++ b/ww/libhv/mqtt/mqtt_client.h @@ -0,0 +1,335 @@ +#ifndef HV_MQTT_CLIENT_H_ +#define HV_MQTT_CLIENT_H_ + +#include "mqtt_protocol.h" +#include "hloop.h" +#include "hssl.h" +#include "hmutex.h" + +#define DEFAULT_MQTT_KEEPALIVE 60 // s + +typedef struct mqtt_client_s mqtt_client_t; + +// @type mqtt_type_e +// @example examples/mqtt +typedef void (*mqtt_client_cb)(mqtt_client_t* cli, int type); + +struct mqtt_client_s { + // connect: host:port + char host[256]; + int port; + int connect_timeout; // ms + // reconnect + reconn_setting_t* reconn_setting; + // login: flags + keepalive + client_id + will + username + password + // flags + unsigned char protocol_version; // Default MQTT_PROTOCOL_V311 + unsigned char clean_session: 1; + unsigned char ssl: 1; // Read Only + unsigned char alloced_ssl_ctx: 1; // intern + unsigned char connected : 1; + unsigned short keepalive; + int ping_cnt; + char client_id[64]; + // will + mqtt_message_t* will; + // auth + char username[64]; + char password[64]; + // message + mqtt_head_t head; + int error; // for MQTT_TYPE_CONNACK + int mid; // for MQTT_TYPE_SUBACK, MQTT_TYPE_PUBACK + mqtt_message_t message; // for MQTT_TYPE_PUBLISH + // callback + mqtt_client_cb cb; + // userdata + void* userdata; + // privdata + hloop_t* loop; + hio_t* io; + htimer_t* reconn_timer; + // SSL/TLS + hssl_ctx_t ssl_ctx; + // thread-safe + hmutex_t mutex_; +}; + +BEGIN_EXTERN_C + +// hloop_new -> malloc(mqtt_client_t) +HV_EXPORT mqtt_client_t* mqtt_client_new(hloop_t* loop DEFAULT(NULL)); +// @see hloop_run +HV_EXPORT void mqtt_client_run (mqtt_client_t* cli); +// @see hloop_stop +HV_EXPORT void mqtt_client_stop(mqtt_client_t* cli); +// hloop_free -> free(mqtt_client_t) +HV_EXPORT void mqtt_client_free(mqtt_client_t* cli); + +// id +HV_EXPORT void mqtt_client_set_id(mqtt_client_t* cli, const char* id); + +// will +HV_EXPORT void mqtt_client_set_will(mqtt_client_t* cli, + mqtt_message_t* will); + +// auth +HV_EXPORT void mqtt_client_set_auth(mqtt_client_t* cli, + const char* username, const char* password); + +// callback +HV_EXPORT void mqtt_client_set_callback(mqtt_client_t* cli, mqtt_client_cb cb); + +// userdata +HV_EXPORT void mqtt_client_set_userdata(mqtt_client_t* cli, void* userdata); +HV_EXPORT void* mqtt_client_get_userdata(mqtt_client_t* cli); + +// error +HV_EXPORT int mqtt_client_get_last_error(mqtt_client_t* cli); + +// SSL/TLS +HV_EXPORT int mqtt_client_set_ssl_ctx(mqtt_client_t* cli, hssl_ctx_t ssl_ctx); +// hssl_ctx_new(opt) -> mqtt_client_set_ssl_ctx +HV_EXPORT int mqtt_client_new_ssl_ctx(mqtt_client_t* cli, hssl_ctx_opt_t* opt); + +// reconnect +HV_EXPORT int mqtt_client_set_reconnect(mqtt_client_t* cli, + reconn_setting_t* reconn); +HV_EXPORT int mqtt_client_reconnect(mqtt_client_t* cli); + +// connect +// hio_create_socket -> hio_connect -> +// on_connect -> mqtt_client_login -> +// on_connack +HV_EXPORT void mqtt_client_set_connect_timeout(mqtt_client_t* cli, int ms); +HV_EXPORT int mqtt_client_connect(mqtt_client_t* cli, + const char* host, + int port DEFAULT(DEFAULT_MQTT_PORT), + int ssl DEFAULT(0)); +HV_EXPORT bool mqtt_client_is_connected(mqtt_client_t* cli); + +// disconnect +// @see hio_close +HV_EXPORT int mqtt_client_disconnect(mqtt_client_t* cli); + +// publish +HV_EXPORT int mqtt_client_publish(mqtt_client_t* cli, + mqtt_message_t* msg); + +// subscribe +HV_EXPORT int mqtt_client_subscribe(mqtt_client_t* cli, + const char* topic, int qos DEFAULT(0)); + +// unsubscribe +HV_EXPORT int mqtt_client_unsubscribe(mqtt_client_t* cli, + const char* topic); + +END_EXTERN_C + +#ifdef __cplusplus + +#include +#include +#include +#include + +namespace hv { + +// @usage examples/mqtt/mqtt_client_test.cpp +class MqttClient { +public: + mqtt_client_t* client; + // callbacks + typedef std::function MqttCallback; + typedef std::function MqttMessageCallback; + MqttCallback onConnect; + MqttCallback onClose; + MqttMessageCallback onMessage; + + MqttClient(hloop_t* loop = NULL) { + client = mqtt_client_new(loop); + } + + ~MqttClient() { + if (client) { + mqtt_client_free(client); + client = NULL; + } + } + + void run() { + mqtt_client_set_callback(client, on_mqtt); + mqtt_client_set_userdata(client, this); + mqtt_client_run(client); + } + + void stop() { + mqtt_client_stop(client); + } + + void setID(const char* id) { + mqtt_client_set_id(client, id); + } + + void setWill(mqtt_message_t* will) { + mqtt_client_set_will(client, will); + } + + void setAuth(const char* username, const char* password) { + mqtt_client_set_auth(client, username, password); + } + + void setPingInterval(int sec) { + client->keepalive = sec; + } + + int lastError() { + return mqtt_client_get_last_error(client); + } + + // SSL/TLS + int setSslCtx(hssl_ctx_t ssl_ctx) { + return mqtt_client_set_ssl_ctx(client, ssl_ctx); + } + int newSslCtx(hssl_ctx_opt_t* opt) { + return mqtt_client_new_ssl_ctx(client, opt); + } + + void setReconnect(reconn_setting_t* reconn) { + mqtt_client_set_reconnect(client, reconn); + } + + void setConnectTimeout(int ms) { + mqtt_client_set_connect_timeout(client, ms); + } + + int connect(const char* host, int port = DEFAULT_MQTT_PORT, int ssl = 0) { + return mqtt_client_connect(client, host, port, ssl); + } + + int reconnect() { + return mqtt_client_reconnect(client); + } + + int disconnect() { + return mqtt_client_disconnect(client); + } + + bool isConnected() { + return mqtt_client_is_connected(client); + } + + int publish(mqtt_message_t* msg, MqttCallback ack_cb = NULL) { + int mid = mqtt_client_publish(client, msg); + if (msg->qos > 0 && mid >= 0 && ack_cb) { + setAckCallback(mid, ack_cb); + } + return mid; + } + + int publish(const std::string& topic, const std::string& payload, int qos = 0, int retain = 0, MqttCallback ack_cb = NULL) { + mqtt_message_t msg; + memset(&msg, 0, sizeof(msg)); + msg.topic_len = topic.size(); + msg.topic = topic.c_str(); + msg.payload_len = payload.size(); + msg.payload = payload.c_str(); + msg.qos = qos; + msg.retain = retain; + return publish(&msg, ack_cb); + } + + int subscribe(const char* topic, int qos = 0, MqttCallback ack_cb = NULL) { + int mid = mqtt_client_subscribe(client, topic, qos); + if (qos > 0 && mid >= 0 && ack_cb) { + setAckCallback(mid, ack_cb); + } + return mid; + } + + int unsubscribe(const char* topic, MqttCallback ack_cb = NULL) { + int mid = mqtt_client_unsubscribe(client, topic); + if (mid >= 0 && ack_cb) { + setAckCallback(mid, ack_cb); + } + return mid; + } + +protected: + void setAckCallback(int mid, MqttCallback cb) { + ack_cbs_mutex.lock(); + ack_cbs[mid] = std::move(cb); + ack_cbs_mutex.unlock(); + } + + void invokeAckCallback(int mid) { + MqttCallback ack_cb = NULL; + ack_cbs_mutex.lock(); + auto iter = ack_cbs.find(mid); + if (iter != ack_cbs.end()) { + ack_cb = std::move(iter->second); + ack_cbs.erase(iter); + } + ack_cbs_mutex.unlock(); + if (ack_cb) ack_cb(this); + } + + static void on_mqtt(mqtt_client_t* cli, int type) { + MqttClient* client = (MqttClient*)mqtt_client_get_userdata(cli); + // printf("on_mqtt type=%d\n", type); + switch(type) { + case MQTT_TYPE_CONNECT: + // printf("mqtt connected!\n"); + break; + case MQTT_TYPE_DISCONNECT: + // printf("mqtt disconnected!\n"); + if (client->onClose) { + client->onClose(client); + } + break; + case MQTT_TYPE_CONNACK: + // printf("mqtt connack!\n"); + if (client->onConnect) { + client->onConnect(client); + } + break; + case MQTT_TYPE_PUBLISH: + if (client->onMessage) { + client->onMessage(client, &cli->message); + } + break; + case MQTT_TYPE_PUBACK: /* qos = 1 */ + // printf("mqtt puback mid=%d\n", cli->mid); + client->invokeAckCallback(cli->mid); + break; + case MQTT_TYPE_PUBREC: /* qos = 2 */ + // printf("mqtt pubrec mid=%d\n", cli->mid); + // wait MQTT_TYPE_PUBCOMP + break; + case MQTT_TYPE_PUBCOMP: /* qos = 2 */ + // printf("mqtt pubcomp mid=%d\n", cli->mid); + client->invokeAckCallback(cli->mid); + break; + case MQTT_TYPE_SUBACK: + // printf("mqtt suback mid=%d\n", cli->mid); + client->invokeAckCallback(cli->mid); + break; + case MQTT_TYPE_UNSUBACK: + // printf("mqtt unsuback mid=%d\n", cli->mid); + client->invokeAckCallback(cli->mid); + break; + default: + break; + } + } + +private: + // mid => ack callback + std::map ack_cbs; + std::mutex ack_cbs_mutex; +}; + +} +#endif + +#endif // HV_MQTT_CLIENT_H_ diff --git a/ww/libhv/mqtt/mqtt_protocol.c b/ww/libhv/mqtt/mqtt_protocol.c new file mode 100644 index 00000000..f5560979 --- /dev/null +++ b/ww/libhv/mqtt/mqtt_protocol.c @@ -0,0 +1,22 @@ +#include "mqtt_protocol.h" +#include "hmath.h" + +int mqtt_head_pack(mqtt_head_t* head, unsigned char buf[]) { + buf[0] = (head->type << 4) | + (head->dup << 3) | + (head->qos << 1) | + (head->retain); + int bytes = varint_encode(head->length, buf + 1); + return 1 + bytes; +} + +int mqtt_head_unpack(mqtt_head_t* head, const unsigned char* buf, int len) { + head->type = (buf[0] >> 4) & 0x0F; + head->dup = (buf[0] >> 3) & 0x01; + head->qos = (buf[0] >> 1) & 0x03; + head->retain = buf[0] & 0x01; + int bytes = len - 1; + head->length = varint_decode(buf + 1, &bytes); + if (bytes <= 0) return bytes; + return 1 + bytes; +} diff --git a/ww/libhv/mqtt/mqtt_protocol.h b/ww/libhv/mqtt/mqtt_protocol.h new file mode 100644 index 00000000..57343e14 --- /dev/null +++ b/ww/libhv/mqtt/mqtt_protocol.h @@ -0,0 +1,82 @@ +#ifndef HV_MQTT_PROTOCOL_H_ +#define HV_MQTT_PROTOCOL_H_ + +#include "hexport.h" + +#define DEFAULT_MQTT_PORT 1883 + +#define MQTT_PROTOCOL_V31 3 +#define MQTT_PROTOCOL_V311 4 +#define MQTT_PROTOCOL_V5 5 // Not yet supproted + +#define MQTT_PROTOCOL_NAME "MQTT" +#define MQTT_PROTOCOL_NAME_v31 "MQIsdp" + +/* + * connect flags + * 0 1 2 3-4 5 6 7 + * reserved clean_session has_will will_qos will_retain has_password has_username + */ +#define MQTT_CONN_CLEAN_SESSION 0x02 +#define MQTT_CONN_HAS_WILL 0x04 +#define MQTT_CONN_WILL_RETAIN 0x20 +#define MQTT_CONN_HAS_PASSWORD 0x40 +#define MQTT_CONN_HAS_USERNAME 0x80 + +typedef enum { + MQTT_TYPE_CONNECT = 1, + MQTT_TYPE_CONNACK = 2, + MQTT_TYPE_PUBLISH = 3, + MQTT_TYPE_PUBACK = 4, + MQTT_TYPE_PUBREC = 5, + MQTT_TYPE_PUBREL = 6, + MQTT_TYPE_PUBCOMP = 7, + MQTT_TYPE_SUBSCRIBE = 8, + MQTT_TYPE_SUBACK = 9, + MQTT_TYPE_UNSUBSCRIBE = 10, + MQTT_TYPE_UNSUBACK = 11, + MQTT_TYPE_PINGREQ = 12, + MQTT_TYPE_PINGRESP = 13, + MQTT_TYPE_DISCONNECT = 14, +} mqtt_type_e; + +typedef enum { + MQTT_CONNACK_ACCEPTED = 0, + MQTT_CONNACK_REFUSED_PROTOCOL_VERSION = 1, + MQTT_CONNACK_REFUSED_IDENTIFIER_REJECTED = 2, + MQTT_CONNACK_REFUSED_SERVER_UNAVAILABLE = 3, + MQTT_CONNACK_REFUSED_BAD_USERNAME_PASSWORD = 4, + MQTT_CONNACK_REFUSED_NOT_AUTHORIZED = 5, +} mqtt_connack_e; + +typedef struct mqtt_head_s { + unsigned char type: 4; + unsigned char dup: 1; + unsigned char qos: 2; + unsigned char retain: 1; + unsigned int length; +} mqtt_head_t; + +typedef struct mqtt_message_s { + unsigned int topic_len; + const char* topic; + unsigned int payload_len; + const char* payload; + unsigned char qos; + unsigned char retain; +} mqtt_message_t; + +BEGIN_EXTERN_C + +#define DEFAULT_MQTT_PACKAGE_MAX_LENGTH (1 << 28) // 256M +HV_INLINE int mqtt_estimate_length(mqtt_head_t* head) { + // 28 bits => 4*7 bits varint + return 1 + 4 + head->length; +} + +HV_EXPORT int mqtt_head_pack(mqtt_head_t* head, unsigned char buf[]); +HV_EXPORT int mqtt_head_unpack(mqtt_head_t* head, const unsigned char* buf, int len); + +END_EXTERN_C + +#endif // HV_MQTT_PROTOCOL_H_ diff --git a/ww/libhv/protocol/README.md b/ww/libhv/protocol/README.md new file mode 100644 index 00000000..3d51514b --- /dev/null +++ b/ww/libhv/protocol/README.md @@ -0,0 +1,10 @@ +## 目录结构 + +``` +. +├── dns.h DNS协议 +├── ftp.h FTP协议 +├── icmp.h ICMP协议 +└── smtp.h SMTP协议 + +``` diff --git a/ww/libhv/protocol/dns.c b/ww/libhv/protocol/dns.c new file mode 100644 index 00000000..4fa55d31 --- /dev/null +++ b/ww/libhv/protocol/dns.c @@ -0,0 +1,336 @@ +#include "dns.h" + +#include "hdef.h" +#include "hsocket.h" +#include "herr.h" + +void dns_free(dns_t* dns) { + SAFE_FREE(dns->questions); + SAFE_FREE(dns->answers); + SAFE_FREE(dns->authorities); + SAFE_FREE(dns->addtionals); +} + +// www.example.com => 3www7example3com +int dns_name_encode(const char* domain, char* buf) { + const char* p = domain; + char* plen = buf++; + int buflen = 1; + int len = 0; + while (*p != '\0') { + if (*p != '.') { + ++len; + *buf = *p; + } + else { + *plen = len; + //printf("len=%d\n", len); + plen = buf; + len = 0; + } + ++p; + ++buf; + ++buflen; + } + *plen = len; + //printf("len=%d\n", len); + *buf = '\0'; + if (len != 0) { + ++buflen; // include last '\0' + } + return buflen; +} + +// 3www7example3com => www.example.com +int dns_name_decode(const char* buf, char* domain) { + const char* p = buf; + int len = *p++; + //printf("len=%d\n", len); + int buflen = 1; + while (*p != '\0') { + if (len-- == 0) { + len = *p; + //printf("len=%d\n", len); + *domain = '.'; + } + else { + *domain = *p; + } + ++p; + ++domain; + ++buflen; + } + *domain = '\0'; + ++buflen; // include last '\0' + return buflen; +} + +int dns_rr_pack(dns_rr_t* rr, char* buf, int len) { + char* p = buf; + char encoded_name[256]; + int encoded_namelen = dns_name_encode(rr->name, encoded_name); + int packetlen = encoded_namelen + 2 + 2 + (rr->data ? (4+2+rr->datalen) : 0); + if (len < packetlen) { + return -1; + } + + memcpy(p, encoded_name, encoded_namelen); + p += encoded_namelen; + uint16_t* pushort = (uint16_t*)p; + *pushort = htons(rr->rtype); + p += 2; + pushort = (uint16_t*)p; + *pushort = htons(rr->rclass); + p += 2; + + // ... + if (rr->datalen && rr->data) { + uint32_t* puint = (uint32_t*)p; + *puint = htonl(rr->ttl); + p += 4; + pushort = (uint16_t*)p; + *pushort = htons(rr->datalen); + p += 2; + memcpy(p, rr->data, rr->datalen); + p += rr->datalen; + } + return packetlen; +} + +int dns_rr_unpack(char* buf, int len, dns_rr_t* rr, int is_question) { + char* p = buf; + int off = 0; + int namelen = 0; + if (*(uint8_t*)p >= 192) { + // name off, we ignore + namelen = 2; + //uint16_t nameoff = (*(uint8_t*)p - 192) * 256 + *(uint8_t*)(p+1); + } + else { + namelen = dns_name_decode(buf, rr->name); + } + if (namelen < 0) return -1; + p += namelen; + off += namelen; + + if (len < off + 4) return -1; + uint16_t* pushort = (uint16_t*)p; + rr->rtype = ntohs(*pushort); + p += 2; + pushort = (uint16_t*)p; + rr->rclass = ntohs(*pushort); + p += 2; + off += 4; + + if (!is_question) { + if (len < off + 6) return -1; + uint32_t* puint = (uint32_t*)p; + rr->ttl = ntohl(*puint); + p += 4; + pushort = (uint16_t*)p; + rr->datalen = ntohs(*pushort); + p += 2; + off += 6; + if (len < off + rr->datalen) return -1; + rr->data = p; + p += rr->datalen; + off += rr->datalen; + } + return off; +} + +int dns_pack(dns_t* dns, char* buf, int len) { + if (len < sizeof(dnshdr_t)) return -1; + int off = 0; + dnshdr_t* hdr = &dns->hdr; + dnshdr_t htonhdr = dns->hdr; + htonhdr.transaction_id = htons(hdr->transaction_id); + htonhdr.nquestion = htons(hdr->nquestion); + htonhdr.nanswer = htons(hdr->nanswer); + htonhdr.nauthority = htons(hdr->nauthority); + htonhdr.naddtional = htons(hdr->naddtional); + memcpy(buf, &htonhdr, sizeof(dnshdr_t)); + off += sizeof(dnshdr_t); + int i; + for (i = 0; i < hdr->nquestion; ++i) { + int packetlen = dns_rr_pack(dns->questions+i, buf+off, len-off); + if (packetlen < 0) return -1; + off += packetlen; + } + for (i = 0; i < hdr->nanswer; ++i) { + int packetlen = dns_rr_pack(dns->answers+i, buf+off, len-off); + if (packetlen < 0) return -1; + off += packetlen; + } + for (i = 0; i < hdr->nauthority; ++i) { + int packetlen = dns_rr_pack(dns->authorities+i, buf+off, len-off); + if (packetlen < 0) return -1; + off += packetlen; + } + for (i = 0; i < hdr->naddtional; ++i) { + int packetlen = dns_rr_pack(dns->addtionals+i, buf+off, len-off); + if (packetlen < 0) return -1; + off += packetlen; + } + return off; +} + +int dns_unpack(char* buf, int len, dns_t* dns) { + memset(dns, 0, sizeof(dns_t)); + if (len < sizeof(dnshdr_t)) return -1; + int off = 0; + dnshdr_t* hdr = &dns->hdr; + memcpy(hdr, buf, sizeof(dnshdr_t)); + off += sizeof(dnshdr_t); + hdr->transaction_id = ntohs(hdr->transaction_id); + hdr->nquestion = ntohs(hdr->nquestion); + hdr->nanswer = ntohs(hdr->nanswer); + hdr->nauthority = ntohs(hdr->nauthority); + hdr->naddtional = ntohs(hdr->naddtional); + int i; + if (hdr->nquestion) { + int bytes = hdr->nquestion * sizeof(dns_rr_t); + SAFE_ALLOC(dns->questions, bytes); + for (i = 0; i < hdr->nquestion; ++i) { + int packetlen = dns_rr_unpack(buf+off, len-off, dns->questions+i, 1); + if (packetlen < 0) return -1; + off += packetlen; + } + } + if (hdr->nanswer) { + int bytes = hdr->nanswer * sizeof(dns_rr_t); + SAFE_ALLOC(dns->answers, bytes); + for (i = 0; i < hdr->nanswer; ++i) { + int packetlen = dns_rr_unpack(buf+off, len-off, dns->answers+i, 0); + if (packetlen < 0) return -1; + off += packetlen; + } + } + if (hdr->nauthority) { + int bytes = hdr->nauthority * sizeof(dns_rr_t); + SAFE_ALLOC(dns->authorities, bytes); + for (i = 0; i < hdr->nauthority; ++i) { + int packetlen = dns_rr_unpack(buf+off, len-off, dns->authorities+i, 0); + if (packetlen < 0) return -1; + off += packetlen; + } + } + if (hdr->naddtional) { + int bytes = hdr->naddtional * sizeof(dns_rr_t); + SAFE_ALLOC(dns->addtionals, bytes); + for (i = 0; i < hdr->naddtional; ++i) { + int packetlen = dns_rr_unpack(buf+off, len-off, dns->addtionals+i, 0); + if (packetlen < 0) return -1; + off += packetlen; + } + } + return off; +} + +// dns_pack -> sendto -> recvfrom -> dns_unpack +int dns_query(dns_t* query, dns_t* response, const char* nameserver) { + char buf[1024]; + int buflen = sizeof(buf); + buflen = dns_pack(query, buf, buflen); + if (buflen < 0) { + return buflen; + } +#ifdef OS_WIN + WSAInit(); +#endif + int sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if (sockfd < 0) { + perror("socket"); + return ERR_SOCKET; + } + so_sndtimeo(sockfd, 5000); + so_rcvtimeo(sockfd, 5000); + int ret = 0; + int nsend, nrecv; + int nparse; + struct sockaddr_in addr; + socklen_t addrlen = sizeof(addr); + memset(&addr, 0, addrlen); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr(nameserver); + addr.sin_port = htons(DNS_PORT); + nsend = sendto(sockfd, buf, buflen, 0, (struct sockaddr*)&addr, addrlen); + if (nsend != buflen) { + ret = ERR_SENDTO; + goto error; + } + nrecv = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr*)&addr, &addrlen); + if (nrecv <= 0) { + ret = ERR_RECVFROM; + goto error; + } + + nparse = dns_unpack(buf, nrecv, response); + if (nparse != nrecv) { + ret = -ERR_INVALID_PACKAGE; + goto error; + } + +error: + if (sockfd != INVALID_SOCKET) { + closesocket(sockfd); + } + return ret; +} + +int nslookup(const char* domain, uint32_t* addrs, int naddr, const char* nameserver) { + dns_t query; + memset(&query, 0, sizeof(query)); + query.hdr.transaction_id = getpid(); + query.hdr.qr = DNS_QUERY; + query.hdr.rd = 1; + query.hdr.nquestion = 1; + + dns_rr_t question; + memset(&question, 0, sizeof(question)); + strncpy(question.name, domain, sizeof(question.name)); + question.rtype = DNS_TYPE_A; + question.rclass = DNS_CLASS_IN; + + query.questions = &question; + + dns_t resp; + memset(&resp, 0, sizeof(resp)); + int ret = dns_query(&query, &resp, nameserver); + if (ret != 0) { + return ret; + } + + dns_rr_t* rr = resp.answers; + int addr_cnt = 0; + if (resp.hdr.transaction_id != query.hdr.transaction_id || + resp.hdr.qr != DNS_RESPONSE || + resp.hdr.rcode != 0) { + ret = -ERR_MISMATCH; + goto end; + } + + if (resp.hdr.nanswer == 0) { + ret = 0; + goto end; + } + + for (int i = 0; i < resp.hdr.nanswer; ++i, ++rr) { + if (rr->rtype == DNS_TYPE_A) { + if (addr_cnt < naddr && rr->datalen == 4) { + memcpy(addrs+addr_cnt, rr->data, 4); + } + ++addr_cnt; + } + /* + else if (rr->rtype == DNS_TYPE_CNAME) { + char name[256]; + dns_name_decode(rr->data, name); + } + */ + } + ret = addr_cnt; +end: + dns_free(&resp); + return ret; +} diff --git a/ww/libhv/protocol/dns.h b/ww/libhv/protocol/dns.h new file mode 100644 index 00000000..2bb9d435 --- /dev/null +++ b/ww/libhv/protocol/dns.h @@ -0,0 +1,105 @@ +#ifndef HV_DNS_H_ +#define HV_DNS_H_ + +#include "hexport.h" +#include "hplatform.h" + +#define DNS_PORT 53 + +#define DNS_QUERY 0 +#define DNS_RESPONSE 1 + +#define DNS_TYPE_A 1 // ipv4 +#define DNS_TYPE_NS 2 +#define DNS_TYPE_CNAME 5 +#define DNS_TYPE_SOA 6 +#define DNS_TYPE_WKS 11 +#define DNS_TYPE_PTR 12 +#define DNS_TYPE_HINFO 13 +#define DNS_TYPE_MX 15 +#define DNS_TYPE_AAAA 28 // ipv6 +#define DNS_TYPE_AXFR 252 +#define DNS_TYPE_ANY 255 + +#define DNS_CLASS_IN 1 + +#define DNS_NAME_MAXLEN 256 + +// sizeof(dnshdr_t) = 12 +typedef struct dnshdr_s { + uint16_t transaction_id; + // flags +#if BYTE_ORDER == LITTLE_ENDIAN + uint8_t rd:1; + uint8_t tc:1; + uint8_t aa:1; + uint8_t opcode:4; + uint8_t qr:1; + + uint8_t rcode:4; + uint8_t cd:1; + uint8_t ad:1; + uint8_t res:1; + uint8_t ra:1; +#elif BYTE_ORDER == BIG_ENDIAN + uint8_t qr:1; // DNS_QUERY or DNS_RESPONSE + uint8_t opcode:4; + uint8_t aa:1; // authoritative + uint8_t tc:1; // truncated + uint8_t rd:1; // recursion desired + + uint8_t ra:1; // recursion available + uint8_t res:1; // reserved + uint8_t ad:1; // authenticated data + uint8_t cd:1; // checking disable + uint8_t rcode:4; +#else +#error "BYTE_ORDER undefined!" +#endif + uint16_t nquestion; + uint16_t nanswer; + uint16_t nauthority; + uint16_t naddtional; +} dnshdr_t; + +typedef struct dns_rr_s { + char name[DNS_NAME_MAXLEN]; // original domain, such as www.example.com + uint16_t rtype; + uint16_t rclass; + uint32_t ttl; + uint16_t datalen; + char* data; +} dns_rr_t; + +typedef struct dns_s { + dnshdr_t hdr; + dns_rr_t* questions; + dns_rr_t* answers; + dns_rr_t* authorities; + dns_rr_t* addtionals; +} dns_t; + +BEGIN_EXTERN_C + +// www.example.com => 3www7example3com +HV_EXPORT int dns_name_encode(const char* domain, char* buf); +// 3www7example3com => www.example.com +HV_EXPORT int dns_name_decode(const char* buf, char* domain); + +HV_EXPORT int dns_rr_pack(dns_rr_t* rr, char* buf, int len); +HV_EXPORT int dns_rr_unpack(char* buf, int len, dns_rr_t* rr, int is_question); + +HV_EXPORT int dns_pack(dns_t* dns, char* buf, int len); +HV_EXPORT int dns_unpack(char* buf, int len, dns_t* dns); +// NOTE: free dns->rrs +HV_EXPORT void dns_free(dns_t* dns); + +// dns_pack -> sendto -> recvfrom -> dns_unpack +HV_EXPORT int dns_query(dns_t* query, dns_t* response, const char* nameserver DEFAULT("127.0.1.1")); + +// domain -> dns_t query; -> dns_query -> dns_t response; -> addrs +HV_EXPORT int nslookup(const char* domain, uint32_t* addrs, int naddr, const char* nameserver DEFAULT("127.0.1.1")); + +END_EXTERN_C + +#endif // HV_DNS_H_ diff --git a/ww/libhv/protocol/ftp.c b/ww/libhv/protocol/ftp.c new file mode 100644 index 00000000..7dcdba7b --- /dev/null +++ b/ww/libhv/protocol/ftp.c @@ -0,0 +1,247 @@ +#include "ftp.h" +#include "hsocket.h" +#include "herr.h" + +const char* ftp_command_str(enum ftp_command cmd) { + switch (cmd) { +#define X(name) case FTP_##name: return #name; + FTP_COMMAND_MAP(X) +#undef X + default: return ""; + } +} + +const char* ftp_status_str(enum ftp_status status) { + switch (status) { +#define XXX(code, name, string) case FTP_STATUS_##name: return #string; + FTP_STATUS_MAP(XXX) +#undef XXX + default: return ""; + } +} + +int ftp_connect(ftp_handle_t* hftp, const char* host, int port) { + int sockfd = ConnectTimeout(host, port, DEFAULT_CONNECT_TIMEOUT); + if (sockfd < 0) { + return sockfd; + } + so_sndtimeo(sockfd, 5000); + so_rcvtimeo(sockfd, 5000); + hftp->sockfd = sockfd; + int ret = 0; + int status_code = 0; + memset(hftp->recvbuf, 0, FTP_RECV_BUFSIZE); + int nrecv = recv(sockfd, hftp->recvbuf, FTP_RECV_BUFSIZE, 0); + if (nrecv <= 0) { + ret = ERR_RECV; + goto error; + } + status_code = atoi(hftp->recvbuf); + if (status_code != FTP_STATUS_READY) { + ret = status_code; + goto error; + } + return 0; + +error: + closesocket(sockfd); + return ret; +} + +int ftp_login(ftp_handle_t* hftp, const char* username, const char* password) { + int status_code = ftp_exec(hftp, "USER", username); + status_code = ftp_exec(hftp, "PASS", password); + return status_code == FTP_STATUS_LOGIN_OK ? 0 : status_code; +} + +int ftp_quit(ftp_handle_t* hftp) { + ftp_exec(hftp, "QUIT", NULL); + closesocket(hftp->sockfd); + return 0; +} + +int ftp_exec(ftp_handle_t* hftp, const char* cmd, const char* param) { + char buf[1024]; + int len = 0; + if (param && *param) { + len = snprintf(buf, sizeof(buf), "%s %s\r\n", cmd, param); + } + else { + len = snprintf(buf, sizeof(buf), "%s\r\n", cmd); + } + int nsend, nrecv; + int ret = 0; + nsend = send(hftp->sockfd, buf, len, 0); + if (nsend != len) { + ret = ERR_SEND; + goto error; + } + //printf("> %s", buf); + memset(hftp->recvbuf, 0, FTP_RECV_BUFSIZE); + nrecv = recv(hftp->sockfd, hftp->recvbuf, FTP_RECV_BUFSIZE, 0); + if (nrecv <= 0) { + ret = ERR_RECV; + goto error; + } + //printf("< %s", hftp->recvbuf); + return atoi(hftp->recvbuf); +error: + closesocket(hftp->sockfd); + return ret; +} + +static int ftp_parse_pasv(const char* resp, char* host, int* port) { + // 227 Entering Passive Mode (127,0,0,1,4,51) + const char* str = strchr(resp, '('); + if (str == NULL) { + return ERR_RESPONSE; + } + int arr[6]; + sscanf(str, "(%d,%d,%d,%d,%d,%d)", + &arr[0], &arr[1], &arr[2], &arr[3], &arr[4], &arr[5]); + sprintf(host, "%d.%d.%d.%d", arr[0], arr[1], arr[2], arr[3]); + *port = arr[4] << 8 | arr[5]; + return 0; +} + +int ftp_download_with_cb(ftp_handle_t* hftp, const char* filepath, ftp_download_cb cb) { + int status_code = ftp_exec(hftp, "PASV", NULL); + if (status_code != FTP_STATUS_PASV) { + return status_code; + } + char host[64]; + int port = 0; + int ret = ftp_parse_pasv(hftp->recvbuf, host, &port); + if (ret != 0) { + return ret; + } + //ftp_exec(hftp, "RETR", filepath); + char request[1024]; + int len = snprintf(request, sizeof(request), "RETR %s\r\n", filepath); + int nsend = send(hftp->sockfd, request, len, 0); + if (nsend != len) { + closesocket(hftp->sockfd); + return ERR_SEND; + } + //printf("> %s", request); + int sockfd = ConnectTimeout(host, port, DEFAULT_CONNECT_TIMEOUT); + if (sockfd < 0) { + return sockfd; + } + int nrecv = recv(hftp->sockfd, hftp->recvbuf, FTP_RECV_BUFSIZE, 0); + if (nrecv <= 0) { + closesocket(hftp->sockfd); + return ERR_RECV; + } + //printf("< %s", hftp->recvbuf); + { + // you can create thread to recv data + char recvbuf[1024]; + int ntotal = 0; + while (1) { + nrecv = recv(sockfd, recvbuf, sizeof(recvbuf), 0); + if (cb) { + cb(hftp, recvbuf, nrecv); + } + if (nrecv <= 0) break; + ntotal += nrecv; + } + } + closesocket(sockfd); + nrecv = recv(hftp->sockfd, hftp->recvbuf, FTP_RECV_BUFSIZE, 0); + if (nrecv <= 0) { + closesocket(hftp->sockfd); + return ERR_RECV; + } + //printf("< %s", hftp->recvbuf); + status_code = atoi(hftp->recvbuf); + return status_code == FTP_STATUS_TRANSFER_COMPLETE ? 0 : status_code; +} + +// local => remote +int ftp_upload(ftp_handle_t* hftp, const char* local_filepath, const char* remote_filepath) { + int status_code = ftp_exec(hftp, "PASV", NULL); + if (status_code != FTP_STATUS_PASV) { + return status_code; + } + char host[64]; + int port = 0; + int ret = ftp_parse_pasv(hftp->recvbuf, host, &port); + if (ret != 0) { + return ret; + } + //ftp_exec(hftp, "STOR", remote_filepath); + char request[1024]; + int len = snprintf(request, sizeof(request), "STOR %s\r\n", remote_filepath); + int nsend = send(hftp->sockfd, request, len, 0); + if (nsend != len) { + closesocket(hftp->sockfd); + return ERR_SEND; + } + //printf("> %s", request); + int sockfd = ConnectTimeout(host, port, DEFAULT_CONNECT_TIMEOUT); + if (sockfd < 0) { + return sockfd; + } + int nrecv = recv(hftp->sockfd, hftp->recvbuf, FTP_RECV_BUFSIZE, 0); + if (nrecv <= 0) { + closesocket(hftp->sockfd); + return ERR_RECV; + } + //printf("< %s", hftp->recvbuf); + { + // you can create thread to send data + FILE* fp = fopen(local_filepath, "rb"); + if (fp == NULL) { + closesocket(sockfd); + return ERR_OPEN_FILE; + } + char sendbuf[1024]; + int nread, nsend; + int ntotal = 0; + while (1) { + nread = fread(sendbuf, 1, sizeof(sendbuf), fp); + if (nread == 0) break; + nsend = send(sockfd, sendbuf, nread, 0); + if (nsend != nread) break; + ntotal += nsend; + } + fclose(fp); + } + closesocket(sockfd); + nrecv = recv(hftp->sockfd, hftp->recvbuf, FTP_RECV_BUFSIZE, 0); + if (nrecv <= 0) { + closesocket(hftp->sockfd); + return ERR_RECV; + } + //printf("< %s", hftp->recvbuf); + status_code = atoi(hftp->recvbuf); + return status_code == FTP_STATUS_TRANSFER_COMPLETE ? 0 : status_code; +} + +static int s_ftp_download_cb(ftp_handle_t* hftp, char* buf, int len) { + FILE* fp = (FILE*)hftp->userdata; + if (fp == NULL) return -1; + if (len <= 0) { + fclose(fp); + hftp->userdata = NULL; + return 0; + } + return fwrite(buf, 1, len, fp); +} + +// remote => local +int ftp_download(ftp_handle_t* hftp, const char* remote_filepath, const char* local_filepath) { + FILE* fp = fopen(local_filepath, "wb"); + if (fp == NULL) { + return ERR_OPEN_FILE; + } + hftp->userdata = (void*)fp; + int ret = ftp_download_with_cb(hftp, remote_filepath, s_ftp_download_cb); + // ensure fclose + if (hftp->userdata != NULL) { + fclose(fp); + hftp->userdata = NULL; + } + return ret; +} diff --git a/ww/libhv/protocol/ftp.h b/ww/libhv/protocol/ftp.h new file mode 100644 index 00000000..d1b60ca6 --- /dev/null +++ b/ww/libhv/protocol/ftp.h @@ -0,0 +1,96 @@ +#ifndef HV_FTP_H_ +#define HV_FTP_H_ + +#include "hexport.h" + +#define FTP_COMMAND_PORT 21 +#define FTP_DATA_PORT 20 + +// ftp_command +// X(name) +#define FTP_COMMAND_MAP(X) \ + X(HELP) \ + X(USER) \ + X(PASS) \ + X(PWD) \ + X(CWD) \ + X(CDUP) \ + X(MKD) \ + X(RMD) \ + X(STAT) \ + X(SIZE) \ + X(DELE) \ + X(RNFR) \ + X(RNTO) \ + X(PORT) \ + X(PASV) \ + X(LIST) \ + X(NLST) \ + X(APPE) \ + X(RETR) \ + X(STOR) \ + X(QUIT) \ + +enum ftp_command { +#define X(name) FTP_##name, + FTP_COMMAND_MAP(X) +#undef X +}; + +// ftp_status +// XXX(code, name, string) +#define FTP_STATUS_MAP(XXX) \ + XXX(220, READY, Ready) \ + XXX(221, BYE, Bye) \ + XXX(226, TRANSFER_COMPLETE, Transfer complete) \ + XXX(227, PASV, Entering Passive Mode) \ + XXX(331, PASS, Password required) \ + XXX(230, LOGIN_OK, Login OK) \ + XXX(250, OK, OK) \ + XXX(500, BAD_SYNTAX, Bad syntax) \ + XXX(530, NOT_LOGIN, Not login) \ + +enum ftp_status { +#define XXX(code, name, string) FTP_STATUS_##name = code, + FTP_STATUS_MAP(XXX) +#undef XXX +}; + +// more friendly macros +#define FTP_MKDIR FTP_MKD +#define FTP_RMDIR FTP_RMD +#define FTP_APPEND FTP_APPE +#define FTP_REMOVE FTP_DELE +#define FTP_DOWNLOAD FTP_RETR +#define FTP_UPLOAD FTP_STOR + +#define FTP_RECV_BUFSIZE 8192 + +typedef struct ftp_handle_s { + int sockfd; + char recvbuf[FTP_RECV_BUFSIZE]; + void* userdata; +} ftp_handle_t; + +BEGIN_EXTERN_C + +HV_EXPORT const char* ftp_command_str(enum ftp_command cmd); +HV_EXPORT const char* ftp_status_str(enum ftp_status status); + +HV_EXPORT int ftp_connect(ftp_handle_t* hftp, const char* host, int port); +HV_EXPORT int ftp_login(ftp_handle_t* hftp, const char* username, const char* password); +HV_EXPORT int ftp_quit(ftp_handle_t* hftp); + +HV_EXPORT int ftp_exec(ftp_handle_t* hftp, const char* cmd, const char* param); + +// local => remote +HV_EXPORT int ftp_upload(ftp_handle_t* hftp, const char* local_filepath, const char* remote_filepath); +// remote => local +HV_EXPORT int ftp_download(ftp_handle_t* hftp, const char* remote_filepath, const char* local_filepath); + +typedef int (*ftp_download_cb)(ftp_handle_t* hftp, char* buf, int len); +HV_EXPORT int ftp_download_with_cb(ftp_handle_t* hftp, const char* filepath, ftp_download_cb cb); + +END_EXTERN_C + +#endif // HV_FTP_H_ diff --git a/ww/libhv/protocol/icmp.c b/ww/libhv/protocol/icmp.c new file mode 100644 index 00000000..864a8390 --- /dev/null +++ b/ww/libhv/protocol/icmp.c @@ -0,0 +1,125 @@ +#include "icmp.h" + +#include "netinet.h" +#include "hdef.h" +#include "hsocket.h" +#include "htime.h" + +#define PING_TIMEOUT 1000 // ms +int ping(const char* host, int cnt) { + static uint16_t seq = 0; + uint16_t pid16 = (uint16_t)getpid(); + char ip[64] = {0}; + uint32_t start_tick, end_tick; + uint64_t start_hrtime, end_hrtime; + int timeout = 0; + int sendbytes = 64; + char sendbuf[64]; + char recvbuf[128]; // iphdr + icmp = 84 at least + icmp_t* icmp_req = (icmp_t*)sendbuf; + iphdr_t* ipheader = (iphdr_t*)recvbuf; + icmp_t* icmp_res; + // ping stat + int send_cnt = 0; + int recv_cnt = 0; + int ok_cnt = 0; + float rtt, min_rtt, max_rtt, total_rtt; + rtt = max_rtt = total_rtt = 0.0f; + min_rtt = 1000000.0f; + //min_rtt = MIN(rtt, min_rtt); + //max_rtt = MAX(rtt, max_rtt); + // gethostbyname -> socket -> setsockopt -> sendto -> recvfrom -> closesocket + sockaddr_u peeraddr; + socklen_t addrlen = sizeof(peeraddr); + memset(&peeraddr, 0, addrlen); + int ret = ResolveAddr(host, &peeraddr); + if (ret != 0) return ret; + sockaddr_ip(&peeraddr, ip, sizeof(ip)); + int sockfd = socket(peeraddr.sa.sa_family, SOCK_RAW, IPPROTO_ICMP); + if (sockfd < 0) { + perror("socket"); + if (errno == EPERM) { + fprintf(stderr, "please use root or sudo to create a raw socket.\n"); + } + return -socket_errno(); + } + + timeout = PING_TIMEOUT; + ret = so_sndtimeo(sockfd, timeout); + if (ret < 0) { + perror("setsockopt"); + goto error; + } + timeout = PING_TIMEOUT; + ret = so_rcvtimeo(sockfd, timeout); + if (ret < 0) { + perror("setsockopt"); + goto error; + } + + icmp_req->icmp_type = ICMP_ECHO; + icmp_req->icmp_code = 0; + icmp_req->icmp_id = pid16; + for (int i = 0; i < sendbytes - sizeof(icmphdr_t); ++i) { + icmp_req->icmp_data[i] = i; + } + start_tick = gettick_ms(); + while (cnt-- > 0) { + // NOTE: checksum + icmp_req->icmp_seq = ++seq; + icmp_req->icmp_cksum = 0; + icmp_req->icmp_cksum = checksum((uint8_t*)icmp_req, sendbytes); + start_hrtime = gethrtime_us(); + addrlen = sockaddr_len(&peeraddr); + int nsend = sendto(sockfd, sendbuf, sendbytes, 0, &peeraddr.sa, addrlen); + if (nsend < 0) { + perror("sendto"); + continue; + } + ++send_cnt; + addrlen = sizeof(peeraddr); + int nrecv = recvfrom(sockfd, recvbuf, sizeof(recvbuf), 0, &peeraddr.sa, &addrlen); + if (nrecv < 0) { + perror("recvfrom"); + continue; + } + ++recv_cnt; + end_hrtime = gethrtime_us(); + // check valid + bool valid = false; + int iphdr_len = ipheader->ihl * 4; + int icmp_len = nrecv - iphdr_len; + if (icmp_len == sendbytes) { + icmp_res = (icmp_t*)(recvbuf + ipheader->ihl*4); + if (icmp_res->icmp_type == ICMP_ECHOREPLY && + icmp_res->icmp_id == pid16 && + icmp_res->icmp_seq == seq) { + valid = true; + } + } + if (valid == false) { + printd("recv invalid icmp packet!\n"); + continue; + } + rtt = (end_hrtime-start_hrtime) / 1000.0f; + min_rtt = MIN(rtt, min_rtt); + max_rtt = MAX(rtt, max_rtt); + total_rtt += rtt; + printd("%d bytes from %s: icmp_seq=%u ttl=%u time=%.1f ms\n", icmp_len, ip, seq, ipheader->ttl, rtt); + fflush(stdout); + ++ok_cnt; + if (cnt > 0) hv_sleep(1); // sleep a while, then agian + } + end_tick = gettick_ms(); + printd("--- %s ping statistics ---\n", host); + printd("%d packets transmitted, %d received, %d%% packet loss, time %d ms\n", + send_cnt, recv_cnt, (send_cnt-recv_cnt)*100/(send_cnt==0?1:send_cnt), end_tick-start_tick); + printd("rtt min/avg/max = %.3f/%.3f/%.3f ms\n", + min_rtt, total_rtt/(ok_cnt==0?1:ok_cnt), max_rtt); + + closesocket(sockfd); + return ok_cnt; +error: + closesocket(sockfd); + return socket_errno() > 0 ? -socket_errno() : -1; +} diff --git a/ww/libhv/protocol/icmp.h b/ww/libhv/protocol/icmp.h new file mode 100644 index 00000000..afd8349e --- /dev/null +++ b/ww/libhv/protocol/icmp.h @@ -0,0 +1,15 @@ +#ifndef HV_ICMP_H_ +#define HV_ICMP_H_ + +#include "hexport.h" + +BEGIN_EXTERN_C + +// @param cnt: ping count +// @return: ok count +// @note: printd $CC -DPRINT_DEBUG +HV_EXPORT int ping(const char* host, int cnt DEFAULT(4)); + +END_EXTERN_C + +#endif // HV_ICMP_H_ diff --git a/ww/libhv/protocol/smtp.c b/ww/libhv/protocol/smtp.c new file mode 100644 index 00000000..d365559d --- /dev/null +++ b/ww/libhv/protocol/smtp.c @@ -0,0 +1,256 @@ +#include "smtp.h" + +#include "hsocket.h" +#include "herr.h" +#include "base64.h" + +const char* smtp_command_str(enum smtp_command cmd) { + switch (cmd) { +#define XX(name, string) case SMTP_##name: return #string; + SMTP_COMMAND_MAP(XX) +#undef XX + default: return ""; + } +} + +const char* smtp_status_str(enum smtp_status status) { + switch (status) { +#define XXX(code, name, string) case SMTP_STATUS_##name: return #string; + SMTP_STATUS_MAP(XXX) +#undef XXX + default: return ""; + } +} + +int smtp_build_command(enum smtp_command cmd, const char* param, char* buf, int buflen) { + switch (cmd) { + // unary + case SMTP_DATA: + case SMTP_QUIT: + return snprintf(buf, buflen, "%s\r\n", smtp_command_str(cmd)); + //
+ case SMTP_MAIL: + case SMTP_RCPT: + return snprintf(buf, buflen, "%s <%s>\r\n", smtp_command_str(cmd), param); + default: + return snprintf(buf, buflen, "%s %s\r\n", smtp_command_str(cmd), param); + } +} + +// EHLO => AUTH PLAIN => MAIL => RCPT => DATA => data => EOB => QUIT +int sendmail(const char* smtp_server, + const char* username, + const char* password, + mail_t* mail) { + char buf[1024] = {0}; + int buflen = sizeof(buf); + int cmdlen = 0; + int status_code = 0; + char basic[256]; + int basiclen; + + int sockfd = ConnectTimeout(smtp_server, SMTP_PORT, DEFAULT_CONNECT_TIMEOUT); + if (sockfd < 0) { + return sockfd; + } + so_sndtimeo(sockfd, 5000); + so_rcvtimeo(sockfd, 5000); + + int ret, nsend, nrecv; + nrecv = recv(sockfd, buf, buflen, 0); + if (nrecv <= 0) { + ret = ERR_RECV; + goto error; + } + status_code = atoi(buf); + if (status_code != SMTP_STATUS_READY) { + ret = status_code; + goto error; + } + // EHLO smtp.xxx.com\r\n + cmdlen = smtp_build_command(SMTP_EHLO, smtp_server, buf, buflen); + nsend = send(sockfd, buf, cmdlen, 0); + if (nsend != cmdlen) { + ret = ERR_SEND; + goto error; + } + nrecv = recv(sockfd, buf, buflen, 0); + if (nrecv <= 0) { + ret = ERR_RECV; + goto error; + } + status_code = atoi(buf); + if (status_code != SMTP_STATUS_OK) { + ret = status_code; + goto error; + } + // AUTH PLAIN\r\n + cmdlen = smtp_build_command(SMTP_AUTH, "PLAIN", buf, buflen); + nsend = send(sockfd, buf, cmdlen, 0); + if (nsend != cmdlen) { + ret = ERR_SEND; + goto error; + } + nrecv = recv(sockfd, buf, buflen, 0); + if (nrecv <= 0) { + ret = ERR_RECV; + goto error; + } + status_code = atoi(buf); + if (status_code != SMTP_STATUS_AUTH) { + ret = status_code; + goto error; + } + { + // BASE64 \0username\0password + int usernamelen = strlen(username); + int passwordlen = strlen(password); + basic[0] = '\0'; + memcpy(basic+1, username, usernamelen); + basic[1+usernamelen] = '\0'; + memcpy(basic+1+usernamelen+1, password, passwordlen); + basiclen = 1 + usernamelen + 1 + passwordlen; + } + hv_base64_encode((unsigned char*)basic, basiclen, buf); + cmdlen = BASE64_ENCODE_OUT_SIZE(basiclen); + buf[cmdlen] = '\r'; + buf[cmdlen+1] = '\n'; + cmdlen += 2; + nsend = send(sockfd, buf, cmdlen, 0); + if (nsend != cmdlen) { + ret = ERR_SEND; + goto error; + } + nrecv = recv(sockfd, buf, buflen, 0); + if (nrecv <= 0) { + ret = ERR_RECV; + goto error; + } + status_code = atoi(buf); + if (status_code != SMTP_STATUS_AUTH_SUCCESS) { + ret = status_code; + goto error; + } + // MAIL FROM: \r\n + cmdlen = smtp_build_command(SMTP_MAIL, mail->from, buf, buflen); + nsend = send(sockfd, buf, cmdlen, 0); + if (nsend != cmdlen) { + ret = ERR_SEND; + goto error; + } + nrecv = recv(sockfd, buf, buflen, 0); + if (nrecv <= 0) { + ret = ERR_RECV; + goto error; + } + status_code = atoi(buf); + if (status_code != SMTP_STATUS_OK) { + ret = status_code; + goto error; + } + // RCPT TO: \r\n + cmdlen = smtp_build_command(SMTP_RCPT, mail->to, buf, buflen); + nsend = send(sockfd, buf, cmdlen, 0); + if (nsend != cmdlen) { + ret = ERR_SEND; + goto error; + } + nrecv = recv(sockfd, buf, buflen, 0); + if (nrecv <= 0) { + ret = ERR_RECV; + goto error; + } + status_code = atoi(buf); + if (status_code != SMTP_STATUS_OK) { + ret = status_code; + goto error; + } + // DATA\r\n + cmdlen = smtp_build_command(SMTP_DATA, NULL, buf, buflen); + nsend = send(sockfd, buf, cmdlen, 0); + if (nsend != cmdlen) { + ret = ERR_SEND; + goto error; + } + nrecv = recv(sockfd, buf, buflen, 0); + if (nrecv <= 0) { + ret = ERR_RECV; + goto error; + } + status_code = atoi(buf); + // SMTP_STATUS_DATA + if (status_code >= 400) { + ret = status_code; + goto error; + } + // From: + cmdlen = snprintf(buf, buflen, "From:%s\r\n", mail->from); + nsend = send(sockfd, buf, cmdlen, 0); + if (nsend != cmdlen) { + ret = ERR_SEND; + goto error; + } + // To: + cmdlen = snprintf(buf, buflen, "To:%s\r\n", mail->to); + nsend = send(sockfd, buf, cmdlen, 0); + if (nsend != cmdlen) { + ret = ERR_SEND; + goto error; + } + // Subject: + cmdlen = snprintf(buf, buflen, "Subject:%s\r\n\r\n", mail->subject); + nsend = send(sockfd, buf, cmdlen, 0); + if (nsend != cmdlen) { + ret = ERR_SEND; + goto error; + } + // body + cmdlen = strlen(mail->body); + nsend = send(sockfd, mail->body, cmdlen, 0); + if (nsend != cmdlen) { + ret = ERR_SEND; + goto error; + } + // EOB + nsend = send(sockfd, SMTP_EOB, SMTP_EOB_LEN, 0); + if (nsend != SMTP_EOB_LEN) { + ret = ERR_SEND; + goto error; + } + nrecv = recv(sockfd, buf, buflen, 0); + if (nrecv <= 0) { + ret = ERR_SEND; + goto error; + } + status_code = atoi(buf); + if (status_code != SMTP_STATUS_OK) { + ret = status_code; + goto error; + } + // QUIT\r\n + cmdlen = smtp_build_command(SMTP_QUIT, NULL, buf, buflen); + nsend = send(sockfd, buf, cmdlen, 0); + if (nsend != cmdlen) { + ret = ERR_SEND; + goto error; + } + nrecv = recv(sockfd, buf, buflen, 0); + if (nrecv <= 0) { + ret = ERR_RECV; + goto error; + } + /* + status_code = atoi(buf); + if (status_code != SMTP_STATUS_BYE) { + ret = status_code; + goto error; + } + */ + ret = SMTP_STATUS_OK; + +error: + if (sockfd != INVALID_SOCKET) { + closesocket(sockfd); + } + return ret; +} diff --git a/ww/libhv/protocol/smtp.h b/ww/libhv/protocol/smtp.h new file mode 100644 index 00000000..ce003996 --- /dev/null +++ b/ww/libhv/protocol/smtp.h @@ -0,0 +1,74 @@ +#ifndef HV_SMTP_H_ +#define HV_SMTP_H_ + +#include "hexport.h" + +#define SMTP_PORT 25 +#define SMTPS_PORT 465 +#define SMTP_EOB "\r\n.\r\n" +#define SMTP_EOB_LEN 5 + +// smtp_command +// XX(name, string) +#define SMTP_COMMAND_MAP(XX)\ + XX(HELO, HELO) \ + XX(EHLO, EHLO) \ + XX(AUTH, AUTH) \ + XX(MAIL, MAIL FROM:) \ + XX(RCPT, RCPT TO:) \ + XX(DATA, DATA) \ + XX(QUIT, QUIT) \ + +enum smtp_command { +#define XX(name, string) SMTP_##name, + SMTP_COMMAND_MAP(XX) +#undef XX +}; + +// smtp_status +// XXX(code, name, string) +#define SMTP_STATUS_MAP(XXX) \ + XXX(220, READY, Ready) \ + XXX(221, BYE, Bye) \ + XXX(235, AUTH_SUCCESS, Authentication success) \ + XXX(250, OK, OK) \ + XXX(334, AUTH, Auth input) \ + XXX(354, DATA, End with .) \ + XXX(500, BAD_SYNTAX, Bad syntax) \ + XXX(502, NOT_IMPLEMENTED,Command not implemented) \ + XXX(503, BAD_SEQUENCE, Bad sequence of commands) \ + XXX(504, UNRECOGNIZED_AUTH_TYPE, Unrecognized authentication type) \ + XXX(535, AUTH_FAILED, Authentication failed) \ + XXX(553, ERR_MAIL, Mailbox name not allowed) \ + XXX(554, ERR_DATA, Transaction failed) \ + +enum smtp_status { +#define XXX(code, name, string) SMTP_STATUS_##name = code, + SMTP_STATUS_MAP(XXX) +#undef XXX +}; + +typedef struct mail_s { + char* from; + char* to; + char* subject; + char* body; +} mail_t; + +BEGIN_EXTERN_C + +HV_EXPORT const char* smtp_command_str(enum smtp_command cmd); +HV_EXPORT const char* smtp_status_str(enum smtp_status status); + +// cmd param\r\n +HV_EXPORT int smtp_build_command(enum smtp_command cmd, const char* param, char* buf, int buflen); +// status_code status_message\r\n + +HV_EXPORT int sendmail(const char* smtp_server, + const char* username, + const char* password, + mail_t* mail); + +END_EXTERN_C + +#endif // HV_SMTP_H_ diff --git a/ww/libhv/util/README.md b/ww/libhv/util/README.md new file mode 100644 index 00000000..b5fc90e7 --- /dev/null +++ b/ww/libhv/util/README.md @@ -0,0 +1,9 @@ +## 目录结构 + +``` +. +├── base64.h BASE64编解码 +├── md5.h MD5数字摘要 +└── sha1.h SHA1安全散列算法 + +``` diff --git a/ww/libhv/util/base64.c b/ww/libhv/util/base64.c new file mode 100644 index 00000000..80e8e555 --- /dev/null +++ b/ww/libhv/util/base64.c @@ -0,0 +1,126 @@ +#include + +#include "base64.h" + +/* BASE 64 encode table */ +static const char base64en[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/', +}; + +#define BASE64_PAD '=' +#define BASE64DE_FIRST '+' +#define BASE64DE_LAST 'z' +/* ASCII order for BASE 64 decode, -1 in unused character */ +static const signed char base64de[] = { + /* '+', ',', '-', '.', '/', '0', '1', '2', */ + 62, -1, -1, -1, 63, 52, 53, 54, + + /* '3', '4', '5', '6', '7', '8', '9', ':', */ + 55, 56, 57, 58, 59, 60, 61, -1, + + /* ';', '<', '=', '>', '?', '@', 'A', 'B', */ + -1, -1, -1, -1, -1, -1, 0, 1, + + /* 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', */ + 2, 3, 4, 5, 6, 7, 8, 9, + + /* 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', */ + 10, 11, 12, 13, 14, 15, 16, 17, + + /* 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', */ + 18, 19, 20, 21, 22, 23, 24, 25, + + /* '[', '\', ']', '^', '_', '`', 'a', 'b', */ + -1, -1, -1, -1, -1, -1, 26, 27, + + /* 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', */ + 28, 29, 30, 31, 32, 33, 34, 35, + + /* 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', */ + 36, 37, 38, 39, 40, 41, 42, 43, + + /* 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', */ + 44, 45, 46, 47, 48, 49, 50, 51, +}; + +int hv_base64_encode(const unsigned char *in, unsigned int inlen, char *out) { + unsigned int i = 0, j = 0; + + for (; i < inlen; i++) { + int s = i % 3; + + switch (s) { + case 0: + out[j++] = base64en[(in[i] >> 2) & 0x3F]; + continue; + case 1: + out[j++] = base64en[((in[i-1] & 0x3) << 4) + ((in[i] >> 4) & 0xF)]; + continue; + case 2: + out[j++] = base64en[((in[i-1] & 0xF) << 2) + ((in[i] >> 6) & 0x3)]; + out[j++] = base64en[in[i] & 0x3F]; + } + } + + /* move back */ + i -= 1; + + /* check the last and add padding */ + if ((i % 3) == 0) { + out[j++] = base64en[(in[i] & 0x3) << 4]; + out[j++] = BASE64_PAD; + out[j++] = BASE64_PAD; + } else if ((i % 3) == 1) { + out[j++] = base64en[(in[i] & 0xF) << 2]; + out[j++] = BASE64_PAD; + } + + return j; +} + +int hv_base64_decode(const char *in, unsigned int inlen, unsigned char *out) { + unsigned int i = 0, j = 0; + + for (; i < inlen; i++) { + int c; + int s = i % 4; + + if (in[i] == '=') + return j; + + if (in[i] < BASE64DE_FIRST || in[i] > BASE64DE_LAST || + (c = base64de[in[i] - BASE64DE_FIRST]) == -1) + return -1; + + switch (s) { + case 0: + out[j] = ((unsigned int)c << 2) & 0xFF; + continue; + case 1: + out[j++] += ((unsigned int)c >> 4) & 0x3; + + /* if not last char with padding */ + if (i < (inlen - 3) || in[inlen - 2] != '=') + out[j] = ((unsigned int)c & 0xF) << 4; + continue; + case 2: + out[j++] += ((unsigned int)c >> 2) & 0xF; + + /* if not last char with padding */ + if (i < (inlen - 2) || in[inlen - 1] != '=') + out[j] = ((unsigned int)c & 0x3) << 6; + continue; + case 3: + out[j++] += (unsigned char)c; + } + } + + return j; +} diff --git a/ww/libhv/util/base64.h b/ww/libhv/util/base64.h new file mode 100644 index 00000000..ed24c0a1 --- /dev/null +++ b/ww/libhv/util/base64.h @@ -0,0 +1,46 @@ +#ifndef HV_BASE64_H_ +#define HV_BASE64_H_ + +#include "hexport.h" + +#define BASE64_ENCODE_OUT_SIZE(s) (((s) + 2) / 3 * 4) +#define BASE64_DECODE_OUT_SIZE(s) (((s)) / 4 * 3) + +BEGIN_EXTERN_C + +// @return encoded size +HV_EXPORT int hv_base64_encode(const unsigned char *in, unsigned int inlen, char *out); + +// @return decoded size +HV_EXPORT int hv_base64_decode(const char *in, unsigned int inlen, unsigned char *out); + +END_EXTERN_C + +#ifdef __cplusplus + +#include +#include + +namespace hv { + +HV_INLINE std::string Base64Encode(const unsigned char* data, unsigned int len) { + int encoded_size = BASE64_ENCODE_OUT_SIZE(len); + std::string encoded_str(encoded_size + 1, 0); + encoded_size = hv_base64_encode(data, len, (char*)encoded_str.data()); + encoded_str.resize(encoded_size); + return encoded_str; +} + +HV_INLINE std::string Base64Decode(const char* str, unsigned int len = 0) { + if (len == 0) len = strlen(str); + int decoded_size = BASE64_DECODE_OUT_SIZE(len); + std::string decoded_buf(decoded_size + 1, 0); + decoded_size = hv_base64_decode(str, len, (unsigned char*)decoded_buf.data()); + decoded_buf.resize(decoded_size); + return decoded_buf; +} + +} +#endif + +#endif // HV_BASE64_H_ diff --git a/ww/libhv/util/md5.c b/ww/libhv/util/md5.c new file mode 100644 index 00000000..21f6c9f4 --- /dev/null +++ b/ww/libhv/util/md5.c @@ -0,0 +1,215 @@ +#include + +#include "md5.h" + +#define F(x,y,z) ((x & y) | (~x & z)) +#define G(x,y,z) ((x & z) | (y & ~z)) +#define H(x,y,z) (x^y^z) +#define I(x,y,z) (y ^ (x | ~z)) +#define ROTATE_LEFT(x,n) ((x << n) | (x >> (32-n))) +#define FF(a,b,c,d,x,s,ac) \ + { \ + a += F(b,c,d) + x + ac; \ + a = ROTATE_LEFT(a,s); \ + a += b; \ + } +#define GG(a,b,c,d,x,s,ac) \ + { \ + a += G(b,c,d) + x + ac; \ + a = ROTATE_LEFT(a,s); \ + a += b; \ + } +#define HH(a,b,c,d,x,s,ac) \ + { \ + a += H(b,c,d) + x + ac; \ + a = ROTATE_LEFT(a,s); \ + a += b; \ + } +#define II(a,b,c,d,x,s,ac) \ + { \ + a += I(b,c,d) + x + ac; \ + a = ROTATE_LEFT(a,s); \ + a += b; \ + } + +static void HV_MD5Transform(unsigned int state[4],unsigned char block[64]); +static void HV_MD5Encode(unsigned char *output,unsigned int *input,unsigned int len); +static void HV_MD5Decode(unsigned int *output,unsigned char *input,unsigned int len); + +static unsigned char PADDING[] = { + 0x80,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +}; + +void HV_MD5Init(HV_MD5_CTX *ctx) { + ctx->count[0] = 0; + ctx->count[1] = 0; + ctx->state[0] = 0x67452301; + ctx->state[1] = 0xEFCDAB89; + ctx->state[2] = 0x98BADCFE; + ctx->state[3] = 0x10325476; +} + +void HV_MD5Update(HV_MD5_CTX *ctx,unsigned char *input,unsigned int inputlen) { + unsigned int i = 0,index = 0,partlen = 0; + index = (ctx->count[0] >> 3) & 0x3F; + partlen = 64 - index; + ctx->count[0] += inputlen << 3; + if(ctx->count[0] < (inputlen << 3)) { + ctx->count[1]++; + } + ctx->count[1] += inputlen >> 29; + + if(inputlen >= partlen) { + memcpy(&ctx->buffer[index],input,partlen); + HV_MD5Transform(ctx->state,ctx->buffer); + for(i = partlen;i+64 <= inputlen;i+=64) { + HV_MD5Transform(ctx->state,&input[i]); + } + index = 0; + } else { + i = 0; + } + + memcpy(&ctx->buffer[index],&input[i],inputlen-i); +} + +void HV_MD5Final(HV_MD5_CTX *ctx,unsigned char digest[16]) { + unsigned int index = 0,padlen = 0; + unsigned char bits[8]; + index = (ctx->count[0] >> 3) & 0x3F; + padlen = (index < 56)?(56-index):(120-index); + HV_MD5Encode(bits,ctx->count,8); + HV_MD5Update(ctx,PADDING,padlen); + HV_MD5Update(ctx,bits,8); + HV_MD5Encode(digest,ctx->state,16); +} + +void HV_MD5Encode(unsigned char *output,unsigned int *input,unsigned int len) { + unsigned int i = 0,j = 0; + while(j < len) { + output[j] = input[i] & 0xFF; + output[j+1] = (input[i] >> 8) & 0xFF; + output[j+2] = (input[i] >> 16) & 0xFF; + output[j+3] = (input[i] >> 24) & 0xFF; + i++; + j+=4; + } +} + +void HV_MD5Decode(unsigned int *output,unsigned char *input,unsigned int len) { + unsigned int i = 0,j = 0; + while(j < len) { + output[i] = (input[j]) | (input[j+1] << 8) | (input[j+2] << 16) | (input[j+3] << 24); + i++; + j+=4; + } +} + +void HV_MD5Transform(unsigned int state[4],unsigned char block[64]) { + unsigned int a = state[0]; + unsigned int b = state[1]; + unsigned int c = state[2]; + unsigned int d = state[3]; + unsigned int x[64]; + + HV_MD5Decode(x,block,64); + + FF(a, b, c, d, x[ 0], 7, 0xd76aa478); + FF(d, a, b, c, x[ 1], 12, 0xe8c7b756); + FF(c, d, a, b, x[ 2], 17, 0x242070db); + FF(b, c, d, a, x[ 3], 22, 0xc1bdceee); + FF(a, b, c, d, x[ 4], 7, 0xf57c0faf); + FF(d, a, b, c, x[ 5], 12, 0x4787c62a); + FF(c, d, a, b, x[ 6], 17, 0xa8304613); + FF(b, c, d, a, x[ 7], 22, 0xfd469501); + FF(a, b, c, d, x[ 8], 7, 0x698098d8); + FF(d, a, b, c, x[ 9], 12, 0x8b44f7af); + FF(c, d, a, b, x[10], 17, 0xffff5bb1); + FF(b, c, d, a, x[11], 22, 0x895cd7be); + FF(a, b, c, d, x[12], 7, 0x6b901122); + FF(d, a, b, c, x[13], 12, 0xfd987193); + FF(c, d, a, b, x[14], 17, 0xa679438e); + FF(b, c, d, a, x[15], 22, 0x49b40821); + + GG(a, b, c, d, x[ 1], 5, 0xf61e2562); + GG(d, a, b, c, x[ 6], 9, 0xc040b340); + GG(c, d, a, b, x[11], 14, 0x265e5a51); + GG(b, c, d, a, x[ 0], 20, 0xe9b6c7aa); + GG(a, b, c, d, x[ 5], 5, 0xd62f105d); + GG(d, a, b, c, x[10], 9, 0x2441453); + GG(c, d, a, b, x[15], 14, 0xd8a1e681); + GG(b, c, d, a, x[ 4], 20, 0xe7d3fbc8); + GG(a, b, c, d, x[ 9], 5, 0x21e1cde6); + GG(d, a, b, c, x[14], 9, 0xc33707d6); + GG(c, d, a, b, x[ 3], 14, 0xf4d50d87); + GG(b, c, d, a, x[ 8], 20, 0x455a14ed); + GG(a, b, c, d, x[13], 5, 0xa9e3e905); + GG(d, a, b, c, x[ 2], 9, 0xfcefa3f8); + GG(c, d, a, b, x[ 7], 14, 0x676f02d9); + GG(b, c, d, a, x[12], 20, 0x8d2a4c8a); + + HH(a, b, c, d, x[ 5], 4, 0xfffa3942); + HH(d, a, b, c, x[ 8], 11, 0x8771f681); + HH(c, d, a, b, x[11], 16, 0x6d9d6122); + HH(b, c, d, a, x[14], 23, 0xfde5380c); + HH(a, b, c, d, x[ 1], 4, 0xa4beea44); + HH(d, a, b, c, x[ 4], 11, 0x4bdecfa9); + HH(c, d, a, b, x[ 7], 16, 0xf6bb4b60); + HH(b, c, d, a, x[10], 23, 0xbebfbc70); + HH(a, b, c, d, x[13], 4, 0x289b7ec6); + HH(d, a, b, c, x[ 0], 11, 0xeaa127fa); + HH(c, d, a, b, x[ 3], 16, 0xd4ef3085); + HH(b, c, d, a, x[ 6], 23, 0x4881d05); + HH(a, b, c, d, x[ 9], 4, 0xd9d4d039); + HH(d, a, b, c, x[12], 11, 0xe6db99e5); + HH(c, d, a, b, x[15], 16, 0x1fa27cf8); + HH(b, c, d, a, x[ 2], 23, 0xc4ac5665); + + II(a, b, c, d, x[ 0], 6, 0xf4292244); + II(d, a, b, c, x[ 7], 10, 0x432aff97); + II(c, d, a, b, x[14], 15, 0xab9423a7); + II(b, c, d, a, x[ 5], 21, 0xfc93a039); + II(a, b, c, d, x[12], 6, 0x655b59c3); + II(d, a, b, c, x[ 3], 10, 0x8f0ccc92); + II(c, d, a, b, x[10], 15, 0xffeff47d); + II(b, c, d, a, x[ 1], 21, 0x85845dd1); + II(a, b, c, d, x[ 8], 6, 0x6fa87e4f); + II(d, a, b, c, x[15], 10, 0xfe2ce6e0); + II(c, d, a, b, x[ 6], 15, 0xa3014314); + II(b, c, d, a, x[13], 21, 0x4e0811a1); + II(a, b, c, d, x[ 4], 6, 0xf7537e82); + II(d, a, b, c, x[11], 10, 0xbd3af235); + II(c, d, a, b, x[ 2], 15, 0x2ad7d2bb); + II(b, c, d, a, x[ 9], 21, 0xeb86d391); + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; +} + +void hv_md5(unsigned char* input, unsigned int inputlen, unsigned char digest[16]) { + HV_MD5_CTX ctx; + HV_MD5Init(&ctx); + HV_MD5Update(&ctx, input, inputlen); + HV_MD5Final(&ctx, digest); +} + +static inline char i2hex(unsigned char i) { + return i < 10 ? i + '0' : i - 10 + 'a'; +} + +void hv_md5_hex(unsigned char* input, unsigned int inputlen, char* output, unsigned int outputlen) { + int i; + unsigned char digest[16]; + if (outputlen < 32) return; + hv_md5(input, inputlen, digest); + for (i = 0; i < 16; ++i) { + *output++ = i2hex(digest[i] >> 4); + *output++ = i2hex(digest[i] & 0x0F); + } + if (outputlen > 32) *output = '\0'; +} diff --git a/ww/libhv/util/md5.h b/ww/libhv/util/md5.h new file mode 100644 index 00000000..9909856f --- /dev/null +++ b/ww/libhv/util/md5.h @@ -0,0 +1,25 @@ +#ifndef HV_MD5_H_ +#define HV_MD5_H_ + +#include "hexport.h" + +typedef struct { + unsigned int count[2]; + unsigned int state[4]; + unsigned char buffer[64]; +} HV_MD5_CTX; + +BEGIN_EXTERN_C + +HV_EXPORT void HV_MD5Init(HV_MD5_CTX *ctx); +HV_EXPORT void HV_MD5Update(HV_MD5_CTX *ctx, unsigned char *input, unsigned int inputlen); +HV_EXPORT void HV_MD5Final(HV_MD5_CTX *ctx, unsigned char digest[16]); + +HV_EXPORT void hv_md5(unsigned char* input, unsigned int inputlen, unsigned char digest[16]); + +// NOTE: if outputlen > 32: output[32] = '\0' +HV_EXPORT void hv_md5_hex(unsigned char* input, unsigned int inputlen, char* output, unsigned int outputlen); + +END_EXTERN_C + +#endif // HV_MD5_H_ diff --git a/ww/libhv/util/sha1.c b/ww/libhv/util/sha1.c new file mode 100644 index 00000000..fea5b697 --- /dev/null +++ b/ww/libhv/util/sha1.c @@ -0,0 +1,313 @@ +/* +SHA-1 in C +By Steve Reid +100% Public Domain + +Test Vectors (from FIPS PUB 180-1) +"abc" + A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D +"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 +A million repetitions of "a" + 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F +*/ + +/* #define LITTLE_ENDIAN * This should be #define'd already, if true. */ +/* #define SHA1HANDSOFF * Copies data before messing with it. */ + +#define SHA1HANDSOFF + +#include +#include + +#include "sha1.h" + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +/* blk0() and blk() perform the initial expand. */ +/* I got the idea of expanding during the round function from SSLeay */ +#if BYTE_ORDER == LITTLE_ENDIAN +#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ + |(rol(block->l[i],8)&0x00FF00FF)) +#elif BYTE_ORDER == BIG_ENDIAN +#define blk0(i) block->l[i] +#else +#error "Endianness not defined!" +#endif +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ + ^block->l[(i+2)&15]^block->l[i&15],1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + +/* Hash a single 512-bit block. This is the core of the algorithm. */ + +void HV_SHA1Transform( + uint32_t state[5], + const unsigned char buffer[64] +) +{ + uint32_t a, b, c, d, e; + + typedef union + { + unsigned char c[64]; + uint32_t l[16]; + } CHAR64LONG16; + +#ifdef SHA1HANDSOFF + CHAR64LONG16 block[1]; /* use array to appear as a pointer */ + + memcpy(block, buffer, 64); +#else + /* The following had better never be used because it causes the + * pointer-to-const buffer to be cast into a pointer to non-const. + * And the result is written through. I threw a "const" in, hoping + * this will cause a diagnostic. + */ + CHAR64LONG16 *block = (const CHAR64LONG16 *) buffer; +#endif + /* Copy context->state[] to working vars */ + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a, b, c, d, e, 0); + R0(e, a, b, c, d, 1); + R0(d, e, a, b, c, 2); + R0(c, d, e, a, b, 3); + R0(b, c, d, e, a, 4); + R0(a, b, c, d, e, 5); + R0(e, a, b, c, d, 6); + R0(d, e, a, b, c, 7); + R0(c, d, e, a, b, 8); + R0(b, c, d, e, a, 9); + R0(a, b, c, d, e, 10); + R0(e, a, b, c, d, 11); + R0(d, e, a, b, c, 12); + R0(c, d, e, a, b, 13); + R0(b, c, d, e, a, 14); + R0(a, b, c, d, e, 15); + R1(e, a, b, c, d, 16); + R1(d, e, a, b, c, 17); + R1(c, d, e, a, b, 18); + R1(b, c, d, e, a, 19); + R2(a, b, c, d, e, 20); + R2(e, a, b, c, d, 21); + R2(d, e, a, b, c, 22); + R2(c, d, e, a, b, 23); + R2(b, c, d, e, a, 24); + R2(a, b, c, d, e, 25); + R2(e, a, b, c, d, 26); + R2(d, e, a, b, c, 27); + R2(c, d, e, a, b, 28); + R2(b, c, d, e, a, 29); + R2(a, b, c, d, e, 30); + R2(e, a, b, c, d, 31); + R2(d, e, a, b, c, 32); + R2(c, d, e, a, b, 33); + R2(b, c, d, e, a, 34); + R2(a, b, c, d, e, 35); + R2(e, a, b, c, d, 36); + R2(d, e, a, b, c, 37); + R2(c, d, e, a, b, 38); + R2(b, c, d, e, a, 39); + R3(a, b, c, d, e, 40); + R3(e, a, b, c, d, 41); + R3(d, e, a, b, c, 42); + R3(c, d, e, a, b, 43); + R3(b, c, d, e, a, 44); + R3(a, b, c, d, e, 45); + R3(e, a, b, c, d, 46); + R3(d, e, a, b, c, 47); + R3(c, d, e, a, b, 48); + R3(b, c, d, e, a, 49); + R3(a, b, c, d, e, 50); + R3(e, a, b, c, d, 51); + R3(d, e, a, b, c, 52); + R3(c, d, e, a, b, 53); + R3(b, c, d, e, a, 54); + R3(a, b, c, d, e, 55); + R3(e, a, b, c, d, 56); + R3(d, e, a, b, c, 57); + R3(c, d, e, a, b, 58); + R3(b, c, d, e, a, 59); + R4(a, b, c, d, e, 60); + R4(e, a, b, c, d, 61); + R4(d, e, a, b, c, 62); + R4(c, d, e, a, b, 63); + R4(b, c, d, e, a, 64); + R4(a, b, c, d, e, 65); + R4(e, a, b, c, d, 66); + R4(d, e, a, b, c, 67); + R4(c, d, e, a, b, 68); + R4(b, c, d, e, a, 69); + R4(a, b, c, d, e, 70); + R4(e, a, b, c, d, 71); + R4(d, e, a, b, c, 72); + R4(c, d, e, a, b, 73); + R4(b, c, d, e, a, 74); + R4(a, b, c, d, e, 75); + R4(e, a, b, c, d, 76); + R4(d, e, a, b, c, 77); + R4(c, d, e, a, b, 78); + R4(b, c, d, e, a, 79); + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + /* Wipe variables */ + a = b = c = d = e = 0; +#ifdef SHA1HANDSOFF + memset(block, '\0', sizeof(block)); +#endif +} + + +/* HV_SHA1Init - Initialize new context */ + +void HV_SHA1Init( + HV_SHA1_CTX * context +) +{ + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + + +/* Run your data through this. */ + +void HV_SHA1Update( + HV_SHA1_CTX * context, + const unsigned char *data, + uint32_t len +) +{ + uint32_t i; + + uint32_t j; + + j = context->count[0]; + if ((context->count[0] += len << 3) < j) + context->count[1]++; + context->count[1] += (len >> 29); + j = (j >> 3) & 63; + if ((j + len) > 63) + { + memcpy(&context->buffer[j], data, (i = 64 - j)); + HV_SHA1Transform(context->state, context->buffer); + for (; i + 63 < len; i += 64) + { + HV_SHA1Transform(context->state, &data[i]); + } + j = 0; + } + else + i = 0; + memcpy(&context->buffer[j], &data[i], len - i); +} + + +/* Add padding and return the message digest. */ + +void HV_SHA1Final( + unsigned char digest[20], + HV_SHA1_CTX * context +) +{ + unsigned i; + + unsigned char finalcount[8]; + + unsigned char c; + +#if 0 /* untested "improvement" by DHR */ + /* Convert context->count to a sequence of bytes + * in finalcount. Second element first, but + * big-endian order within element. + * But we do it all backwards. + */ + unsigned char *fcp = &finalcount[8]; + + for (i = 0; i < 2; i++) + { + uint32_t t = context->count[i]; + + int j; + + for (j = 0; j < 4; t >>= 8, j++) + *--fcp = (unsigned char) t} +#else + for (i = 0; i < 8; i++) + { + finalcount[i] = (unsigned char) ((context->count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 255); /* Endian independent */ + } +#endif + c = 0200; + HV_SHA1Update(context, &c, 1); + while ((context->count[0] & 504) != 448) + { + c = 0000; + HV_SHA1Update(context, &c, 1); + } + HV_SHA1Update(context, finalcount, 8); /* Should cause a HV_SHA1Transform() */ + for (i = 0; i < 20; i++) + { + digest[i] = (unsigned char) + ((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255); + } + /* Wipe variables */ + memset(context, '\0', sizeof(*context)); + memset(&finalcount, '\0', sizeof(finalcount)); +} + +void HV_SHA1( + char *hash_out, + const char *str, + uint32_t len) +{ + HV_SHA1_CTX ctx; + unsigned int ii; + + HV_SHA1Init(&ctx); + for (ii=0; ii> 4); + *output++ = i2hex(digest[i] & 0x0F); + } + if (outputlen > 40) *output = '\0'; +} diff --git a/ww/libhv/util/sha1.h b/ww/libhv/util/sha1.h new file mode 100644 index 00000000..3d74765e --- /dev/null +++ b/ww/libhv/util/sha1.h @@ -0,0 +1,55 @@ +#ifndef HV_SHA1_H_ +#define HV_SHA1_H_ + +/* + SHA-1 in C + By Steve Reid + 100% Public Domain + */ + +/* for uint32_t */ +#include + +#include "hexport.h" + +typedef struct { + uint32_t state[5]; + uint32_t count[2]; + unsigned char buffer[64]; +} HV_SHA1_CTX; + +BEGIN_EXTERN_C + +HV_EXPORT void HV_SHA1Transform( + uint32_t state[5], + const unsigned char buffer[64] + ); + +HV_EXPORT void HV_SHA1Init( + HV_SHA1_CTX * context + ); + +HV_EXPORT void HV_SHA1Update( + HV_SHA1_CTX * context, + const unsigned char *data, + uint32_t len + ); + +HV_EXPORT void HV_SHA1Final( + unsigned char digest[20], + HV_SHA1_CTX * context + ); + +HV_EXPORT void HV_SHA1( + char *hash_out, + const char *str, + uint32_t len); + +HV_EXPORT void hv_sha1(unsigned char* input, uint32_t inputlen, unsigned char digest[20]); + +// NOTE: if outputlen > 40: output[40] = '\0' +HV_EXPORT void hv_sha1_hex(unsigned char* input, uint32_t inputlen, char* output, uint32_t outputlen); + +END_EXTERN_C + +#endif // HV_SHA1_H_