diff options
Diffstat (limited to 'wsutil')
188 files changed, 41230 insertions, 0 deletions
diff --git a/wsutil/.editorconfig b/wsutil/.editorconfig new file mode 100644 index 00000000..78cac438 --- /dev/null +++ b/wsutil/.editorconfig @@ -0,0 +1,117 @@ +# +# Editor configuration +# +# https://editorconfig.org/ +# + +[adler32.[ch]] +indent_size = 2 + +[aes.[ch]] +indent_style = tab +indent_size = tab + +[dot11decrypt_wep.[ch]] +indent_style = tab +indent_size = tab + +[base64.[ch]] +indent_style = tab +indent_size = tab + +[bitswap.[ch]] +indent_size = 2 + +[buffer.[ch]] +indent_style = tab +indent_size = tab + +[cfutils.[ch]] +indent_style = tab +indent_size = tab + +[clopts_common.[ch]] +indent_size = 2 + +[crash_info.[ch]] +indent_style = tab +indent_size = tab + +[crc10.[ch]] +indent_style = tab +indent_size = tab + +[crc32.[ch]] +indent_style = tab +indent_size = tab + +[des.[ch]] +indent_style = tab +indent_size = tab + +[g711.[ch]] +indent_style = tab +indent_size = tab + +[interface.[ch]] +indent_style = tab +indent_size = tab + +[jsmn.[ch]] +indent_size = 8 + +[md4.[ch]] +indent_style = tab +indent_size = tab + +[mpeg-audio.[ch]] +indent_style = tab +indent_size = tab + +[os_version_info.[ch]] +indent_style = tab +indent_size = tab + +[privileges.[ch]] +indent_style = tab +indent_size = tab + +[rc4.[ch]] +indent_size = 2 + +[report_err.[ch]] +indent_style = tab +indent_size = tab + +[tempfile.[ch]] +indent_size = 2 + +[time_util.[ch]] +indent_style = tab +indent_size = tab + +[to_str.[ch]] +indent_style = tab +indent_size = tab + +[type_util.[ch]] +indent_size = 2 + +[u3.[ch]] +indent_size = 2 + +[ws_getopt.[ch]] +indent_style = tab +indent_size = tab + +[ws_mempbrk.[ch]] +indent_style = tab +indent_size = tab + +[ws_mempbrk_sse42.[ch]] +indent_size = 2 + +[ws_strptime.[ch]] +indent_style = tab +indent_size = tab + diff --git a/wsutil/802_11-utils.c b/wsutil/802_11-utils.c new file mode 100644 index 00000000..ee79f839 --- /dev/null +++ b/wsutil/802_11-utils.c @@ -0,0 +1,113 @@ +/* 802_11-utils.c + * 802.11 utility definitions + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 2007 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" +#include "802_11-utils.h" + +typedef struct freq_cvt_s { + unsigned fmin; /* Minimum frequency in MHz */ + unsigned fmax; /* Maximum frequency in MHz */ + int cmin; /* Minimum/base channel */ + bool is_bg; /* B/G channel? */ +} freq_cvt_t; + +#define FREQ_STEP 5 /* MHz. This seems to be consistent, thankfully */ + +/* + * XXX - Japanese channels 182 through 196 actually have center + * frequencies that are off by 2.5 MHz from these values, according + * to the IEEE standard, although the table in ARIB STD T-71 version 5.2: + * + * http://www.arib.or.jp/english/html/overview/doc/1-STD-T71v5_2.pdf + * + * section 5.3.8.3.3 doesn't show that. + * + * XXX - what about the U.S. public safety 4.9 GHz band? + * + * XXX - what about 802.11ad? + */ +static freq_cvt_t freq_cvt[] = { + { 2412, 2472, 1, true }, /* IEEE Std 802.11-2020: Section 15.4.4.3 and Annex E */ + { 2484, 2484, 14, true }, /* IEEE Std 802.11-2020: Section 15.4.4.3 and Annex E */ + { 5000, 5925, 0, false }, /* IEEE Std 802.11-2020: Annex E */ + { 5950, 7125, 0, false }, /* IEEE Std 802.11ax-2021: Annex E */ + { 4910, 4980, 182, false }, +}; + +#define NUM_FREQ_CVT (sizeof(freq_cvt) / sizeof(freq_cvt_t)) +#define MAX_CHANNEL(fc) ( (int) ((fc.fmax - fc.fmin) / FREQ_STEP) + fc.cmin ) + +/* + * Get channel number given a Frequency + */ +int +ieee80211_mhz_to_chan(unsigned freq) { + unsigned i; + + for (i = 0; i < NUM_FREQ_CVT; i++) { + if (freq >= freq_cvt[i].fmin && freq <= freq_cvt[i].fmax) { + return ((freq - freq_cvt[i].fmin) / FREQ_STEP) + freq_cvt[i].cmin; + } + } + return -1; +} + +/* + * Get Frequency given a Channel number + * + * XXX - Because channel numbering schemes for 2.4 and 5 overlap with 6 GHz, + * this function may not return the correct channel. For example, the frequency + * for channel 1 in 2.4 GHz band is 2412 MHz, while the frequency for channel 1 + * in the 6 GHz band is 5955 MHz. To resolve this problem, this function needs + * to take a starting frequency to convert channel to frequencies correctly. + * Unfortunately, this is not possible in some cases, so for now, the order on + * which frequency ranges are defined will favor 2.4 and 5 GHz over 6 GHz. + */ +unsigned +ieee80211_chan_to_mhz(int chan, bool is_bg) { + unsigned i; + + for (i = 0; i < NUM_FREQ_CVT; i++) { + if (is_bg == freq_cvt[i].is_bg && + chan >= freq_cvt[i].cmin && chan <= MAX_CHANNEL(freq_cvt[i])) { + return ((chan - freq_cvt[i].cmin) * FREQ_STEP) + freq_cvt[i].fmin; + } + } + return 0; +} + +/* + * Get channel representation string given a Frequency + */ +char* +ieee80211_mhz_to_str(unsigned freq){ + int chan = ieee80211_mhz_to_chan(freq); + bool is_bg = FREQ_IS_BG(freq); + + if (chan < 0) { + return ws_strdup_printf("%u", freq); + } else { + return ws_strdup_printf("%u [%s %u]", freq, is_bg ? "BG" : "A", + chan); + } +} + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/802_11-utils.h b/wsutil/802_11-utils.h new file mode 100644 index 00000000..887faddb --- /dev/null +++ b/wsutil/802_11-utils.h @@ -0,0 +1,115 @@ +/* 802_11-utils.h + * 802.11 utility definitions + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 2007 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __802_11_UTILS_H__ +#define __802_11_UTILS_H__ + +#include <wireshark.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** @file + * 802.11 utilities. + */ + +/** + * Given a center frequency in MHz, return a channel number. + * @param freq Frequency in MHz. + * @return The equivalent channel or -1 if no match is found. + */ +WS_DLL_PUBLIC +int +ieee80211_mhz_to_chan(unsigned freq); + +/** + * Given an 802.11 channel number and a band type, return a center frequency. + * @param chan Channel number + * @param is_bg true if the channel is a b/g channel, false otherwise. + * @return The equivalent frequency or 0 if no match is found. + */ +WS_DLL_PUBLIC +unsigned +ieee80211_chan_to_mhz(int chan, bool is_bg); + +/** + * Given an 802.11 channel center frequency in MHz, return a string + * representation. + * @param freq Frequench in MHz. + * @return A string showing the frequency, channel number, and type. + * The string must be freed with g_free() after use. + */ +WS_DLL_PUBLIC +char* +ieee80211_mhz_to_str(unsigned freq); + +/* Should this be "(freq < 4920)", or something else? */ +#define FREQ_IS_BG(freq) ((freq) <= 2484) +#define CHAN_IS_BG(chan) ((chan) <= 14) + +/* + * Test whether a data rate is an {HR}/DSSS (legacy DSSS/11b) data rate + * and whether it's an OFDM (11a/11g OFDM mode) data rate. + * + * rate is in units of 500 Kb/s. + * + * The 22 and 33 Mb/s rates for DSSS use Packet Binary Convolutional + * Coding (PBCC). That was provided by Texas Instruments as 11b+, + * and was in section 19.6 "ERP-PBCC operation specifications" of + * IEEE Std 802.11g-2003, and sections 18.4.6.6 "DSSS/PBCC data modulation + * and modulation rate (optional)" and 19.6 "ERP-PBCC operation + * specifications" of IEEE Std 802.11-2007, and sections 17.4.6.7 "DSSS/PBCC + * data modulation and modulation rate (optional)" and 19.6 "ERP-PBCC + * operation specifications" of IEEE Std 802.11-2012, marked as optional + * in both cases, but is not present in IEEE Std 802.11-2016. + * + * (Note: not to be confused with "peanut butter and chocolate chips": + * + * https://www.bigoven.com/recipe/peanut-butter-chocolate-chip-cookies-pbcc-cookies/186266 + * + * :-)) + */ +#define RATE_IS_DSSS(rate) \ + ((rate) == 2 /* 1 Mb/s */ || \ + (rate) == 4 /* 2 Mb/s */ || \ + (rate) == 11 /* 5.5 Mb/s */ || \ + (rate) == 22 /* 11 Mb/s */ || \ + (rate) == 44 /* 22 Mb/s */ || \ + (rate) == 66 /* 33 Mb/s */) + +#define RATE_IS_OFDM(rate) \ + ((rate) == 12 /* 6 Mb/s */ || \ + (rate) == 18 /* 9 Mb/s */ || \ + (rate) == 24 /* 12 Mb/s */ || \ + (rate) == 36 /* 18 Mb/s */ || \ + (rate) == 48 /* 24 Mb/s */ || \ + (rate) == 72 /* 36 Mb/s */ || \ + (rate) == 96 /* 48 Mb/s */ || \ + (rate) == 108 /* 54 Mb/s */) + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __802_11_UTILS_H__ */ + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/CMakeLists.txt b/wsutil/CMakeLists.txt new file mode 100644 index 00000000..ba8633e1 --- /dev/null +++ b/wsutil/CMakeLists.txt @@ -0,0 +1,494 @@ +# CMakeLists.txt +# +# Wireshark - Network traffic analyzer +# By Gerald Combs <gerald@wireshark.org> +# Copyright 1998 Gerald Combs +# +# SPDX-License-Identifier: GPL-2.0-or-later +# + +file(TO_NATIVE_PATH "${CMAKE_INSTALL_PREFIX}" PATH_INSTALL_PREFIX) +string(REPLACE "\\" "\\\\" PATH_INSTALL_PREFIX "${PATH_INSTALL_PREFIX}") +file(TO_NATIVE_PATH "${CMAKE_INSTALL_DATADIR}" PATH_DATA_DIR) +string(REPLACE "\\" "\\\\" PATH_DATA_DIR "${PATH_DATA_DIR}") +file(TO_NATIVE_PATH "${CMAKE_INSTALL_DOCDIR}" PATH_DOC_DIR) +string(REPLACE "\\" "\\\\" PATH_DOC_DIR "${PATH_DOC_DIR}") +file(TO_NATIVE_PATH "${PLUGIN_INSTALL_LIBDIR}" PATH_PLUGIN_DIR) +string(REPLACE "\\" "\\\\" PATH_PLUGIN_DIR "${PATH_PLUGIN_DIR}") +file(TO_NATIVE_PATH "${EXTCAP_INSTALL_LIBDIR}" PATH_EXTCAP_DIR) +string(REPLACE "\\" "\\\\" PATH_EXTCAP_DIR "${PATH_EXTCAP_DIR}") + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/path_config.h.in ${CMAKE_CURRENT_BINARY_DIR}/path_config.h) + +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +set(WMEM_PUBLIC_HEADERS + wmem/wmem.h + wmem/wmem_array.h + wmem/wmem_core.h + wmem/wmem_list.h + wmem/wmem_map.h + wmem/wmem_miscutl.h + wmem/wmem_multimap.h + wmem/wmem_queue.h + wmem/wmem_stack.h + wmem/wmem_strbuf.h + wmem/wmem_strutl.h + wmem/wmem_tree.h + wmem/wmem_interval_tree.h + wmem/wmem_user_cb.h +) + +set(WMEM_HEADER_FILES + ${WMEM_PUBLIC_HEADERS} + wmem/wmem_allocator.h + wmem/wmem_allocator_block.h + wmem/wmem_allocator_block_fast.h + wmem/wmem_allocator_simple.h + wmem/wmem_allocator_strict.h + wmem/wmem_interval_tree.h + wmem/wmem_map_int.h + wmem/wmem_tree-int.h + wmem/wmem_user_cb_int.h +) + +set(WMEM_FILES + wmem/wmem_array.c + wmem/wmem_core.c + wmem/wmem_allocator_block.c + wmem/wmem_allocator_block_fast.c + wmem/wmem_allocator_simple.c + wmem/wmem_allocator_strict.c + wmem/wmem_interval_tree.c + wmem/wmem_list.c + wmem/wmem_map.c + wmem/wmem_miscutl.c + wmem/wmem_multimap.c + wmem/wmem_stack.c + wmem/wmem_strbuf.c + wmem/wmem_strutl.c + wmem/wmem_tree.c + wmem/wmem_user_cb.c +) + +set(WSUTIL_PUBLIC_HEADERS + 802_11-utils.h + adler32.h + base32.h + bits_count_ones.h + bits_ctz.h + bitswap.h + buffer.h + clopts_common.h + cmdarg_err.h + codecs.h + color.h + cpu_info.h + crash_info.h + crc5.h + crc6.h + crc7.h + crc8.h + crc10.h + crc11.h + crc16.h + crc16-plain.h + crc32.h + curve25519.h + eax.h + epochs.h + exported_pdu_tlvs.h + feature_list.h + filesystem.h + g711.h + inet_addr.h + inet_ipv4.h + inet_ipv6.h + interface.h + introspection.h + jsmn.h + json_dumper.h + mpeg-audio.h + nstime.h + os_version_info.h + pint.h + please_report_bug.h + pow2.h + privileges.h + processes.h + regex.h + report_message.h + sign_ext.h + sober128.h + socket.h + str_util.h + strnatcmp.h + strtoi.h + tempfile.h + time_util.h + to_str.h + type_util.h + unicode-utils.h + utf8_entities.h + version_info.h + ws_assert.h + ws_cpuid.h + glib-compat.h + ws_getopt.h + ws_mempbrk.h + ws_mempbrk_int.h + ws_pipe.h + ws_roundup.h + ws_strptime.h + wsgcrypt.h + wsjson.h + wslog.h + xtea.h +) + +set(WSUTIL_COMMON_FILES + 802_11-utils.c + adler32.c + base32.c + bitswap.c + buffer.c + clopts_common.c + cmdarg_err.c + codecs.c + crash_info.c + crc10.c + crc16.c + crc16-plain.c + crc32.c + crc5.c + crc6.c + crc7.c + crc8.c + crc11.c + curve25519.c + dot11decrypt_wep.c + eax.c + feature_list.c + filesystem.c + filter_files.c + g711.c + inet_addr.c + interface.c + introspection.c + jsmn.c + json_dumper.c + mpeg-audio.c + nstime.c + cpu_info.c + os_version_info.c + please_report_bug.c + privileges.c + regex.c + rsa.c + sober128.c + socket.c + strnatcmp.c + str_util.c + strtoi.c + report_message.c + tempfile.c + time_util.c + to_str.c + type_util.c + unicode-utils.c + version_info.c + ws_getopt.c + ws_mempbrk.c + ws_pipe.c + ws_strptime.c + wsgcrypt.c + wsjson.c + wslog.c + xtea.c +) + +if(WIN32) + list(APPEND WSUTIL_COMMON_FILES + console_win32.c + ) +endif() + +if(ENABLE_PLUGINS) + list(APPEND WSUTIL_COMMON_FILES + plugins.c + ) +endif() + +set(WSUTIL_FILES + ${WMEM_FILES} + ${WSUTIL_COMMON_FILES} +) + +if(WIN32) + list(APPEND WSUTIL_FILES + file_util.c + win32-utils.c + ) +endif(WIN32) + + +if(HAVE_MACOS_FRAMEWORKS) + list(APPEND WSUTIL_FILES cfutils.c) +endif() + +# +# XXX - we're assuming MSVC doesn't require a flag to enable SSE 4.2 +# support, and that, if the compiler supports a flag for SSE 4.2 +# support, the intrinsics are supported iff we can include the +# <nmmintrin.h> flag. +# +# We only check for the GCC-style -msse4.2 flag and the Sun C +# -xarch=sse4_2 flag. +# +if(CMAKE_C_COMPILER_ID MATCHES "MSVC") + set(COMPILER_CAN_HANDLE_SSE4_2 TRUE) + set(SSE4_2_FLAG "") +elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "i686|x86|x86_64|AMD64") + check_c_compiler_flag(-msse4.2 COMPILER_CAN_HANDLE_SSE4_2) + if(COMPILER_CAN_HANDLE_SSE4_2) + set(SSE4_2_FLAG "-msse4.2") + else() + check_c_compiler_flag(-xarch=sse4_2 COMPILER_CAN_HANDLE_SSE4_2) + if(COMPILER_CAN_HANDLE_SSE4_2) + set(SSE4_2_FLAG "-xarch=sse4_2") + endif() + endif() +else() + set(COMPILE_CAN_HANDLE_SSE4_2 FALSE) + set(SSE4_2_FLAG "") +endif() + +if(SSE4_2_FLAG) + message(STATUS "SSE4.2 compiler flag: ${SSE4_2_FLAG}") +else() + message(STATUS "No SSE4.2 compiler flag enabled") +endif() +if(COMPILER_CAN_HANDLE_SSE4_2) + # + # Make sure we have the necessary headers for the SSE4.2 intrinsics + # and that we can use them. + # + # First, check whether we have emmintrin.h and can use it + # *without* the SSE 4.2 flag. + # + check_include_file("emmintrin.h" EMMINTRIN_H_WORKS) + + # + # OK, if that works, see whether we have nmmintrin.h and + # can use it *with* the SSE 4.2 flag. + # + if(EMMINTRIN_H_WORKS) + # + # Does this add the SSE4.2 flags to the beginning of + # CFLAGS? + # + # Note that if there's a mix of "enable SSE 4.2" and + # "disable SSE 4.2" flags, this may not indicate that + # we can use the header. That's not a bug, that's a + # feature; the other flags may have been forced by + # the build process, e.g. in Gentoo Linux, and we want + # to check this with whatever flags will actually be + # used when building (see bug 10792). + # + cmake_push_check_state() + set(CMAKE_REQUIRED_FLAGS "${SSE4_2_FLAG}") + check_include_file("nmmintrin.h" HAVE_SSE4_2) + cmake_pop_check_state() + endif() +endif() +if(HAVE_SSE4_2) + list(APPEND WSUTIL_FILES ws_mempbrk_sse42.c) +endif() + +if(APPLE) + # + # We assume that APPLE means macOS so that we have the macOS + # frameworks. + # + FIND_LIBRARY (APPLE_CORE_FOUNDATION_LIBRARY CoreFoundation) +endif() + +set_source_files_properties( + ${WSUTIL_FILES} + PROPERTIES + COMPILE_FLAGS "${WERROR_COMMON_FLAGS}" +) + +if (HAVE_SSE4_2) + # TODO with CMake 2.8.12, we could use COMPILE_OPTIONS and just append + # instead of this COMPILE_FLAGS duplication... + set_source_files_properties( + ws_mempbrk_sse42.c + PROPERTIES + COMPILE_FLAGS "${WERROR_COMMON_FLAGS} ${SSE4_2_FLAG}" + ) +endif() + +if (ENABLE_APPLICATION_BUNDLE) + set_source_files_properties( + filesystem.c + PROPERTIES + COMPILE_FLAGS "${WERROR_COMMON_FLAGS} -DENABLE_APPLICATION_BUNDLE" +) +endif() + +add_library(wsutil + ${WSUTIL_FILES} + ${CMAKE_BINARY_DIR}/resources/libwsutil.rc +) + +if(NOT VCSVERSION_OVERRIDE) + add_dependencies(wsutil vcs_version) +endif() + +target_compile_definitions(wsutil PRIVATE + WS_BUILD_DLL + BUILD_WSUTIL +) + +set_target_properties(wsutil PROPERTIES + PREFIX "lib" + LINK_FLAGS "${WS_LINK_FLAGS}" + VERSION "15.0.0" SOVERSION 15 + FOLDER "DLLs" + INSTALL_RPATH "${LIBRARY_INSTALL_RPATH}" +) +if(MSVC) + set_target_properties(wsutil PROPERTIES LINK_FLAGS_DEBUG "${WS_MSVC_DEBUG_LINK_FLAGS}") +endif() + +target_link_libraries(wsutil + PUBLIC + ${GLIB2_LIBRARIES} + PRIVATE + ${GMODULE2_LIBRARIES} + ${APPLE_CORE_FOUNDATION_LIBRARY} + ${CMAKE_DL_LIBS} + ${GCRYPT_LIBRARIES} + ${GNUTLS_LIBRARIES} + ${ZLIB_LIBRARIES} + $<IF:$<CONFIG:Debug>,${PCRE2_DEBUG_LIBRARIES},${PCRE2_LIBRARIES}> + ${WIN_IPHLPAPI_LIBRARY} + ${WIN_WS2_32_LIBRARY} +) + +target_include_directories(wsutil SYSTEM + PUBLIC + ${GLIB2_INCLUDE_DIRS} + ${GCRYPT_INCLUDE_DIRS} + ${GNUTLS_INCLUDE_DIRS} + PRIVATE + ${GMODULE2_INCLUDE_DIRS} + ${ZLIB_INCLUDE_DIRS} + ${PCRE2_INCLUDE_DIRS} +) + +install(TARGETS wsutil + EXPORT WiresharkTargets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +install(FILES ${WMEM_PUBLIC_HEADERS} + DESTINATION "${PROJECT_INSTALL_INCLUDEDIR}/wsutil/wmem" + COMPONENT "Development" + EXCLUDE_FROM_ALL +) + +install(FILES ${WSUTIL_PUBLIC_HEADERS} + DESTINATION "${PROJECT_INSTALL_INCLUDEDIR}/wsutil" + COMPONENT "Development" + EXCLUDE_FROM_ALL +) + +add_library(wsutil_static STATIC + ${WSUTIL_FILES} +) + +target_compile_definitions(wsutil_static PRIVATE + ENABLE_STATIC + BUILD_WSUTIL +) + +target_link_libraries(wsutil_static + PUBLIC + ${GLIB2_LIBRARIES} + PRIVATE + ${GMODULE2_LIBRARIES} + ${APPLE_CORE_FOUNDATION_LIBRARY} + ${CMAKE_DL_LIBS} + ${GCRYPT_LIBRARIES} + ${GNUTLS_LIBRARIES} + ${ZLIB_LIBRARIES} + $<IF:$<CONFIG:Debug>,${PCRE2_DEBUG_LIBRARIES},${PCRE2_LIBRARIES}> + ${WIN_IPHLPAPI_LIBRARY} + ${WIN_WS2_32_LIBRARY} +) + +target_include_directories(wsutil_static SYSTEM + PUBLIC + ${GLIB2_INCLUDE_DIRS} + ${GCRYPT_INCLUDE_DIRS} + ${GNUTLS_INCLUDE_DIRS} + PRIVATE + ${GMODULE2_INCLUDE_DIRS} + ${ZLIB_INCLUDE_DIRS} + ${PCRE2_INCLUDE_DIRS} +) + +if(NOT VCSVERSION_OVERRIDE) + add_dependencies(wsutil_static vcs_version) +endif() + +add_executable(wmem_test EXCLUDE_FROM_ALL wmem/wmem_test.c ${WMEM_FILES}) + +target_link_libraries(wmem_test wsutil) + +set_target_properties(wmem_test PROPERTIES + FOLDER "Tests" + EXCLUDE_FROM_DEFAULT_BUILD True + COMPILE_DEFINITIONS "WS_BUILD_DLL" + COMPILE_FLAGS "${WERROR_COMMON_FLAGS}" +) + +add_executable(test_wsutil EXCLUDE_FROM_ALL + test_wsutil.c +) + +target_link_libraries(test_wsutil ${GLIB2_LIBRARIES} wsutil) + +set_target_properties(test_wsutil PROPERTIES + FOLDER "Tests" + EXCLUDE_FROM_DEFAULT_BUILD True + COMPILE_FLAGS "${WERROR_COMMON_FLAGS}" +) + +CHECKAPI( + NAME + wsutil + SWITCHES + SOURCES + ${WMEM_FILES} + ${WSUTIL_COMMON_FILES} +) + +set_source_files_properties(jsmn.c PROPERTIES COMPILE_DEFINITIONS "JSMN_STRICT") + +# +# Editor modelines - https://www.wireshark.org/tools/modelines.html +# +# Local variables: +# c-basic-offset: 8 +# tab-width: 8 +# indent-tabs-mode: t +# End: +# +# vi: set shiftwidth=8 tabstop=8 noexpandtab: +# :indentSize=8:tabSize=8:noTabs=false: +# diff --git a/wsutil/adler32.c b/wsutil/adler32.c new file mode 100644 index 00000000..2830eab4 --- /dev/null +++ b/wsutil/adler32.c @@ -0,0 +1,58 @@ +/* adler32.c + * Compute the Adler32 checksum (RFC 1950) + * 2003 Tomas Kukosa + * Based on code from RFC 1950 (Chapter 9. Appendix: Sample code) + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <wsutil/adler32.h> + +#include <string.h> + +#define BASE 65521 /* largest prime smaller than 65536 */ + +/*--- update_adler32 --------------------------------------------------------*/ +uint32_t update_adler32(uint32_t adler, const uint8_t *buf, size_t len) +{ + uint32_t s1 = adler & 0xffff; + uint32_t s2 = (adler >> 16) & 0xffff; + size_t n; + + for (n = 0; n < len; n++) { + s1 = (s1 + buf[n]) % BASE; + s2 = (s2 + s1) % BASE; + } + return (s2 << 16) + s1; +} + +/*--- adler32 ---------------------------------------------------------------*/ +uint32_t adler32_bytes(const uint8_t *buf, size_t len) +{ + return update_adler32(1, buf, len); +} + +/*--- adler32_str -----------------------------------------------------------*/ +uint32_t adler32_str(const char *buf) +{ + return update_adler32(1, (const uint8_t*)buf, strlen(buf)); +} + +/*---------------------------------------------------------------------------*/ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local Variables: + * c-basic-offset: 2 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=2 tabstop=8 expandtab: + * :indentSize=2:tabSize=8:noTabs=true: + */ diff --git a/wsutil/adler32.h b/wsutil/adler32.h new file mode 100644 index 00000000..9ff7c649 --- /dev/null +++ b/wsutil/adler32.h @@ -0,0 +1,30 @@ +/** @file + * Compute the Adler32 checksum (RFC 1950) + * 2003 Tomas Kukosa + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef ADLER32_H +#define ADLER32_H + +#include <wireshark.h> + +#ifdef __cplusplus +extern "C"{ +#endif + +WS_DLL_PUBLIC uint32_t update_adler32(uint32_t adler, const uint8_t *buf, size_t len); +WS_DLL_PUBLIC uint32_t adler32_bytes(const uint8_t *buf, size_t len); +WS_DLL_PUBLIC uint32_t adler32_str(const char *buf); + +#ifdef __cplusplus +} +#endif + +#endif /* ADLER32_H */ + diff --git a/wsutil/base32.c b/wsutil/base32.c new file mode 100644 index 00000000..ad8b800c --- /dev/null +++ b/wsutil/base32.c @@ -0,0 +1,68 @@ +/* base32.c + * Base-32 conversion + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" +#include "base32.h" + +#include <string.h> + +/* + * Cjdns style base32 encoding + */ + +/** Returned by ws_base32_encode() if the input is not valid base32. */ +#define Base32_BAD_INPUT -1 +/** Returned by ws_base32_encode() if the output buffer is too small. */ +#define Base32_TOO_BIG -2 + +int ws_base32_decode(uint8_t* output, const uint32_t outputLength, + const uint8_t* in, const uint32_t inputLength) +{ + uint32_t outIndex = 0; + uint32_t inIndex = 0; + uint32_t work = 0; + uint32_t bits = 0; + static const uint8_t* kChars = (uint8_t*) "0123456789bcdfghjklmnpqrstuvwxyz"; + while (inIndex < inputLength) { + work |= ((unsigned) in[inIndex++]) << bits; + bits += 8; + while (bits >= 5) { + if (outIndex >= outputLength) { + return Base32_TOO_BIG; + } + output[outIndex++] = kChars[work & 31]; + bits -= 5; + work >>= 5; + } + } + if (bits) { + if (outIndex >= outputLength) { + return Base32_TOO_BIG; + } + output[outIndex++] = kChars[work & 31]; + } + if (outIndex < outputLength) { + output[outIndex] = '\0'; + } + return outIndex; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 8 + * tab-width: 8 + * indent-tabs-mode: t + * End: + * + * vi: set shiftwidth=8 tabstop=8 noexpandtab: + * :indentSize=8:tabSize=8:noTabs=false: + */ diff --git a/wsutil/base32.h b/wsutil/base32.h new file mode 100644 index 00000000..ec1b244e --- /dev/null +++ b/wsutil/base32.h @@ -0,0 +1,46 @@ +/** @file + * Base-32 conversion + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#ifndef __BASE32_H__ +#define __BASE32_H__ + +#include <wireshark.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** Returned by base32_decode() if the input is not valid base32. */ +#define Base32_BAD_INPUT -1 +/** Returned by base32_decode() if the output buffer is too small. */ +#define Base32_TOO_BIG -2 + +/* Encoding of a base32 byte array */ +WS_DLL_PUBLIC +int ws_base32_decode(uint8_t* output, const uint32_t outputLength, + const uint8_t* in, const uint32_t inputLength); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __BASE32_H__ */ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/bits_count_ones.h b/wsutil/bits_count_ones.h new file mode 100644 index 00000000..1b4f1d68 --- /dev/null +++ b/wsutil/bits_count_ones.h @@ -0,0 +1,51 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WSUTIL_BITS_COUNT_ONES_H__ +#define __WSUTIL_BITS_COUNT_ONES_H__ + +#include <inttypes.h> + +/* + * The variable-precision SWAR algorithm is an interesting way to count + * the number of bits set in an integer: + * + * https://www.playingwithpointers.com/blog/swar.html + * + * See + * + * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=36041 + * https://danluu.com/assembly-intrinsics/ + * + * for discussions of various forms of population-counting code on x86. + * + * See + * + * https://docs.microsoft.com/en-us/cpp/intrinsics/popcnt16-popcnt-popcnt64 + * + * for MSVC's population count intrinsics. + * + * Note that not all x86 processors support the POPCOUNT instruction. + * + * Other CPUs may have population count instructions as well. + */ + +static inline int +ws_count_ones(const uint64_t x) +{ + uint64_t bits = x; + + bits = bits - ((bits >> 1) & G_GUINT64_CONSTANT(0x5555555555555555)); + bits = (bits & G_GUINT64_CONSTANT(0x3333333333333333)) + ((bits >> 2) & G_GUINT64_CONSTANT(0x3333333333333333)); + bits = (bits + (bits >> 4)) & G_GUINT64_CONSTANT(0x0F0F0F0F0F0F0F0F); + + return (int)((bits * G_GUINT64_CONSTANT(0x0101010101010101)) >> 56); +} + +#endif /* __WSUTIL_BITS_COUNT_ONES_H__ */ diff --git a/wsutil/bits_ctz.h b/wsutil/bits_ctz.h new file mode 100644 index 00000000..b4c43e3f --- /dev/null +++ b/wsutil/bits_ctz.h @@ -0,0 +1,92 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WSUTIL_BITS_CTZ_H__ +#define __WSUTIL_BITS_CTZ_H__ + +#include <inttypes.h> + +/* ws_ctz == trailing zeros == position of lowest set bit [0..63] */ +/* ws_ilog2 == position of highest set bit == 63 - leading zeros [0..63] */ + +/* The return value of both ws_ctz and ws_ilog2 is undefined for x == 0 */ + +#if defined(__GNUC__) && ((__GNUC__ > 3) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)) + +static inline int +ws_ctz(uint64_t x) +{ + return __builtin_ctzll(x); +} + +static inline int +ws_ilog2(uint64_t x) +{ + return 63 - __builtin_clzll(x); +} + +#else + +static inline int +__ws_ctz32(uint32_t x) +{ + /* From http://graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightMultLookup */ + static const uint8_t table[32] = { + 0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, + 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9 + }; + + return table[((uint32_t)((x & -(int32_t)x) * 0x077CB531U)) >> 27]; +} + +static inline int +ws_ctz(uint64_t x) +{ + uint32_t hi = x >> 32; + uint32_t lo = (uint32_t) x; + + if (lo == 0) + return 32 + __ws_ctz32(hi); + else + return __ws_ctz32(lo); +} + +static inline int +__ws_ilog2_32(uint32_t x) +{ + /* From http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogDeBruijn */ + static const uint8_t table[32] = { + 0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, + 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31 + }; + + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + + return table[((uint32_t)(x * 0x07C4ACDDU)) >> 27]; +} + +static inline int +ws_ilog2(uint64_t x) +{ + uint32_t hi = x >> 32; + uint32_t lo = (uint32_t) x; + + if (hi == 0) + return __ws_ilog2_32(lo); + else + return 32 + __ws_ilog2_32(hi); +} + +#endif + +#endif /* __WSUTIL_BITS_CTZ_H__ */ diff --git a/wsutil/bitswap.c b/wsutil/bitswap.c new file mode 100644 index 00000000..33f78485 --- /dev/null +++ b/wsutil/bitswap.c @@ -0,0 +1,71 @@ +/* bitswap.c + * Table of bit-swapped values of bytes + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" +#include "bitswap.h" + +/* "swaptab[i]" is the value of "i" with the bits reversed. */ +static const uint8_t swaptab[256] = +{ + 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, + 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, + 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, + 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, + 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, + 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, + 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, + 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, + 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, + 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, + 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, + 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, + 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, + 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6, + 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, + 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, + 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, + 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, + 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, + 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, + 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, + 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, + 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, + 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, + 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, + 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, + 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, + 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, + 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, + 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, + 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, + 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff, +}; + +void bitswap_buf_inplace(uint8_t *buf, size_t len) +{ + size_t i; + + for (i = 0; i < len; i++) + buf[i] = swaptab[buf[i]]; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local Variables: + * c-basic-offset: 2 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=2 tabstop=8 expandtab: + * :indentSize=2:tabSize=8:noTabs=true: + */ diff --git a/wsutil/bitswap.h b/wsutil/bitswap.h new file mode 100644 index 00000000..7e836609 --- /dev/null +++ b/wsutil/bitswap.h @@ -0,0 +1,26 @@ +/** @file + * Macro to bitswap a byte by looking it up in a table + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __BITSWAP_H__ +#define __BITSWAP_H__ + +#include <wireshark.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +WS_DLL_PUBLIC void bitswap_buf_inplace(uint8_t *buf, size_t len); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* bitswap.h */ diff --git a/wsutil/buffer.c b/wsutil/buffer.c new file mode 100644 index 00000000..95330086 --- /dev/null +++ b/wsutil/buffer.c @@ -0,0 +1,206 @@ +/* buffer.c + * + * Wiretap Library + * Copyright (c) 1998 by Gilbert Ramirez <gram@alumni.rice.edu> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#include "config.h" +#define WS_LOG_DOMAIN LOG_DOMAIN_WSUTIL +#include "buffer.h" + +#include <stdlib.h> +#include <string.h> + +#include <wsutil/ws_assert.h> +#include <wsutil/wslog.h> + +#define SMALL_BUFFER_SIZE (2 * 1024) /* Everyone still uses 1500 byte frames, right? */ +static GPtrArray *small_buffers = NULL; /* Guaranteed to be at least SMALL_BUFFER_SIZE */ +/* XXX - Add medium and large buffers? */ + +/* Initializes a buffer with a certain amount of allocated space */ +void +ws_buffer_init(Buffer* buffer, size_t space) +{ + ws_assert(buffer); + if (G_UNLIKELY(!small_buffers)) small_buffers = g_ptr_array_sized_new(1024); + + if (space <= SMALL_BUFFER_SIZE) { + if (small_buffers->len > 0) { + buffer->data = (uint8_t*) g_ptr_array_remove_index(small_buffers, small_buffers->len - 1); + ws_assert(buffer->data); + } else { + buffer->data = (uint8_t*)g_malloc(SMALL_BUFFER_SIZE); + } + buffer->allocated = SMALL_BUFFER_SIZE; + } else { + buffer->data = (uint8_t*)g_malloc(space); + buffer->allocated = space; + } + buffer->start = 0; + buffer->first_free = 0; +} + +/* Frees the memory used by a buffer */ +void +ws_buffer_free(Buffer* buffer) +{ + ws_assert(buffer); + if (buffer->allocated == SMALL_BUFFER_SIZE) { + ws_assert(buffer->data); + g_ptr_array_add(small_buffers, buffer->data); + } else { + g_free(buffer->data); + } + buffer->allocated = 0; + buffer->data = NULL; +} + +/* Assures that there are 'space' bytes at the end of the used space + so that another routine can copy directly into the buffer space. After + doing that, the routine will also want to run + ws_buffer_increase_length(). */ +void +ws_buffer_assure_space(Buffer* buffer, size_t space) +{ + ws_assert(buffer); + size_t available_at_end = buffer->allocated - buffer->first_free; + size_t space_used; + bool space_at_beginning; + + /* If we've got the space already, good! */ + if (space <= available_at_end) { + return; + } + + /* Maybe we don't have the space available at the end, but we would + if we moved the used space back to the beginning of the + allocation. The buffer could have become fragmented through lots + of calls to ws_buffer_remove_start(). I'm using buffer->start as the + same as 'available_at_start' in this comparison. */ + + /* or maybe there's just no more room. */ + + space_at_beginning = buffer->start >= space; + if (space_at_beginning || buffer->start > 0) { + space_used = buffer->first_free - buffer->start; + /* this memory copy better be safe for overlapping memory regions! */ + memmove(buffer->data, buffer->data + buffer->start, space_used); + buffer->start = 0; + buffer->first_free = space_used; + } + /*if (buffer->start >= space) {*/ + if (space_at_beginning) { + return; + } + + /* We'll allocate more space */ + buffer->allocated += space + 1024; + buffer->data = (uint8_t*)g_realloc(buffer->data, buffer->allocated); +} + +void +ws_buffer_append(Buffer* buffer, uint8_t *from, size_t bytes) +{ + ws_assert(buffer); + ws_buffer_assure_space(buffer, bytes); + memcpy(buffer->data + buffer->first_free, from, bytes); + buffer->first_free += bytes; +} + +void +ws_buffer_remove_start(Buffer* buffer, size_t bytes) +{ + ws_assert(buffer); + if (buffer->start + bytes > buffer->first_free) { + ws_error("ws_buffer_remove_start trying to remove %" PRIu64 " bytes. s=%" PRIu64 " ff=%" PRIu64 "!\n", + (uint64_t)bytes, (uint64_t)buffer->start, + (uint64_t)buffer->first_free); + /** ws_error() does an abort() and thus never returns **/ + } + buffer->start += bytes; + + if (buffer->start == buffer->first_free) { + buffer->start = 0; + buffer->first_free = 0; + } +} + + +#ifndef SOME_FUNCTIONS_ARE_DEFINES +void +ws_buffer_clean(Buffer* buffer) +{ + ws_assert(buffer); + ws_buffer_remove_start(buffer, ws_buffer_length(buffer)); +} +#endif + +#ifndef SOME_FUNCTIONS_ARE_DEFINES +void +ws_buffer_increase_length(Buffer* buffer, size_t bytes) +{ + ws_assert(buffer); + buffer->first_free += bytes; +} +#endif + +#ifndef SOME_FUNCTIONS_ARE_DEFINES +size_t +ws_buffer_length(Buffer* buffer) +{ + ws_assert(buffer); + return buffer->first_free - buffer->start; +} +#endif + +#ifndef SOME_FUNCTIONS_ARE_DEFINES +uint8_t * +ws_buffer_start_ptr(Buffer* buffer) +{ + ws_assert(buffer); + return buffer->data + buffer->start; +} +#endif + +#ifndef SOME_FUNCTIONS_ARE_DEFINES +uint8_t * +ws_buffer_end_ptr(Buffer* buffer) +{ + ws_assert(buffer); + return buffer->data + buffer->first_free; +} +#endif + +#ifndef SOME_FUNCTIONS_ARE_DEFINES +void +ws_buffer_append_buffer(Buffer* buffer, Buffer* src_buffer) +{ + ws_assert(buffer); + ws_buffer_append(buffer, ws_buffer_start_ptr(src_buffer), ws_buffer_length(src_buffer)); +} +#endif + +void +ws_buffer_cleanup(void) +{ + if (small_buffers) { + g_ptr_array_set_free_func(small_buffers, g_free); + g_ptr_array_free(small_buffers, true); + small_buffers = NULL; + } +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 8 + * tab-width: 8 + * indent-tabs-mode: t + * End: + * + * vi: set shiftwidth=8 tabstop=8 noexpandtab: + * :indentSize=8:tabSize=8:noTabs=false: + */ diff --git a/wsutil/buffer.h b/wsutil/buffer.h new file mode 100644 index 00000000..628d324c --- /dev/null +++ b/wsutil/buffer.h @@ -0,0 +1,68 @@ +/** @file + * + * Wiretap Library + * Copyright (c) 1998 by Gilbert Ramirez <gram@alumni.rice.edu> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __W_BUFFER_H__ +#define __W_BUFFER_H__ + +#include <inttypes.h> +#include <stddef.h> +#include "ws_symbol_export.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#define SOME_FUNCTIONS_ARE_DEFINES + +typedef struct Buffer { + uint8_t *data; + size_t allocated; + size_t start; + size_t first_free; +} Buffer; + +WS_DLL_PUBLIC +void ws_buffer_init(Buffer* buffer, size_t space); +WS_DLL_PUBLIC +void ws_buffer_free(Buffer* buffer); +WS_DLL_PUBLIC +void ws_buffer_assure_space(Buffer* buffer, size_t space); +WS_DLL_PUBLIC +void ws_buffer_append(Buffer* buffer, uint8_t *from, size_t bytes); +WS_DLL_PUBLIC +void ws_buffer_remove_start(Buffer* buffer, size_t bytes); +WS_DLL_PUBLIC +void ws_buffer_cleanup(void); + +#ifdef SOME_FUNCTIONS_ARE_DEFINES +# define ws_buffer_clean(buffer) ws_buffer_remove_start((buffer), ws_buffer_length(buffer)) +# define ws_buffer_increase_length(buffer,bytes) (buffer)->first_free += (bytes) +# define ws_buffer_length(buffer) ((buffer)->first_free - (buffer)->start) +# define ws_buffer_start_ptr(buffer) ((buffer)->data + (buffer)->start) +# define ws_buffer_end_ptr(buffer) ((buffer)->data + (buffer)->first_free) +# define ws_buffer_append_buffer(buffer,src_buffer) ws_buffer_append((buffer), ws_buffer_start_ptr(src_buffer), ws_buffer_length(src_buffer)) +#else + WS_DLL_PUBLIC + void ws_buffer_clean(Buffer* buffer); + WS_DLL_PUBLIC + void ws_buffer_increase_length(Buffer* buffer, size_t bytes); + WS_DLL_PUBLIC + size_t ws_buffer_length(Buffer* buffer); + WS_DLL_PUBLIC + uint8_t* ws_buffer_start_ptr(Buffer* buffer); + WS_DLL_PUBLIC + uint8_t* ws_buffer_end_ptr(Buffer* buffer); + WS_DLL_PUBLIC + void ws_buffer_append_buffer(Buffer* buffer, Buffer* src_buffer); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif diff --git a/wsutil/cfutils.c b/wsutil/cfutils.c new file mode 100644 index 00000000..5693411a --- /dev/null +++ b/wsutil/cfutils.c @@ -0,0 +1,51 @@ +/* cfutils.c + * Routines to work around deficiencies in Core Foundation, such as the + * lack of a routine to convert a CFString to a C string of arbitrary + * size. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 2001 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" + +#include <glib.h> +#include <CoreFoundation/CoreFoundation.h> +#include <wsutil/cfutils.h> + +/* + * Convert a CFString to a UTF-8-encoded C string; the resulting string + * is allocated with g_malloc(). Returns NULL if the conversion fails. + */ +char * +CFString_to_C_string(CFStringRef cfstring) +{ + CFIndex string_len; + char *string; + + string_len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstring), + kCFStringEncodingUTF8); + string = (char *)g_malloc(string_len + 1); + if (!CFStringGetCString(cfstring, string, string_len + 1, + kCFStringEncodingUTF8)) { + g_free(string); + return NULL; + } + return string; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 8 + * tab-width: 8 + * indent-tabs-mode: t + * End: + * + * vi: set shiftwidth=8 tabstop=8 noexpandtab: + * :indentSize=8:tabSize=8:noTabs=false: + */ diff --git a/wsutil/cfutils.h b/wsutil/cfutils.h new file mode 100644 index 00000000..12cbefef --- /dev/null +++ b/wsutil/cfutils.h @@ -0,0 +1,31 @@ +/** @file + * Declarations of routines to work around deficiencies in Core Foundation, + * such as the lack of a routine to convert a CFString to a C string of + * arbitrary size. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 2001 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WSUTIL_CFUTILS_H__ +#define __WSUTIL_CFUTILS_H__ + +#include "ws_symbol_export.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * Convert a CFString to a g_malloc()ated C string. + */ +WS_DLL_PUBLIC char *CFString_to_C_string(CFStringRef cfstring); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __WSUTIL_CFUTILS_H__ */ diff --git a/wsutil/clopts_common.c b/wsutil/clopts_common.c new file mode 100644 index 00000000..fa17f8e3 --- /dev/null +++ b/wsutil/clopts_common.c @@ -0,0 +1,108 @@ +/* clopts_common.c + * Handle command-line arguments common to various programs + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" + +#include "clopts_common.h" + +#include <stdlib.h> +#include <errno.h> + +#include <wsutil/strtoi.h> +#include <wsutil/cmdarg_err.h> + +int +get_natural_int(const char *string, const char *name) +{ + int32_t number; + + if (!ws_strtoi32(string, NULL, &number)) { + if (errno == EINVAL) { + cmdarg_err("The specified %s \"%s\" isn't a decimal number", name, string); + exit(1); + } + if (number < 0) { + cmdarg_err("The specified %s \"%s\" is a negative number", name, string); + exit(1); + } + cmdarg_err("The specified %s \"%s\" is too large (greater than %d)", + name, string, number); + exit(1); + } + if (number < 0) { + cmdarg_err("The specified %s \"%s\" is a negative number", name, string); + exit(1); + } + return (int)number; +} + +int +get_positive_int(const char *string, const char *name) +{ + int number; + + number = get_natural_int(string, name); + + if (number == 0) { + cmdarg_err("The specified %s is zero", name); + exit(1); + } + + return number; +} + +uint32_t +get_guint32(const char *string, const char *name) +{ + uint32_t number; + + if (!ws_strtou32(string, NULL, &number)) { + if (errno == EINVAL) { + cmdarg_err("The specified %s \"%s\" isn't a decimal number", name, string); + exit(1); + } + cmdarg_err("The specified %s \"%s\" is too large (greater than %d)", + name, string, number); + exit(1); + } + return number; +} + +uint32_t +get_nonzero_guint32(const char *string, const char *name) +{ + uint32_t number; + + number = get_guint32(string, name); + + if (number == 0) { + cmdarg_err("The specified %s is zero", name); + exit(1); + } + + return number; +} + +double +get_positive_double(const char *string, const char *name) +{ + double number = g_ascii_strtod(string, NULL); + + if (errno == EINVAL) { + cmdarg_err("The specified %s \"%s\" isn't a floating point number", name, string); + exit(1); + } + if (number < 0.0) { + cmdarg_err("The specified %s \"%s\" is a negative number", name, string); + exit(1); + } + + return number; +} diff --git a/wsutil/clopts_common.h b/wsutil/clopts_common.h new file mode 100644 index 00000000..3f8b7f71 --- /dev/null +++ b/wsutil/clopts_common.h @@ -0,0 +1,56 @@ +/** @file + * + * Handle command-line arguments common to various programs + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __CLOPTS_COMMON_H__ +#define __CLOPTS_COMMON_H__ + +#include <wireshark.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * Long options. + * For long options with no corresponding short options, we define values + * outside the range of ASCII graphic characters, make that the last + * component of the entry for the long option, and have a case for that + * option in the switch statement. + */ +// Base value for capture related long options +#define LONGOPT_BASE_CAPTURE 1000 +// Base value for dissector related long options +#define LONGOPT_BASE_DISSECTOR 2000 +// Base value for application specific long options +#define LONGOPT_BASE_APPLICATION 3000 +// Base value for GUI specific long options +#define LONGOPT_BASE_GUI 4000 + +WS_DLL_PUBLIC int +get_natural_int(const char *string, const char *name); + +WS_DLL_PUBLIC int +get_positive_int(const char *string, const char *name); + +WS_DLL_PUBLIC uint32_t +get_guint32(const char *string, const char *name); + +WS_DLL_PUBLIC uint32_t +get_nonzero_guint32(const char *string, const char *name); + +WS_DLL_PUBLIC double +get_positive_double(const char *string, const char *name); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __CLOPTS_COMMON_H__ */ diff --git a/wsutil/cmdarg_err.c b/wsutil/cmdarg_err.c new file mode 100644 index 00000000..85a4d8d4 --- /dev/null +++ b/wsutil/cmdarg_err.c @@ -0,0 +1,59 @@ +/* cmdarg_err.c + * Routines to report command-line argument errors. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" + +#include "cmdarg_err.h" + +static void (*print_err)(const char *, va_list ap); +static void (*print_err_cont)(const char *, va_list ap); + +/* + * Set the reporting functions for error messages. + */ +void +cmdarg_err_init(void (*err)(const char *, va_list), + void (*err_cont)(const char *, va_list)) +{ + print_err = err; + print_err_cont = err_cont; +} + +/* + * Report an error in command-line arguments. + */ +void +vcmdarg_err(const char *fmt, va_list ap) +{ + print_err(fmt, ap); +} + +void +cmdarg_err(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + print_err(fmt, ap); + va_end(ap); +} + +/* + * Report additional information for an error in command-line arguments. + */ +void +cmdarg_err_cont(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + print_err_cont(fmt, ap); + va_end(ap); +} diff --git a/wsutil/cmdarg_err.h b/wsutil/cmdarg_err.h new file mode 100644 index 00000000..daa50524 --- /dev/null +++ b/wsutil/cmdarg_err.h @@ -0,0 +1,51 @@ +/** @file + * + * Declarations of routines to report command-line argument errors. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __CMDARG_ERR_H__ +#define __CMDARG_ERR_H__ + +#include <wireshark.h> +#include <stdarg.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * Set the reporting functions for error messages. + */ +WS_DLL_PUBLIC void +cmdarg_err_init(void (*err)(const char *, va_list), + void (*err_cont)(const char *, va_list)); + +/* + * Report an error in command-line arguments. + */ +WS_DLL_PUBLIC void +vcmdarg_err(const char *fmt, va_list ap) + G_GNUC_PRINTF(1, 0); + +WS_DLL_PUBLIC void +cmdarg_err(const char *fmt, ...) + G_GNUC_PRINTF(1, 2); + +/* + * Report additional information for an error in command-line arguments. + */ +WS_DLL_PUBLIC void +cmdarg_err_cont(const char *fmt, ...) + G_GNUC_PRINTF(1, 2); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __CMDARG_ERR_H__ */ diff --git a/wsutil/codecs.c b/wsutil/codecs.c new file mode 100644 index 00000000..857cd33b --- /dev/null +++ b/wsutil/codecs.c @@ -0,0 +1,194 @@ +/* codecs.c + * codecs interface 2007 Tomas Kukosa + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" + +#include "codecs.h" + +#include <wsutil/wslog.h> +#ifdef HAVE_PLUGINS +#include <wsutil/plugins.h> +#endif + +#ifdef HAVE_PLUGINS +static plugins_t *libwscodecs_plugins = NULL; +#endif + +static GSList *codecs_plugins = NULL; + +#ifdef HAVE_PLUGINS +void +codecs_register_plugin(const codecs_plugin *plug) +{ + codecs_plugins = g_slist_prepend(codecs_plugins, (codecs_plugin *)plug); +} +#else /* HAVE_PLUGINS */ +void +codecs_register_plugin(const codecs_plugin *plug _U_) +{ + ws_warning("codecs_register_plugin: built without support for binary plugins"); +} +#endif /* HAVE_PLUGINS */ + +static void +call_plugin_register_codec_module(void * data, void * user_data _U_) +{ + codecs_plugin *plug = (codecs_plugin *)data; + + if (plug->register_codec_module) { + plug->register_codec_module(); + } +} + + +/* + * For all codec plugins, call their register routines. + */ +void +codecs_init(void) +{ +#ifdef HAVE_PLUGINS + libwscodecs_plugins = plugins_init(WS_PLUGIN_CODEC); +#endif + g_slist_foreach(codecs_plugins, call_plugin_register_codec_module, NULL); +} + +void +codecs_cleanup(void) +{ + g_slist_free(codecs_plugins); + codecs_plugins = NULL; +#ifdef HAVE_PLUGINS + plugins_cleanup(libwscodecs_plugins); + libwscodecs_plugins = NULL; +#endif +} + + +struct codec_handle { + const char *name; + codec_init_fn init_fn; + codec_release_fn release_fn; + codec_get_channels_fn channels_fn; + codec_get_frequency_fn frequency_fn; + codec_decode_fn decode_fn; +}; + +/* + * List of registered codecs. + */ +static GHashTable *registered_codecs = NULL; + + +/* Find a registered codec by name. */ +codec_handle_t +find_codec(const char *name) +{ + codec_handle_t ret; + char *key = g_ascii_strup(name, -1); + + ret = (registered_codecs) ? (codec_handle_t)g_hash_table_lookup(registered_codecs, key) : NULL; + g_free(key); + return ret; +} + +/* Register a codec by name. */ +bool +register_codec(const char *name, codec_init_fn init_fn, codec_release_fn release_fn, + codec_get_channels_fn channels_fn, codec_get_frequency_fn frequency_fn, + codec_decode_fn decode_fn) +{ + struct codec_handle *handle; + char *key; + + /* Create our hash table if it doesn't already exist */ + if (registered_codecs == NULL) + registered_codecs = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + + /* RFC 4855 3. Mapping to SDP Parameters "Note that the payload format + * (encoding) names... are commonly shown in upper case. These names + * are case-insensitive in both places." + */ + key = g_ascii_strup(name, -1); + + /* Make sure the registration is unique */ + if (g_hash_table_lookup(registered_codecs, key) != NULL) { + g_free(key); + return false; /* report an error, or have our caller do it? */ + } + + handle = g_new(struct codec_handle, 1); + handle->name = name; + handle->init_fn = init_fn; + handle->release_fn = release_fn; + handle->channels_fn = channels_fn; + handle->frequency_fn = frequency_fn; + handle->decode_fn = decode_fn; + + g_hash_table_insert(registered_codecs, (void *)key, (void *) handle); + return true; +} + +/* Deregister a codec by name. */ +bool +deregister_codec(const char *name) +{ + bool ret = false; + if (registered_codecs) { + char *key = g_ascii_strup(name, -1); + + ret = g_hash_table_remove(registered_codecs, key); + g_free(key); + } + return ret; +} + +void *codec_init(codec_handle_t codec, codec_context_t *context) +{ + if (!codec) return NULL; + return (codec->init_fn)(context); +} + +void codec_release(codec_handle_t codec, codec_context_t *context) +{ + if (!codec) return; + (codec->release_fn)(context); +} + +unsigned codec_get_channels(codec_handle_t codec, codec_context_t *context) +{ + if (!codec) return 0; + return (codec->channels_fn)(context); +} + +unsigned codec_get_frequency(codec_handle_t codec, codec_context_t *context) +{ + if (!codec) return 0; + return (codec->frequency_fn)(context); +} + +size_t codec_decode(codec_handle_t codec, codec_context_t *context, const void *input, size_t inputSizeBytes, void *output, size_t *outputSizeBytes) +{ + if (!codec) return 0; + return (codec->decode_fn)(context, input, inputSizeBytes, output, outputSizeBytes); +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/codecs.h b/wsutil/codecs.h new file mode 100644 index 00000000..be340bc7 --- /dev/null +++ b/wsutil/codecs.h @@ -0,0 +1,149 @@ +/** @file + * codecs interface 2007 Tomas Kukosa + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef _CODECS_H_ +#define _CODECS_H_ + +#include "ws_symbol_export.h" +#include "ws_attributes.h" + +#include <stdbool.h> + +#include "wsutil/wmem/wmem_map.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct { + void (*register_codec_module)(void); /* routine to call to register a codec */ +} codecs_plugin; + +WS_DLL_PUBLIC void codecs_register_plugin(const codecs_plugin *plug); + +/** + * For all built-in codecs and codec plugins, call their register routines. + */ +WS_DLL_PUBLIC void codecs_init(void); + +WS_DLL_PUBLIC void codecs_cleanup(void); + +/** + * Get compile-time information for libraries used by libwscodecs. + */ +WS_DLL_PUBLIC void codec_get_compiled_version_info(GString *str); + +struct codec_handle; +typedef struct codec_handle *codec_handle_t; + +typedef struct _codec_context_t { + unsigned sample_rate; + unsigned channels; + wmem_map_t *fmtp_map; + void *priv; /* Private state set by the decoder */ +} codec_context_t; + +/*****************************************************************************/ +/* Interface which must be implemented by a codec */ +/* Codec decodes bytes to samples. Sample is 2 bytes! Codec writer must + * be careful when API refers bytes and when samples and its counts. + */ +/*****************************************************************************/ + +/** Initialize context of codec. + * Context can contain any information required by codec to pass between calls + * Note: There is just one codec context in runtime therefore no RTP stream + * related information should be stored in the context! + * + * @return Pointer to codec context + */ +typedef void *(*codec_init_fn)(codec_context_t *context); + +/** Destroy context of codec + * + * @param context Pointer to codec context + */ +typedef void (*codec_release_fn)(codec_context_t *context); + +/** Get count of channels provided by the codec + * + * @param context Pointer to codec context + * + * @return Count of channels (e.g. 1) + */ +typedef unsigned (*codec_get_channels_fn)(codec_context_t *context); + +/** Get frequency/rate provided by the codec + * + * @param context Pointer to codec context + * + * @return Frequency (e.g. 8000) + */ +typedef unsigned (*codec_get_frequency_fn)(codec_context_t *context); + +/** Decode one frame of payload + * Function is called twice, with different values of parameters: + * (1) To query size of required buffer in bytes for decoded samples + * pointed by inputBytes: + * outputSamples or outputSamplesSize must be set NULL + * (2) To decode samples: + * outputSamples points to allocated memory, outputSamplesSize is set to + * value returned in step (1) + * + * @param context Pointer to codec context + * @param inputBytes Pointer to input frame + * @param inputBytesSize Length of input frame in bytes + * (count of bytes to decode) + * @param outputSamples Pointer to output buffer with samples + * @param outputSamplesSize Length of output buffer in bytes (not samples!) + * Function can override this value. All codecs set it to same value as it returns in (2) when (2) is called. + * + * @return Count of reqired bytes (!not samples) to allocate in (1) or + * Count of decoded bytes (!not samples) in (2) + */ +typedef size_t (*codec_decode_fn)(codec_context_t *context, + const void *inputBytes, size_t inputBytesSize, + void *outputSamples, size_t *outputSamplesSize); + +/*****************************************************************************/ +/* Codec registering interface */ +/*****************************************************************************/ + +WS_DLL_PUBLIC bool register_codec(const char *name, codec_init_fn init_fn, + codec_release_fn release_fn, codec_get_channels_fn channels_fn, + codec_get_frequency_fn frequency_fn, codec_decode_fn decode_fn); +WS_DLL_PUBLIC bool deregister_codec(const char *name); +WS_DLL_PUBLIC codec_handle_t find_codec(const char *name); +WS_DLL_PUBLIC void *codec_init(codec_handle_t codec, codec_context_t *context); +WS_DLL_PUBLIC void codec_release(codec_handle_t codec, codec_context_t *context); +WS_DLL_PUBLIC unsigned codec_get_channels(codec_handle_t codec, codec_context_t *context); +WS_DLL_PUBLIC unsigned codec_get_frequency(codec_handle_t codec, codec_context_t *context); +WS_DLL_PUBLIC size_t codec_decode(codec_handle_t codec, codec_context_t *context, + const void *inputBytes, size_t inputBytesSize, + void *outputSamples, size_t *outputSamplesSize); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _CODECS_H_ */ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/color.h b/wsutil/color.h new file mode 100644 index 00000000..794ba054 --- /dev/null +++ b/wsutil/color.h @@ -0,0 +1,57 @@ +/** @file + * + * Definitions for colors + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#ifndef __COLOR_H__ +#define __COLOR_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#include <inttypes.h> + +/* + * Data structure holding RGB value for a color, 16 bits per channel. + */ +typedef struct { + uint16_t red; + uint16_t green; + uint16_t blue; +} color_t; + +/* + * Convert a color_t to a 24-bit RGB value, reducing each channel to + * 8 bits and combining them. + */ +inline static unsigned int +color_t_to_rgb(const color_t *color) { + return (((color->red >> 8) << 16) + | ((color->green >> 8) << 8) + | (color->blue >> 8)); +} + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/console_win32.c b/wsutil/console_win32.c new file mode 100644 index 00000000..eed18c88 --- /dev/null +++ b/wsutil/console_win32.c @@ -0,0 +1,295 @@ +/* console_win32.c + * Console support for MSWindows + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 2002, Jeffrey C. Foster <jfoste@woodward.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifdef _WIN32 + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#include <glib.h> +#include <wsutil/file_util.h> + +#include "console_win32.h" + +#include <fcntl.h> +#include <conio.h> +#include <windows.h> +#include <tchar.h> + +static bool has_console; /* true if app has console */ +static bool console_wait; /* "Press any key..." */ +static bool stdin_capture = false; /* Don't grab stdin & stdout if true */ + +/* + * Check whether a given standard handle needs to be redirected. + * + * If you run a Windows-subsystem program from cmd.exe on Windows XP, + * and you haven't redirected the handle in question, GetStdHandle() + * succeeds (so it doesn't return INVALID_HANDLE_VALUE or NULL), but + * GetFile_type fails on the results with ERROR_INVALID_HANDLE. + * In that case, redirection to a console is necessary. + * + * If you run it from the shell prompt in "mintty" in at least some + * versions of Cygwin on Windows XP, and you haven't redirected the + * handle in question, GetStdHandle() succeeds and returns a handle + * that's a pipe or socket; it appears mintty reads from it and outputs + * what it reads to the console. + */ +static bool +needs_redirection(int std_handle) +{ + HANDLE fd; + DWORD handle_type; + DWORD error; + + fd = GetStdHandle(std_handle); + if (fd == NULL) { + /* + * No standard handle. According to Microsoft's + * documentation for GetStdHandle(), one reason for + * this would be that the process is "a service on + * an interactive desktop"; I'm not sure whether + * such a process should be popping up a console. + * + * However, it also appears to be the case for + * the standard input and standard error, but + * *not* the standard output, for something run + * with a double-click in Windows Explorer, + * sow we'll say it needs redirection. + */ + return true; + } + if (fd == INVALID_HANDLE_VALUE) { + /* + * OK, I'm not when this would happen; return + * "no redirection" for now. + */ + return false; + } + handle_type = GetFileType(fd); + if (handle_type == FILE_TYPE_UNKNOWN) { + error = GetLastError(); + if (error == ERROR_INVALID_HANDLE) { + /* + * OK, this appears to be the case where we're + * running something in a mode that needs a + * console. + */ + return true; + } + } + + /* + * Assume no redirection is needed for all other cases. + */ + return false; +} + +/* + * If this application has no console window to which its standard output + * would go, create one. + */ +void +create_console(void) +{ + bool must_redirect_stdin; + bool must_redirect_stdout; + bool must_redirect_stderr; + + if (stdin_capture) { + /* We've been handed "-i -". Don't mess with stdio. */ + return; + } + + if (has_console) { + return; + } + + /* Are the standard input, output, and error invalid handles? */ + must_redirect_stdin = needs_redirection(STD_INPUT_HANDLE); + must_redirect_stdout = needs_redirection(STD_OUTPUT_HANDLE); + must_redirect_stderr = needs_redirection(STD_ERROR_HANDLE); + + /* If none of them are invalid, we don't need to do anything. */ + if (!must_redirect_stdin && !must_redirect_stdout && !must_redirect_stderr) + return; + + /* OK, at least one of them needs to be redirected to a console; + try to attach to the parent process's console and, if that fails, + try to create one. */ + /* + * See if we have an existing console (i.e. we were run from a + * command prompt). + */ + if (!AttachConsole(ATTACH_PARENT_PROCESS)) { + /* Probably not, as we couldn't attach to the parent process's + console. + Try to create a console. + + According to a comment on + +http://msdn.microsoft.com/en-us/library/windows/desktop/ms681952(v=vs.85).aspx + + (which now redirects to a docs.microsoft.com page that is + devoid of comments, and which is not available on the + Wayback Machine) + + and according to + +http://connect.microsoft.com/VisualStudio/feedback/details/689696/installing-security-update-kb2507938-prevents-console-allocation + + (which has disappeared, and isn't available on the Wayback + Machine) + + and + +https://answers.microsoft.com/en-us/windows/forum/windows_xp-windows_update/kb2567680-andor-kb2507938-breaks-attachconsole-api/e8191280-2d49-4be4-9918-18486fba0afa + +even a failed attempt to attach to another process's console +will cause subsequent AllocConsole() calls to fail, possibly due +to bugs introduced by a security patch. To work around this, we +do a FreeConsole() first. */ + FreeConsole(); + if (AllocConsole()) { + /* That succeeded. */ + console_wait = true; + SetConsoleTitle(_T("Wireshark Debug Console")); + } else { + /* On Windows XP, this still fails; FreeConsole() apparently + doesn't clear the state, as it does on Windows 7. */ + return; /* couldn't create console */ + } + } + + if (must_redirect_stdin) + ws_freopen("CONIN$", "r", stdin); + if (must_redirect_stdout) { + ws_freopen("CONOUT$", "w", stdout); + fprintf(stdout, "\n"); + } + if (must_redirect_stderr) { + ws_freopen("CONOUT$", "w", stderr); + fprintf(stderr, "\n"); + } + + /* Now register "destroy_console()" as a routine to be called just + before the application exits, so that we can destroy the console + after the user has typed a key (so that the console doesn't just + disappear out from under them, giving the user no chance to see + the message(s) we put in there). */ + atexit(destroy_console); + + /* Well, we have a console now. */ + has_console = true; +} + +void +restore_pipes(void) +{ + bool must_redirect_stdin; + bool must_redirect_stdout; + bool must_redirect_stderr; + + HANDLE fd; + + if (stdin_capture) { + /* We've been handed "-i -". Don't mess with stdio. */ + return; + } + + if (has_console) { + return; + } + + /* Are the standard input, output, and error invalid handles? */ + must_redirect_stdin = needs_redirection(STD_INPUT_HANDLE); + must_redirect_stdout = needs_redirection(STD_OUTPUT_HANDLE); + must_redirect_stderr = needs_redirection(STD_ERROR_HANDLE); + + /* If none of them are invalid, we don't need to do anything. */ + if (!must_redirect_stdin && !must_redirect_stdout && !must_redirect_stderr) + return; + + if (!must_redirect_stdin) + fd = GetStdHandle(STD_INPUT_HANDLE); + + /* OK, at least one of them needs to be redirected to a console; + try to attach to the parent process's console and, if that fails, + cleanup and return. */ + /* + * See if we have an existing console (i.e. we were run from a + * command prompt). + */ + if (!AttachConsole(ATTACH_PARENT_PROCESS)) { + FreeConsole(); + return; /* No parent - cleanup and exit */ + } + + if (must_redirect_stdin) { + ws_freopen("CONIN$", "r", stdin); + } else { + SetStdHandle(STD_INPUT_HANDLE, fd); + } + if (must_redirect_stdout) { + ws_freopen("CONOUT$", "w", stdout); + fprintf(stdout, "\n"); + } + if (must_redirect_stderr) { + ws_freopen("CONOUT$", "w", stderr); + fprintf(stderr, "\n"); + } + + /* Now register "destroy_console()" as a routine to be called just + before the application exits, so that we can destroy the console + after the user has typed a key (so that the console doesn't just + disappear out from under them, giving the user no chance to see + the message(s) we put in there). */ + atexit(destroy_console); + + /* Well, we have a console now. */ + has_console = true; +} + +void +destroy_console(void) +{ + if (console_wait) { + printf("\n\nPress any key to exit\n"); + _getch(); + } + FreeConsole(); +} + +void +set_console_wait(bool set_console_wait) +{ + console_wait = set_console_wait; +} + +bool +get_console_wait(void) +{ + return console_wait; +} + +void +set_stdin_capture(bool set_stdin_capture) +{ + stdin_capture = set_stdin_capture; +} + +bool +get_stdin_capture(void) +{ + return stdin_capture; +} + +#endif /* _WIN32 */ diff --git a/wsutil/console_win32.h b/wsutil/console_win32.h new file mode 100644 index 00000000..b1020c1a --- /dev/null +++ b/wsutil/console_win32.h @@ -0,0 +1,73 @@ +/** @file + * + * Console support for MSWindows + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 2002, Jeffrey C. Foster <jfoste@woodward.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __CONSOLE_WIN32_H__ +#define __CONSOLE_WIN32_H__ + +#include <wireshark.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#ifdef _WIN32 + +/** @file + * Win32 specific console. + */ + +/** Create Windows console. + * + */ +WS_DLL_PUBLIC +void create_console(void); + +/** Connect to stdio if available. + * + */ +WS_DLL_PUBLIC +void restore_pipes(void); + +/** Destroy Windows console. + * + */ +WS_DLL_PUBLIC +void destroy_console(void); + +/** Set console wait. GTK+ only. + * @param console_wait set/no set console wait + */ +WS_DLL_PUBLIC +void set_console_wait(bool console_wait); +/** get console wait + * @return set/no set console wait + */ +WS_DLL_PUBLIC +bool get_console_wait(void); + +/** Set stdin capture. + * @param console_wait set/no stdin_capture + */ +WS_DLL_PUBLIC +void set_stdin_capture(bool set_stdin_capture); + +/** get stdin caputre + * @return set/no set stdin_capture + */ +WS_DLL_PUBLIC +bool get_stdin_capture(void); +#endif/* _WIN32 */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __CONSOLE_WIN32_H__ */ diff --git a/wsutil/cpu_info.c b/wsutil/cpu_info.c new file mode 100644 index 00000000..f9e6aa25 --- /dev/null +++ b/wsutil/cpu_info.c @@ -0,0 +1,491 @@ +/* cpu_info.c + * Routines to report CPU information + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" +#include <wsutil/cpu_info.h> + +#include <string.h> + +#include <wsutil/ws_cpuid.h> +#include <wsutil/file_util.h> + +#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) || defined(__APPLE__) + #define HAVE_SYSCTL +#elif defined(sun) || defined(__sun) + #define HAVE_SYSINFO +#endif + +#if defined(_WIN32) + #include <windows.h> +#elif defined(HAVE_SYSCTL) + #include <sys/types.h> + #include <sys/sysctl.h> +#elif defined(HAVE_SYSINFO) + #include <sys/systeminfo.h> +#endif + +/* + * Functions used for the GTree we use to keep a list of *unique* + * model strings. + */ +static int +compare_model_names(gconstpointer a, gconstpointer b, void * user_data _U_) +{ + return strcmp((const char *)a, (const char *)b); +} + +struct string_info { + GString *str; + const char *sep; +}; + +static gboolean +add_model_name_to_string(void * key, void * value _U_, + void * data) +{ + struct string_info *info = (struct string_info *)data; + + /* Separate this from the previous entry, if necessary. */ + if (info->sep != NULL) + g_string_append(info->str, info->sep); + + /* Now add the model name. */ + g_string_append(info->str, g_strstrip((char *)key)); + + /* + * There will *definitely* need to be a separator for any subsequent + * model string. + */ + info->sep = ", "; + + /* Keep going. */ + return false; +} + +/* + * Get the CPU info, and append it to the GString + * + * On at least some OSes, there's a call that will return this information + * for all CPU types for which the OS determines that information, not just + * x86 processors with CPUID and the brand string. On those OSes, we use + * that. + * + * On other OSes, we use ws_cpuid(), which will fail unconditionally on + * non-x86 CPUs. + */ +void +get_cpu_info(GString *str) +{ + GTree *model_names = g_tree_new_full(compare_model_names, NULL, g_free, NULL); + +#if defined(__linux__) + /* + * We scan /proc/cpuinfo looking for lines that begins with + * "model name\t: ", and extract what comes after that prefix. + * + * /proc/cpuinfo can report information about multiple "CPU"s. + * A "CPU" appears to be a CPU core, so this treats a multi-core + * chip as multiple CPUs (which is arguably should), but doesn't + * appear to treat a multi-threaded core as multiple CPUs. + * + * So we accumulate a table of *multiple* CPU strings, saving + * one copy of each unique string, and glue them together at + * the end. We use a GTree for this. + * + * We test for Linux first, so that, even if you're on a Linux + * that supports sysctl(), we don't use it, we scan /proc/cpuinfo, + * as that's the right way to do this. + */ + FILE *proc_cpuinfo; + + proc_cpuinfo = ws_fopen("/proc/cpuinfo", "r"); + if (proc_cpuinfo == NULL) { + /* Just give up. */ + g_tree_destroy(model_names); + return; + } + + char *line = NULL; + size_t linecap = 0; + static const char prefix[] = "model name\t: "; + #define PREFIX_STRLEN (sizeof prefix - 1) + ssize_t linelen; + + /* + * Read lines from /proc/cpuinfo; stop when we either hit an EOF + * or get an error. + */ + for (;;) { + linelen = getline(&line, &linecap, proc_cpuinfo); + if (linelen == -1) { + /* EOF or error; just stop. */ + break; + } + /* Remove trailing newline. */ + if (linelen != 0) + line[linelen - 1] = '\0'; + if (strncmp(line, prefix, PREFIX_STRLEN) == 0) { + /* OK, we have a model name. */ + char *model_name; + + /* Get everything after the prefix. */ + model_name = g_strdup(line + PREFIX_STRLEN); + + /* + * Add an entry to the tree with the model name as key and + * a null value. There will only be one such entry in the + * tree; if there's already such an entry, it will be left + * alone, and model_name will be freed, otherwise a new + * node will be created using model_name as the key. + * + * Thus, we don't free model_name; either it will be freed + * for us, or it will be used in the tree and freed when we + * free the tree. + */ + g_tree_insert(model_names, model_name, NULL); + } + } + + fclose(proc_cpuinfo); +#define xx_free free /* hack so checkAPIs doesn't complain */ + xx_free(line); /* yes, free(), as getline() mallocates it */ +#elif defined(_WIN32) + /* + * They're in the Registry. (Isn't everything?) + */ + HKEY processors_key; + + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, + L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor", + 0, KEY_READ, &processors_key) != ERROR_SUCCESS) { + /* Just give up. */ + g_tree_destroy(model_names); + return; + } + + /* + * The processors appear under that key. Enumerate all the keys + * under it. + */ + DWORD num_subkeys; + DWORD max_subkey_len; + wchar_t *subkey_buf; + + /* + * How many subkeys are there, and what's the biggest subkey size? + * + * I assume that when the documentation says that some number is + * in units of "Unicode characters" they mean "units of elements + * of UTF-16 characters", i.e. "units of 2-octet items". + */ + if (RegQueryInfoKeyW(processors_key, NULL, NULL, NULL, &num_subkeys, + &max_subkey_len, NULL, NULL, NULL, NULL, NULL, + NULL) != ERROR_SUCCESS) { + /* Just give up. */ + g_tree_destroy(model_names); + return; + } + + /* + * max_subkey_len does not count the trailing '\0'. Add it. + */ + max_subkey_len++; + + /* + * Allocate a buffer for the subkey. + */ + subkey_buf = (wchar_t *)g_malloc(max_subkey_len * sizeof (wchar_t)); + if (subkey_buf == NULL) { + /* Just give up. */ + g_tree_destroy(model_names); + return; + } + + for (DWORD processor_index = 0; processor_index < num_subkeys; + processor_index++) { + /* + * The documentation says that this is "in characters"; I'm + * assuming, for now, that they mean "Unicode characters", + * meaning "2-octet items". + */ + DWORD subkey_bufsize = max_subkey_len; + if (RegEnumKeyExW(processors_key, processor_index, subkey_buf, + &subkey_bufsize, NULL, NULL, NULL, + NULL) != ERROR_SUCCESS) { + /* Just exit the loop. */ + break; + } + + /* + * Get the length of processor name string for this processor. + * + * That's the "ProcessorNameString" value for the subkey of + * processors_key with the name in subkey_buf. + * + * It's a string, so only allow REG_SZ values. + */ + DWORD model_name_bufsize; + + model_name_bufsize = 0; + if (RegGetValueW(processors_key, subkey_buf, L"ProcessorNameString", + RRF_RT_REG_SZ, NULL, NULL, + &model_name_bufsize) != ERROR_SUCCESS) { + /* Just exit the loop. */ + break; + } + + /* + * Allocate a buffer for the string, as UTF-16. + * The retrieved length includes the terminating '\0'. + */ + wchar_t *model_name_wchar = g_malloc(model_name_bufsize); + if (RegGetValueW(processors_key, subkey_buf, L"ProcessorNameString", + RRF_RT_REG_SZ, NULL, model_name_wchar, + &model_name_bufsize) != ERROR_SUCCESS) { + /* Just exit the loop. */ + g_free(model_name_wchar); + break; + } + + /* Convert it to UTF-8. */ + char *model_name = g_utf16_to_utf8(model_name_wchar, -1, NULL, NULL, NULL); + g_free(model_name_wchar); + + /* + * Add an entry to the tree with the model name as key and + * a null value. There will only be one such entry in the + * tree; if there's already such an entry, it will be left + * alone, and model_name will be freed, otherwise a new + * node will be created using model_name as the key. + * + * Thus, we don't free model_name; either it will be freed + * for us, or it will be used in the tree and freed when we + * free the tree. + */ + g_tree_insert(model_names, model_name, NULL); + } + + g_free(subkey_buf); + + /* + * Close the registry key. + */ + RegCloseKey(processors_key); +#elif defined(HAVE_SYSCTL) + /* + * Fetch the string using the appropriate sysctl. + */ + size_t model_name_len; + char *model_name; + #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) + /* + * Thanks, OpenBSD guys, for not having APIs to map MIB names to + * MIB values! Just consruct the MIB entry directly. + * + * We also do that for FreeBSD and DragonFly BSD, because we can. + * + * FreeBSD appears to support this for x86, PowerPC/Power ISA, and + * Arm. OpenBSD appears to support this for a number of + * architectures. DragonFly BSD appears to support it only for + * x86, but I think they only run on x86-64 now, and may never + * have run on anything non-x86. + */ + int mib[2] = { CTL_HW, HW_MODEL }; + size_t miblen = 2; + #else + /* These require a lookup, as they don't have #defines. */ + #if defined(__APPLE__) /* Darwin */ + /* + * The code seems to support this on both x86 and ARM. + */ + #define BRAND_STRING_SYSCTL "machdep.cpu.brand_string" + #define MIB_DEPTH 3 + #elif defined(__NetBSD__) + /* + * XXX - the "highly portable Unix-like Open Source operating + * system" that "is available for a wide range of platforms" + * doesn't seem to support this except on x86, and doesn't + * seem to support any other MIB for, for example, ARM64. + * + * Maybe someday, so use it anyway. + */ + #define BRAND_STRING_SYSCTL "machdep.cpu_brand" + #define MIB_DEPTH 2 + #endif + int mib[MIB_DEPTH]; + size_t miblen = MIB_DEPTH; + + /* Look up the sysctl name and get the MIB. */ + if (sysctlnametomib(BRAND_STRING_SYSCTL, mib, &miblen) == -1) { + /* + * Either there's no such string or something else went wrong. + * Just give up. + */ + g_tree_destroy(model_names); + return; + } + #endif + if (sysctl(mib, (u_int)miblen, NULL, &model_name_len, NULL, 0) == -1) { + /* + * Either there's no such string or something else went wrong. + * Just give up. + */ + g_tree_destroy(model_names); + return; + } + model_name = g_malloc(model_name_len); + if (sysctl(mib, (u_int)miblen, model_name, &model_name_len, NULL, 0) == -1) { + /* + * Either there's no such string or something else went wrong. + * Just give up. + */ + g_free(model_name); + g_tree_destroy(model_names); + return; + } + g_tree_insert(model_names, model_name, NULL); +#elif defined(HAVE_SYSINFO) && defined(SI_CPUBRAND) + /* + * Solaris. Use sysinfo() with SI_CPUBRAND; the documentation + * indicates that it works on SPARC as well as x86. + * + * Unfortunately, SI_CPUBRAND seems to be a recent addition, so + * older versions of Solaris - dating back to some versions of + * 11.3 - don't have it. + */ + int model_name_len; + char *model_name; + + /* How big is the model name? */ + model_name_len = sysinfo(SI_CPUBRAND, NULL, 0); + if (model_name_len == -1) { + g_tree_destroy(model_names); + return; + } + model_name = g_malloc(model_name_len); + if (sysinfo(SI_CPUBRAND, model_name, model_name_len) == -1) { + g_tree_destroy(model_names); + return; + } + g_tree_insert(model_names, model_name, NULL); +#else + /* + * OS for which we don't support the "get the CPU type" call; we + * use ws_cpuid(), which uses CPUID on x86 and doesn't get any + * information for other instruction sets. + */ + uint32_t CPUInfo[4]; + char CPUBrandString[0x40]; + unsigned nExIds; + + /* + * Calling ws_cpuid with 0x80000000 as the selector argument, i.e. + * executing a cpuid instruction with EAX equal to 0x80000000 and + * ECX equal to 0, gets the number of valid extended IDs. + */ + if (!ws_cpuid(CPUInfo, 0x80000000)) { + g_tree_destroy(model_names); + return; + } + + nExIds = CPUInfo[0]; + + if (nExIds<0x80000005) { + g_tree_destroy(model_names); + return; + } + + memset(CPUBrandString, 0, sizeof(CPUBrandString)); + + /* Interpret CPU brand string */ + ws_cpuid(CPUInfo, 0x80000002); + memcpy(CPUBrandString, CPUInfo, sizeof(CPUInfo)); + ws_cpuid(CPUInfo, 0x80000003); + memcpy(CPUBrandString + 16, CPUInfo, sizeof(CPUInfo)); + ws_cpuid(CPUInfo, 0x80000004); + memcpy(CPUBrandString + 32, CPUInfo, sizeof(CPUInfo)); + + model_name = g_strdup(g_strstrip(CPUBrandString)); + g_tree_insert(model_names, model_name, NULL); +#endif + + int num_model_names = g_tree_nnodes(model_names); + + if (num_model_names > 0) { + /* + * We have at least one model name, so add the name(s) to + * the string. + * + * If the string is not empty, separate the name(s) from + * what precedes it. + */ + if (str->len > 0) + g_string_append(str, ", with "); + + if (num_model_names > 1) { + /* + * There's more than one, so put the list inside curly + * brackets. + */ + g_string_append(str, "{ "); + } + + /* Iterate over the tree, adding model names to the string. */ + struct string_info info; + info.str = str; + info.sep = NULL; + g_tree_foreach(model_names, add_model_name_to_string, &info); + + if (num_model_names > 1) { + /* + * There's more than one, so put the list inside curly + * brackets. + */ + g_string_append(str, " }"); + } + } + + /* We're done; get rid of the tree. */ + g_tree_destroy(model_names); + + /* + * We do this on all OSes and instruction sets, so that we don't + * have to figure out how to dredge the "do we have SSE 4.2?" + * information from whatever source provides it in the OS on + * x86 processors. We already have ws_cpuid_sse42() (which we + * use to determine whether to use SSE 4.2 code to scan buffers + * for strings), so use that; it always returns "false" on non-x86 + * processors. + * + * If you have multiple CPUs, some of which support it and some + * of which don't, I'm not sure we can guarantee that buffer + * scanning will work if, for example, the scanning code gets + * preempted while running on an SSE-4.2-capable CPU and, when + * it gets rescheduled, gets rescheduled on a non-SSE-4.2-capable + * CPU and tries to continue the SSE 4.2-based scan. So we don't + * worry about that case; constructing a CPU string is the *least* + * of our worries in that case. + */ + if (ws_cpuid_sse42()) + g_string_append(str, " (with SSE4.2)"); +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/cpu_info.h b/wsutil/cpu_info.h new file mode 100644 index 00000000..e2e011a2 --- /dev/null +++ b/wsutil/cpu_info.h @@ -0,0 +1,26 @@ +/** @file + * Declarations of routines to report CPU information + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WSUTIL_CPU_INFO_H__ +#define __WSUTIL_CPU_INFO_H__ + +#include <wireshark.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +WS_DLL_PUBLIC void get_cpu_info(GString *str); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __WSUTIL_CPU_INFO_H__ */ diff --git a/wsutil/crash_info.c b/wsutil/crash_info.c new file mode 100644 index 00000000..bf1fc70a --- /dev/null +++ b/wsutil/crash_info.c @@ -0,0 +1,178 @@ +/* crash_info.c + * Routines to try to provide more useful information in crash dumps. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 2006 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" + +#include "crash_info.h" + +#ifdef __APPLE__ +/* + * Copyright 2005-2012 Apple Inc. All rights reserved. + * + * IMPORTANT: This Apple software is supplied to you by Apple Computer, + * Inc. ("Apple") in consideration of your agreement to the following + * terms, and your use, installation, modification or redistribution of + * this Apple software constitutes acceptance of these terms. If you do + * not agree with these terms, please do not use, install, modify or + * redistribute this Apple software. + * + * In consideration of your agreement to abide by the following terms, and + * subject to these terms, Apple grants you a personal, non-exclusive + * license, under Apple's copyrights in this original Apple software (the + * "Apple Software"), to use, reproduce, modify and redistribute the Apple + * Software, with or without modifications, in source and/or binary forms; + * provided that if you redistribute the Apple Software in its entirety and + * without modifications, you must retain this notice and the following + * text and disclaimers in all such redistributions of the Apple Software. + * Neither the name, trademarks, service marks or logos of Apple Computer, + * Inc. may be used to endorse or promote products derived from the Apple + * Software without specific prior written permission from Apple. Except + * as expressly stated in this notice, no other rights or licenses, express + * or implied, are granted by Apple herein, including but not limited to + * any patent rights that may be infringed by your derivative works or by + * other works in which the Apple Software may be incorporated. + * + * The Apple Software is provided by Apple on an "AS IS" basis. APPLE + * MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION + * THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND + * OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. + * + * IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, + * MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED + * AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), + * STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdint.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> + +/* + * This used to be the way to add an application-specific string to + * crash dumps; see + * + * http://www.allocinit.net/blog/2008/01/04/application-specific-information-in-leopard-crash-reports/ + * + * It still appears to work as of OS X 10.8 (Mountain Lion). + */ +__private_extern__ char *__crashreporter_info__ = NULL; + +#if 0 +/* + * And this appears to be the new way to do it, as of Lion. + * However, if we do both, we get the message twice, so we're + * not doing this one, for now. + * + * This code was lifted from SVN trunk CUPS. + */ +#define _crc_make_getter(attr, type) (type)(gCRAnnotations.attr) +#define _crc_make_setter(attr, arg) (gCRAnnotations.attr = (uint64_t)(unsigned long)(arg)) +#define CRASH_REPORTER_CLIENT_HIDDEN __attribute__((visibility("hidden"))) +#define CRASHREPORTER_ANNOTATIONS_VERSION 4 +#define CRASHREPORTER_ANNOTATIONS_SECTION "__crash_info" + +/* + * Yes, these are all 64-bit, even on 32-bit platforms. + * + * version is presumably the version of this structure. + * + * message and message2 are reported, one after the other, + * under "Application Specific Information". + * + * signature_string is reported under "Application Specific + * Signatures". + * + * backtrace is reported under "Application Specific Backtrace". + * + * Dunno which versions are supported by which versions of macOS. + */ +struct crashreporter_annotations_t { + uint64_t version; /* unsigned long */ + uint64_t message; /* char * */ + uint64_t signature_string; /* char * */ + uint64_t backtrace; /* char * */ + uint64_t message2; /* char * */ + uint64_t thread; /* uint64_t */ + uint64_t dialog_mode; /* unsigned int */ +}; + +CRASH_REPORTER_CLIENT_HIDDEN +struct crashreporter_annotations_t gCRAnnotations + __attribute__((section("__DATA," CRASHREPORTER_ANNOTATIONS_SECTION))) = { + CRASHREPORTER_ANNOTATIONS_VERSION, /* version */ + 0, /* message */ + 0, /* signature_string */ + 0, /* backtrace */ + 0, /* message2 */ + 0, /* thread */ + 0 /* dialog_mode */ +}; + +#define CRSetCrashLogMessage(m) _crc_make_setter(message, m) +#endif /* 0 */ + +void +ws_vadd_crash_info(const char *fmt, va_list ap) +{ + char *m, *old_info, *new_info; + + m = ws_strdup_vprintf(fmt, ap); + if (__crashreporter_info__ == NULL) + __crashreporter_info__ = m; + else { + old_info = __crashreporter_info__; + new_info = ws_strdup_printf("%s\n%s", old_info, m); + g_free(m); + __crashreporter_info__ = new_info; + g_free(old_info); + } +} + +#else /* __APPLE__ */ +/* + * Perhaps Google Breakpad (http://code.google.com/p/google-breakpad/) or + * other options listed at + * http://stackoverflow.com/questions/7631908/library-for-logging-call-stack-at-runtime-windows-linux + * ? + */ +void +ws_vadd_crash_info(const char *fmt _U_, va_list ap _U_) +{ +} +#endif /* __APPLE__ */ + +void +ws_add_crash_info(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + ws_vadd_crash_info(fmt, ap); + va_end(ap); +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 8 + * tab-width: 8 + * indent-tabs-mode: t + * End: + * + * vi: set shiftwidth=8 tabstop=8 noexpandtab: + * :indentSize=8:tabSize=8:noTabs=false: + */ diff --git a/wsutil/crash_info.h b/wsutil/crash_info.h new file mode 100644 index 00000000..37840067 --- /dev/null +++ b/wsutil/crash_info.h @@ -0,0 +1,29 @@ +/** @file + * Routines to try to provide more useful information in crash dumps. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 2006 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __CRASH_INFO_H__ +#define __CRASH_INFO_H__ + +#include <wireshark.h> + +#ifdef __cplusplus +extern "C" { +#endif + +WS_DLL_PUBLIC void ws_vadd_crash_info(const char *fmt, va_list ap); + +WS_DLL_PUBLIC void ws_add_crash_info(const char *fmt, ...) + G_GNUC_PRINTF(1,2); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __CRASH_INFO_H__ */ diff --git a/wsutil/crc10.c b/wsutil/crc10.c new file mode 100644 index 00000000..ecbf5f23 --- /dev/null +++ b/wsutil/crc10.c @@ -0,0 +1,83 @@ +/* + * crc10.c + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" +#include "crc10.h" + +/* + * Charles Michael Heard's CRC-10 code, from + * + * http://web.archive.org/web/20061005231950/http://cell-relay.indiana.edu/cell-relay/publications/software/CRC/crc10.html + * + * with the CRC table initialized with values computed by + * his "gen_byte_crc10_table()" routine, rather than by calling that + * routine at run time, and with various data type cleanups. + */ +static const uint16_t byte_crc10_table[256] = { + 0x0000, 0x0233, 0x0255, 0x0066, 0x0299, 0x00aa, 0x00cc, 0x02ff, + 0x0301, 0x0132, 0x0154, 0x0367, 0x0198, 0x03ab, 0x03cd, 0x01fe, + 0x0031, 0x0202, 0x0264, 0x0057, 0x02a8, 0x009b, 0x00fd, 0x02ce, + 0x0330, 0x0103, 0x0165, 0x0356, 0x01a9, 0x039a, 0x03fc, 0x01cf, + 0x0062, 0x0251, 0x0237, 0x0004, 0x02fb, 0x00c8, 0x00ae, 0x029d, + 0x0363, 0x0150, 0x0136, 0x0305, 0x01fa, 0x03c9, 0x03af, 0x019c, + 0x0053, 0x0260, 0x0206, 0x0035, 0x02ca, 0x00f9, 0x009f, 0x02ac, + 0x0352, 0x0161, 0x0107, 0x0334, 0x01cb, 0x03f8, 0x039e, 0x01ad, + 0x00c4, 0x02f7, 0x0291, 0x00a2, 0x025d, 0x006e, 0x0008, 0x023b, + 0x03c5, 0x01f6, 0x0190, 0x03a3, 0x015c, 0x036f, 0x0309, 0x013a, + 0x00f5, 0x02c6, 0x02a0, 0x0093, 0x026c, 0x005f, 0x0039, 0x020a, + 0x03f4, 0x01c7, 0x01a1, 0x0392, 0x016d, 0x035e, 0x0338, 0x010b, + 0x00a6, 0x0295, 0x02f3, 0x00c0, 0x023f, 0x000c, 0x006a, 0x0259, + 0x03a7, 0x0194, 0x01f2, 0x03c1, 0x013e, 0x030d, 0x036b, 0x0158, + 0x0097, 0x02a4, 0x02c2, 0x00f1, 0x020e, 0x003d, 0x005b, 0x0268, + 0x0396, 0x01a5, 0x01c3, 0x03f0, 0x010f, 0x033c, 0x035a, 0x0169, + 0x0188, 0x03bb, 0x03dd, 0x01ee, 0x0311, 0x0122, 0x0144, 0x0377, + 0x0289, 0x00ba, 0x00dc, 0x02ef, 0x0010, 0x0223, 0x0245, 0x0076, + 0x01b9, 0x038a, 0x03ec, 0x01df, 0x0320, 0x0113, 0x0175, 0x0346, + 0x02b8, 0x008b, 0x00ed, 0x02de, 0x0021, 0x0212, 0x0274, 0x0047, + 0x01ea, 0x03d9, 0x03bf, 0x018c, 0x0373, 0x0140, 0x0126, 0x0315, + 0x02eb, 0x00d8, 0x00be, 0x028d, 0x0072, 0x0241, 0x0227, 0x0014, + 0x01db, 0x03e8, 0x038e, 0x01bd, 0x0342, 0x0171, 0x0117, 0x0324, + 0x02da, 0x00e9, 0x008f, 0x02bc, 0x0043, 0x0270, 0x0216, 0x0025, + 0x014c, 0x037f, 0x0319, 0x012a, 0x03d5, 0x01e6, 0x0180, 0x03b3, + 0x024d, 0x007e, 0x0018, 0x022b, 0x00d4, 0x02e7, 0x0281, 0x00b2, + 0x017d, 0x034e, 0x0328, 0x011b, 0x03e4, 0x01d7, 0x01b1, 0x0382, + 0x027c, 0x004f, 0x0029, 0x021a, 0x00e5, 0x02d6, 0x02b0, 0x0083, + 0x012e, 0x031d, 0x037b, 0x0148, 0x03b7, 0x0184, 0x01e2, 0x03d1, + 0x022f, 0x001c, 0x007a, 0x0249, 0x00b6, 0x0285, 0x02e3, 0x00d0, + 0x011f, 0x032c, 0x034a, 0x0179, 0x0386, 0x01b5, 0x01d3, 0x03e0, + 0x021e, 0x002d, 0x004b, 0x0278, 0x0087, 0x02b4, 0x02d2, 0x00e1 +}; + +/* Update the data block's CRC-10 remainder one byte at a time */ +uint16_t +update_crc10_by_bytes(uint16_t crc10_accum, const uint8_t *data_blk_ptr, + int data_blk_size) +{ + register int i; + + for (i = 0; i < data_blk_size; i++) { + crc10_accum = ((crc10_accum << 8) & 0x3ff) + ^ byte_crc10_table[( crc10_accum >> 2) & 0xff] + ^ *data_blk_ptr++; + } + return crc10_accum; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 8 + * tab-width: 8 + * indent-tabs-mode: t + * End: + * + * vi: set shiftwidth=8 tabstop=8 noexpandtab: + * :indentSize=8:tabSize=8:noTabs=false: + */ diff --git a/wsutil/crc10.h b/wsutil/crc10.h new file mode 100644 index 00000000..9ff5322a --- /dev/null +++ b/wsutil/crc10.h @@ -0,0 +1,18 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __CRC10_H__ +#define __CRC10_H__ + +#include <wireshark.h> + +/* Update the data block's CRC-10 remainder one byte at a time */ +WS_DLL_PUBLIC uint16_t update_crc10_by_bytes(uint16_t crc10, const uint8_t *data_blk_ptr, int data_blk_size); + +#endif /* __CRC10_H__ */ diff --git a/wsutil/crc11.c b/wsutil/crc11.c new file mode 100644 index 00000000..d7ebb1cb --- /dev/null +++ b/wsutil/crc11.c @@ -0,0 +1,80 @@ +/* + * crc11.c + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + + +#include <wsutil/crc11.h> + +/** + * Functions and types for CRC checks. + * + * Generated on Tue Aug 7 15:45:57 2012, + * by pycrc v0.7.10, http://www.tty1.net/pycrc/ + * using the configuration: + * Width = 11 + * Poly = 0x307 + * XorIn = 0x000 + * ReflectIn = False + * XorOut = 0x000 + * ReflectOut = False + * Algorithm = table-driven + *****************************************************************************/ +/** + * Static table used for the table_driven implementation. + *****************************************************************************/ +static const uint16_t crc11_table_307_noreflect_noxor[256] = { + 0x000, 0x307, 0x60e, 0x509, 0x71b, 0x41c, 0x115, 0x212, 0x531, 0x636, 0x33f, 0x038, 0x22a, 0x12d, 0x424, 0x723, + 0x165, 0x262, 0x76b, 0x46c, 0x67e, 0x579, 0x070, 0x377, 0x454, 0x753, 0x25a, 0x15d, 0x34f, 0x048, 0x541, 0x646, + 0x2ca, 0x1cd, 0x4c4, 0x7c3, 0x5d1, 0x6d6, 0x3df, 0x0d8, 0x7fb, 0x4fc, 0x1f5, 0x2f2, 0x0e0, 0x3e7, 0x6ee, 0x5e9, + 0x3af, 0x0a8, 0x5a1, 0x6a6, 0x4b4, 0x7b3, 0x2ba, 0x1bd, 0x69e, 0x599, 0x090, 0x397, 0x185, 0x282, 0x78b, 0x48c, + 0x594, 0x693, 0x39a, 0x09d, 0x28f, 0x188, 0x481, 0x786, 0x0a5, 0x3a2, 0x6ab, 0x5ac, 0x7be, 0x4b9, 0x1b0, 0x2b7, + 0x4f1, 0x7f6, 0x2ff, 0x1f8, 0x3ea, 0x0ed, 0x5e4, 0x6e3, 0x1c0, 0x2c7, 0x7ce, 0x4c9, 0x6db, 0x5dc, 0x0d5, 0x3d2, + 0x75e, 0x459, 0x150, 0x257, 0x045, 0x342, 0x64b, 0x54c, 0x26f, 0x168, 0x461, 0x766, 0x574, 0x673, 0x37a, 0x07d, + 0x63b, 0x53c, 0x035, 0x332, 0x120, 0x227, 0x72e, 0x429, 0x30a, 0x00d, 0x504, 0x603, 0x411, 0x716, 0x21f, 0x118, + 0x02f, 0x328, 0x621, 0x526, 0x734, 0x433, 0x13a, 0x23d, 0x51e, 0x619, 0x310, 0x017, 0x205, 0x102, 0x40b, 0x70c, + 0x14a, 0x24d, 0x744, 0x443, 0x651, 0x556, 0x05f, 0x358, 0x47b, 0x77c, 0x275, 0x172, 0x360, 0x067, 0x56e, 0x669, + 0x2e5, 0x1e2, 0x4eb, 0x7ec, 0x5fe, 0x6f9, 0x3f0, 0x0f7, 0x7d4, 0x4d3, 0x1da, 0x2dd, 0x0cf, 0x3c8, 0x6c1, 0x5c6, + 0x380, 0x087, 0x58e, 0x689, 0x49b, 0x79c, 0x295, 0x192, 0x6b1, 0x5b6, 0x0bf, 0x3b8, 0x1aa, 0x2ad, 0x7a4, 0x4a3, + 0x5bb, 0x6bc, 0x3b5, 0x0b2, 0x2a0, 0x1a7, 0x4ae, 0x7a9, 0x08a, 0x38d, 0x684, 0x583, 0x791, 0x496, 0x19f, 0x298, + 0x4de, 0x7d9, 0x2d0, 0x1d7, 0x3c5, 0x0c2, 0x5cb, 0x6cc, 0x1ef, 0x2e8, 0x7e1, 0x4e6, 0x6f4, 0x5f3, 0x0fa, 0x3fd, + 0x771, 0x476, 0x17f, 0x278, 0x06a, 0x36d, 0x664, 0x563, 0x240, 0x147, 0x44e, 0x749, 0x55b, 0x65c, 0x355, 0x052, + 0x614, 0x513, 0x01a, 0x31d, 0x10f, 0x208, 0x701, 0x406, 0x325, 0x022, 0x52b, 0x62c, 0x43e, 0x739, 0x230, 0x137 +}; +/** + * Update the crc value with new data. + * + * \param data Pointer to a buffer of \a data_len bytes. + * \param data_len Number of bytes in the \a data buffer. + * \return The updated crc value. + *****************************************************************************/ +uint16_t crc11_307_noreflect_noxor(const uint8_t *data, uint64_t data_len) +{ + uint16_t crc = 0; + unsigned tbl_idx; + + while (data_len--) { + tbl_idx = ((crc >> 3) ^ *data) & 0xff; + crc = (crc11_table_307_noreflect_noxor[tbl_idx] ^ (crc << 8)) & 0x7ff; + + data++; + } + return crc & 0x7ff; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/crc11.h b/wsutil/crc11.h new file mode 100644 index 00000000..3c48dd5c --- /dev/null +++ b/wsutil/crc11.h @@ -0,0 +1,41 @@ +/** @file + * http://www.tty1.net/pycrc/faq_en.html#code-ownership + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __CRC11_____H__ + +#include <stdint.h> + +#include "ws_symbol_export.h" + +#ifdef __cplusplus +extern "C" { +#endif +/** + * Functions and types for CRC checks. + * + * Generated on Tue Aug 7 15:45:57 2012, + * by pycrc v0.7.10, http://www.tty1.net/pycrc/ + * using the configuration: + * Width = 11 + * Poly = 0x307 + * XorIn = 0x000 + * ReflectIn = False + * XorOut = 0x000 + * ReflectOut = False + * Algorithm = table-driven + *****************************************************************************/ +WS_DLL_PUBLIC +uint16_t crc11_307_noreflect_noxor(const uint8_t *data, uint64_t data_len); + +#ifdef __cplusplus +} /* closing brace for extern "C" */ +#endif + +#endif /*__CRC11_____H__*/ diff --git a/wsutil/crc16-plain.c b/wsutil/crc16-plain.c new file mode 100644 index 00000000..732f636a --- /dev/null +++ b/wsutil/crc16-plain.c @@ -0,0 +1,194 @@ +/* + * crc16-plain.c + * http://www.tty1.net/pycrc/faq_en.html#code-ownership + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/** + * \file crc16-plain.c + * Functions and types for CRC checks. + * + * Generated on Wed Mar 18 14:12:09 2009, + * by pycrc v0.7, http://www.tty1.net/pycrc/ + * using the configuration: + * Width = 16 + * Poly = 0x8005 + * XorIn = 0x0000 + * ReflectIn = True + * XorOut = 0x0000 + * ReflectOut = True + * Algorithm = table-driven + * Direct = True + * + * Modified 2009-03-16 not to include <stdint.h> as our Win32 environment + * appears not to have it; we're using GLib types, instead. + * Modified 2023-06-28 to use C99 types. + *****************************************************************************/ +#include "wsutil/crc16-plain.h" + +/** + * Static table used for the table_driven implementation. + *****************************************************************************/ +static const crc16_plain_t crc_table_reflected[256] = { + 0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241, + 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440, + 0xcc01, 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40, + 0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841, + 0xd801, 0x18c0, 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40, + 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41, + 0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641, + 0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040, + 0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240, + 0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441, + 0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41, + 0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840, + 0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41, + 0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40, + 0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681, 0x2640, + 0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041, + 0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240, + 0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441, + 0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41, + 0xaa01, 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840, + 0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41, + 0xbe01, 0x7ec0, 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40, + 0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640, + 0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041, + 0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241, + 0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481, 0x5440, + 0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40, + 0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841, + 0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40, + 0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41, + 0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641, + 0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081, 0x4040 +}; + +/** + * Reflect all bits of a \a data word of \a data_len bytes. + * + * \param data The data word to be reflected. + * \param data_len The width of \a data expressed in number of bits. + * \return The reflected data. + *****************************************************************************/ +long crc16_plain_reflect(long data, size_t data_len) +{ + unsigned int i; + long ret; + + ret = data & 0x01; + for (i = 1; i < data_len; i++) + { + data >>= 1; + ret = (ret << 1) | (data & 0x01); + } + return ret; +} + + +/** + * Update the crc value with new data. + * + * \param crc The current crc value. + * \param data Pointer to a buffer of \a data_len bytes. + * \param data_len Number of bytes in the \a data buffer. + * \return The updated crc value. + *****************************************************************************/ +crc16_plain_t crc16_plain_update(crc16_plain_t crc, const unsigned char *data, size_t data_len) +{ + unsigned int tbl_idx; + + while (data_len--) { + tbl_idx = (crc ^ *data) & 0xff; + crc = (crc_table_reflected[tbl_idx] ^ (crc >> 8)) & 0xffff; + + data++; + } + return crc & 0xffff; +} + +/* Generated on Tue Jul 24 09:08:46 2012, + * by pycrc v0.7.10, http://www.tty1.net/pycrc/ + * using the configuration: + * Width = 16 + * Poly = 0x8005 + * XorIn = 0x0000 + * ReflectIn = False + * XorOut = 0x0000 + * ReflectOut = False + * Algorithm = table-driven + *****************************************************************************/ +static const uint16_t crc16_table_8005_noreflect_noxor[256] = { + 0x0000, 0x8005, 0x800f, 0x000a, 0x801b, 0x001e, 0x0014, 0x8011, + 0x8033, 0x0036, 0x003c, 0x8039, 0x0028, 0x802d, 0x8027, 0x0022, + 0x8063, 0x0066, 0x006c, 0x8069, 0x0078, 0x807d, 0x8077, 0x0072, + 0x0050, 0x8055, 0x805f, 0x005a, 0x804b, 0x004e, 0x0044, 0x8041, + 0x80c3, 0x00c6, 0x00cc, 0x80c9, 0x00d8, 0x80dd, 0x80d7, 0x00d2, + 0x00f0, 0x80f5, 0x80ff, 0x00fa, 0x80eb, 0x00ee, 0x00e4, 0x80e1, + 0x00a0, 0x80a5, 0x80af, 0x00aa, 0x80bb, 0x00be, 0x00b4, 0x80b1, + 0x8093, 0x0096, 0x009c, 0x8099, 0x0088, 0x808d, 0x8087, 0x0082, + 0x8183, 0x0186, 0x018c, 0x8189, 0x0198, 0x819d, 0x8197, 0x0192, + 0x01b0, 0x81b5, 0x81bf, 0x01ba, 0x81ab, 0x01ae, 0x01a4, 0x81a1, + 0x01e0, 0x81e5, 0x81ef, 0x01ea, 0x81fb, 0x01fe, 0x01f4, 0x81f1, + 0x81d3, 0x01d6, 0x01dc, 0x81d9, 0x01c8, 0x81cd, 0x81c7, 0x01c2, + 0x0140, 0x8145, 0x814f, 0x014a, 0x815b, 0x015e, 0x0154, 0x8151, + 0x8173, 0x0176, 0x017c, 0x8179, 0x0168, 0x816d, 0x8167, 0x0162, + 0x8123, 0x0126, 0x012c, 0x8129, 0x0138, 0x813d, 0x8137, 0x0132, + 0x0110, 0x8115, 0x811f, 0x011a, 0x810b, 0x010e, 0x0104, 0x8101, + 0x8303, 0x0306, 0x030c, 0x8309, 0x0318, 0x831d, 0x8317, 0x0312, + 0x0330, 0x8335, 0x833f, 0x033a, 0x832b, 0x032e, 0x0324, 0x8321, + 0x0360, 0x8365, 0x836f, 0x036a, 0x837b, 0x037e, 0x0374, 0x8371, + 0x8353, 0x0356, 0x035c, 0x8359, 0x0348, 0x834d, 0x8347, 0x0342, + 0x03c0, 0x83c5, 0x83cf, 0x03ca, 0x83db, 0x03de, 0x03d4, 0x83d1, + 0x83f3, 0x03f6, 0x03fc, 0x83f9, 0x03e8, 0x83ed, 0x83e7, 0x03e2, + 0x83a3, 0x03a6, 0x03ac, 0x83a9, 0x03b8, 0x83bd, 0x83b7, 0x03b2, + 0x0390, 0x8395, 0x839f, 0x039a, 0x838b, 0x038e, 0x0384, 0x8381, + 0x0280, 0x8285, 0x828f, 0x028a, 0x829b, 0x029e, 0x0294, 0x8291, + 0x82b3, 0x02b6, 0x02bc, 0x82b9, 0x02a8, 0x82ad, 0x82a7, 0x02a2, + 0x82e3, 0x02e6, 0x02ec, 0x82e9, 0x02f8, 0x82fd, 0x82f7, 0x02f2, + 0x02d0, 0x82d5, 0x82df, 0x02da, 0x82cb, 0x02ce, 0x02c4, 0x82c1, + 0x8243, 0x0246, 0x024c, 0x8249, 0x0258, 0x825d, 0x8257, 0x0252, + 0x0270, 0x8275, 0x827f, 0x027a, 0x826b, 0x026e, 0x0264, 0x8261, + 0x0220, 0x8225, 0x822f, 0x022a, 0x823b, 0x023e, 0x0234, 0x8231, + 0x8213, 0x0216, 0x021c, 0x8219, 0x0208, 0x820d, 0x8207, 0x0202 +}; + +/** + * Calculate the crc-16 (x^16 + x^15 + x^2 + 1) value for data. Note that this + * CRC is not equal to crc16_plain. + * + * \param data Pointer to a buffer of \a data_len bytes. + * \param data_len Number of bytes in the \a data buffer. + * \return The crc value. + *****************************************************************************/ +uint16_t crc16_8005_noreflect_noxor(const uint8_t *data, uint64_t data_len) +{ + unsigned tbl_idx; + uint16_t crc = 0; + + while (data_len--) { + tbl_idx = ((crc >> 8) ^ *data) & 0xff; + crc = (crc16_table_8005_noreflect_noxor[tbl_idx] ^ (crc << 8)) & 0xffff; + + data++; + } + return crc & 0xffff; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/crc16-plain.h b/wsutil/crc16-plain.h new file mode 100644 index 00000000..0e1eab6f --- /dev/null +++ b/wsutil/crc16-plain.h @@ -0,0 +1,121 @@ +/** @file + * http://www.tty1.net/pycrc/faq_en.html#code-ownership + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/** + * \file crc16-plain.h + * Functions and types for CRC checks. + * + * Generated on Wed Mar 18 14:12:15 2009, + * by pycrc v0.7, http://www.tty1.net/pycrc/ + * using the configuration: + * Width = 16 + * Poly = 0x8005 + * XorIn = 0x0000 + * ReflectIn = True + * XorOut = 0x0000 + * ReflectOut = True + * Algorithm = table-driven + * Direct = True + * + * Modified 2009-03-16 not to include <stdint.h> as our Win32 environment + * appears not to have it; we're using GLib types, instead. + *****************************************************************************/ +#ifndef __CRC____PLAIN_H__ +#define __CRC____PLAIN_H__ + +#include <stddef.h> +#include <stdint.h> + +#include "ws_symbol_export.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * The definition of the used algorithm. + *****************************************************************************/ +#define CRC_ALGO_TABLE_DRIVEN 1 + +/** + * The type of the CRC values. + * + * This type must be big enough to contain at least 16 bits. + *****************************************************************************/ +typedef uint16_t crc16_plain_t; + +/** + * Reflect all bits of a \a data word of \a data_len bytes. + * + * \param data The data word to be reflected. + * \param data_len The width of \a data expressed in number of bits. + * \return The reflected data. + *****************************************************************************/ +long crc16_plain_reflect(long data, size_t data_len); + +/** + * Calculate the initial crc value. + * + * \return The initial crc value. + *****************************************************************************/ +static inline crc16_plain_t crc16_plain_init(void) +{ + return 0x0000; +} + +/** + * Update the crc value with new data. + * + * \param crc The current crc value. + * \param data Pointer to a buffer of \a data_len bytes. + * \param data_len Number of bytes in the \a data buffer. + * \return The updated crc value. + *****************************************************************************/ +WS_DLL_PUBLIC +crc16_plain_t crc16_plain_update(crc16_plain_t crc, const unsigned char *data, size_t data_len); + +/** + * Calculate the final crc value. + * + * \param crc The current crc value. + * \return The final crc value. + *****************************************************************************/ +static inline crc16_plain_t crc16_plain_finalize(crc16_plain_t crc) +{ + return crc ^ 0x0000; +} + +/* Generated on Tue Jul 24 09:08:46 2012, + * by pycrc v0.7.10, http://www.tty1.net/pycrc/ + * using the configuration: + * Width = 16 + * Poly = 0x8005 + * XorIn = 0x0000 + * ReflectIn = False + * XorOut = 0x0000 + * ReflectOut = False + * Algorithm = table-driven + * + * Calculate the crc-16 (x^16 + x^15 + x^2 + 1) value for data. Note that this + * CRC is not equal to crc16_plain. + * + * \param data Pointer to a buffer of \a data_len bytes. + * \param data_len Number of bytes in the \a data buffer. + * \return The crc value. + *****************************************************************************/ +WS_DLL_PUBLIC +uint16_t crc16_8005_noreflect_noxor(const uint8_t *data, uint64_t data_len); + + +#ifdef __cplusplus +} /* closing brace for extern "C" */ +#endif + +#endif /* __CRC____PLAIN_H__ */ diff --git a/wsutil/crc16.c b/wsutil/crc16.c new file mode 100644 index 00000000..dfb2e2d6 --- /dev/null +++ b/wsutil/crc16.c @@ -0,0 +1,483 @@ +/* crc16.c + * CRC-16 routine + * + * 2004 Richard van der Hoff <richardv@mxtelecom.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * References: + * "A Painless Guide to CRC Error Detection Algorithms", Ross Williams + * http://www.repairfaq.org/filipg/LINK/F_crc_v3.html + * + * ITU-T Recommendation V.42 (2002), "Error-Correcting Procedures for + * DCEs using asynchronous-to-synchronous conversion", Para. 8.1.1.6.1 + */ + +#include "config.h" + +#include <wsutil/crc16.h> + +/*****************************************************************/ + +/* + * Table for the CCITT/ITU/CRC-16 16-bit CRC + * + * Polynomial is + * + * x^16 + x^12 + x^5 + 1 + */ + +/* */ +/* CRC LOOKUP TABLE */ +/* ================ */ +/* The following CRC lookup table was generated automagically */ +/* by the Rocksoft^tm Model CRC Algorithm Table Generation */ +/* Program V1.0 using the following model parameters: */ +/* */ +/* Width : 2 bytes. */ +/* Poly : 0x1021 */ +/* Reverse : true. */ +/* */ +/* For more information on the Rocksoft^tm Model CRC Algorithm, */ +/* see the document titled "A Painless Guide to CRC Error */ +/* Detection Algorithms" by Ross Williams. See */ +/* */ +/* http://www.ross.net/crc/crcpaper.html */ +/* */ +/* which links to a text version and an HTML-but-not-all-on-one- */ +/* page version, or various HTML-all-on-one-page versions such */ +/* as the one at */ +/* */ +/* http://www.geocities.com/SiliconValley/Pines/8659/crc.htm */ +/* */ +/* (search for the title to find others). */ +/* */ +/*****************************************************************/ + +static const unsigned crc16_ccitt_table_reverse[256] = +{ + 0x0000, 0x1189, 0x2312, 0x329B, 0x4624, 0x57AD, 0x6536, 0x74BF, + 0x8C48, 0x9DC1, 0xAF5A, 0xBED3, 0xCA6C, 0xDBE5, 0xE97E, 0xF8F7, + 0x1081, 0x0108, 0x3393, 0x221A, 0x56A5, 0x472C, 0x75B7, 0x643E, + 0x9CC9, 0x8D40, 0xBFDB, 0xAE52, 0xDAED, 0xCB64, 0xF9FF, 0xE876, + 0x2102, 0x308B, 0x0210, 0x1399, 0x6726, 0x76AF, 0x4434, 0x55BD, + 0xAD4A, 0xBCC3, 0x8E58, 0x9FD1, 0xEB6E, 0xFAE7, 0xC87C, 0xD9F5, + 0x3183, 0x200A, 0x1291, 0x0318, 0x77A7, 0x662E, 0x54B5, 0x453C, + 0xBDCB, 0xAC42, 0x9ED9, 0x8F50, 0xFBEF, 0xEA66, 0xD8FD, 0xC974, + 0x4204, 0x538D, 0x6116, 0x709F, 0x0420, 0x15A9, 0x2732, 0x36BB, + 0xCE4C, 0xDFC5, 0xED5E, 0xFCD7, 0x8868, 0x99E1, 0xAB7A, 0xBAF3, + 0x5285, 0x430C, 0x7197, 0x601E, 0x14A1, 0x0528, 0x37B3, 0x263A, + 0xDECD, 0xCF44, 0xFDDF, 0xEC56, 0x98E9, 0x8960, 0xBBFB, 0xAA72, + 0x6306, 0x728F, 0x4014, 0x519D, 0x2522, 0x34AB, 0x0630, 0x17B9, + 0xEF4E, 0xFEC7, 0xCC5C, 0xDDD5, 0xA96A, 0xB8E3, 0x8A78, 0x9BF1, + 0x7387, 0x620E, 0x5095, 0x411C, 0x35A3, 0x242A, 0x16B1, 0x0738, + 0xFFCF, 0xEE46, 0xDCDD, 0xCD54, 0xB9EB, 0xA862, 0x9AF9, 0x8B70, + 0x8408, 0x9581, 0xA71A, 0xB693, 0xC22C, 0xD3A5, 0xE13E, 0xF0B7, + 0x0840, 0x19C9, 0x2B52, 0x3ADB, 0x4E64, 0x5FED, 0x6D76, 0x7CFF, + 0x9489, 0x8500, 0xB79B, 0xA612, 0xD2AD, 0xC324, 0xF1BF, 0xE036, + 0x18C1, 0x0948, 0x3BD3, 0x2A5A, 0x5EE5, 0x4F6C, 0x7DF7, 0x6C7E, + 0xA50A, 0xB483, 0x8618, 0x9791, 0xE32E, 0xF2A7, 0xC03C, 0xD1B5, + 0x2942, 0x38CB, 0x0A50, 0x1BD9, 0x6F66, 0x7EEF, 0x4C74, 0x5DFD, + 0xB58B, 0xA402, 0x9699, 0x8710, 0xF3AF, 0xE226, 0xD0BD, 0xC134, + 0x39C3, 0x284A, 0x1AD1, 0x0B58, 0x7FE7, 0x6E6E, 0x5CF5, 0x4D7C, + 0xC60C, 0xD785, 0xE51E, 0xF497, 0x8028, 0x91A1, 0xA33A, 0xB2B3, + 0x4A44, 0x5BCD, 0x6956, 0x78DF, 0x0C60, 0x1DE9, 0x2F72, 0x3EFB, + 0xD68D, 0xC704, 0xF59F, 0xE416, 0x90A9, 0x8120, 0xB3BB, 0xA232, + 0x5AC5, 0x4B4C, 0x79D7, 0x685E, 0x1CE1, 0x0D68, 0x3FF3, 0x2E7A, + 0xE70E, 0xF687, 0xC41C, 0xD595, 0xA12A, 0xB0A3, 0x8238, 0x93B1, + 0x6B46, 0x7ACF, 0x4854, 0x59DD, 0x2D62, 0x3CEB, 0x0E70, 0x1FF9, + 0xF78F, 0xE606, 0xD49D, 0xC514, 0xB1AB, 0xA022, 0x92B9, 0x8330, + 0x7BC7, 0x6A4E, 0x58D5, 0x495C, 0x3DE3, 0x2C6A, 0x1EF1, 0x0F78 +}; + +/* Same as above, only without reverse (Reverse=false) */ +static const unsigned crc16_ccitt_table[256] = +{ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, + 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, + 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, + 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, + 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, + 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, + 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, + 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, + 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, + 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, + 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, + 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, + 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, + 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, + 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, + 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, + 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, + 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, + 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, + 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, + 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, + 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, + 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0 +}; + +/* This table was compiled using the polynom 0x5935 */ +static const unsigned crc16_precompiled_5935[256] = +{ + 0x0000, 0x5935, 0xB26A, 0xEB5F, 0x3DE1, 0x64D4, 0x8F8B, 0xD6BE, + 0x7BC2, 0x22F7, 0xC9A8, 0x909D, 0x4623, 0x1F16, 0xF449, 0xAD7C, + 0xF784, 0xAEB1, 0x45EE, 0x1CDB, 0xCA65, 0x9350, 0x780F, 0x213A, + 0x8C46, 0xD573, 0x3E2C, 0x6719, 0xB1A7, 0xE892, 0x03CD, 0x5AF8, + 0xB63D, 0xEF08, 0x0457, 0x5D62, 0x8BDC, 0xD2E9, 0x39B6, 0x6083, + 0xCDFF, 0x94CA, 0x7F95, 0x26A0, 0xF01E, 0xA92B, 0x4274, 0x1B41, + 0x41B9, 0x188C, 0xF3D3, 0xAAE6, 0x7C58, 0x256D, 0xCE32, 0x9707, + 0x3A7B, 0x634E, 0x8811, 0xD124, 0x079A, 0x5EAF, 0xB5F0, 0xECC5, + 0x354F, 0x6C7A, 0x8725, 0xDE10, 0x08AE, 0x519B, 0xBAC4, 0xE3F1, + 0x4E8D, 0x17B8, 0xFCE7, 0xA5D2, 0x736C, 0x2A59, 0xC106, 0x9833, + 0xC2CB, 0x9BFE, 0x70A1, 0x2994, 0xFF2A, 0xA61F, 0x4D40, 0x1475, + 0xB909, 0xE03C, 0x0B63, 0x5256, 0x84E8, 0xDDDD, 0x3682, 0x6FB7, + 0x8372, 0xDA47, 0x3118, 0x682D, 0xBE93, 0xE7A6, 0x0CF9, 0x55CC, + 0xF8B0, 0xA185, 0x4ADA, 0x13EF, 0xC551, 0x9C64, 0x773B, 0x2E0E, + 0x74F6, 0x2DC3, 0xC69C, 0x9FA9, 0x4917, 0x1022, 0xFB7D, 0xA248, + 0x0F34, 0x5601, 0xBD5E, 0xE46B, 0x32D5, 0x6BE0, 0x80BF, 0xD98A, + 0x6A9E, 0x33AB, 0xD8F4, 0x81C1, 0x577F, 0x0E4A, 0xE515, 0xBC20, + 0x115C, 0x4869, 0xA336, 0xFA03, 0x2CBD, 0x7588, 0x9ED7, 0xC7E2, + 0x9D1A, 0xC42F, 0x2F70, 0x7645, 0xA0FB, 0xF9CE, 0x1291, 0x4BA4, + 0xE6D8, 0xBFED, 0x54B2, 0x0D87, 0xDB39, 0x820C, 0x6953, 0x3066, + 0xDCA3, 0x8596, 0x6EC9, 0x37FC, 0xE142, 0xB877, 0x5328, 0x0A1D, + 0xA761, 0xFE54, 0x150B, 0x4C3E, 0x9A80, 0xC3B5, 0x28EA, 0x71DF, + 0x2B27, 0x7212, 0x994D, 0xC078, 0x16C6, 0x4FF3, 0xA4AC, 0xFD99, + 0x50E5, 0x09D0, 0xE28F, 0xBBBA, 0x6D04, 0x3431, 0xDF6E, 0x865B, + 0x5FD1, 0x06E4, 0xEDBB, 0xB48E, 0x6230, 0x3B05, 0xD05A, 0x896F, + 0x2413, 0x7D26, 0x9679, 0xCF4C, 0x19F2, 0x40C7, 0xAB98, 0xF2AD, + 0xA855, 0xF160, 0x1A3F, 0x430A, 0x95B4, 0xCC81, 0x27DE, 0x7EEB, + 0xD397, 0x8AA2, 0x61FD, 0x38C8, 0xEE76, 0xB743, 0x5C1C, 0x0529, + 0xE9EC, 0xB0D9, 0x5B86, 0x02B3, 0xD40D, 0x8D38, 0x6667, 0x3F52, + 0x922E, 0xCB1B, 0x2044, 0x7971, 0xAFCF, 0xF6FA, 0x1DA5, 0x4490, + 0x1E68, 0x475D, 0xAC02, 0xF537, 0x2389, 0x7ABC, 0x91E3, 0xC8D6, + 0x65AA, 0x3C9F, 0xD7C0, 0x8EF5, 0x584B, 0x017E, 0xEA21, 0xB314 +}; + +/* This table was compiled using the polynom 0x755B */ +static const unsigned crc16_precompiled_755B[] = +{ + 0x0000, 0x755b, 0xeab6, 0x9fed, 0xa037, 0xd56c, 0x4a81, 0x3fda, /* 0x00 */ + 0x3535, 0x406e, 0xdf83, 0xaad8, 0x9502, 0xe059, 0x7fb4, 0x0aef, /* 0x08 */ + 0x6a6a, 0x1f31, 0x80dc, 0xf587, 0xca5d, 0xbf06, 0x20eb, 0x55b0, /* 0x10 */ + 0x5f5f, 0x2a04, 0xb5e9, 0xc0b2, 0xff68, 0x8a33, 0x15de, 0x6085, /* 0x18 */ + 0xd4d4, 0xa18f, 0x3e62, 0x4b39, 0x74e3, 0x01b8, 0x9e55, 0xeb0e, /* 0x20 */ + 0xe1e1, 0x94ba, 0x0b57, 0x7e0c, 0x41d6, 0x348d, 0xab60, 0xde3b, /* 0x28 */ + 0xbebe, 0xcbe5, 0x5408, 0x2153, 0x1e89, 0x6bd2, 0xf43f, 0x8164, /* 0x30 */ + 0x8b8b, 0xfed0, 0x613d, 0x1466, 0x2bbc, 0x5ee7, 0xc10a, 0xb451, /* 0x38 */ + 0xdcf3, 0xa9a8, 0x3645, 0x431e, 0x7cc4, 0x099f, 0x9672, 0xe329, /* 0x40 */ + 0xe9c6, 0x9c9d, 0x0370, 0x762b, 0x49f1, 0x3caa, 0xa347, 0xd61c, /* 0x48 */ + 0xb699, 0xc3c2, 0x5c2f, 0x2974, 0x16ae, 0x63f5, 0xfc18, 0x8943, /* 0x50 */ + 0x83ac, 0xf6f7, 0x691a, 0x1c41, 0x239b, 0x56c0, 0xc92d, 0xbc76, /* 0x58 */ + 0x0827, 0x7d7c, 0xe291, 0x97ca, 0xa810, 0xdd4b, 0x42a6, 0x37fd, /* 0x60 */ + 0x3d12, 0x4849, 0xd7a4, 0xa2ff, 0x9d25, 0xe87e, 0x7793, 0x02c8, /* 0x68 */ + 0x624d, 0x1716, 0x88fb, 0xfda0, 0xc27a, 0xb721, 0x28cc, 0x5d97, /* 0x70 */ + 0x5778, 0x2223, 0xbdce, 0xc895, 0xf74f, 0x8214, 0x1df9, 0x68a2, /* 0x78 */ + 0xccbd, 0xb9e6, 0x260b, 0x5350, 0x6c8a, 0x19d1, 0x863c, 0xf367, /* 0x80 */ + 0xf988, 0x8cd3, 0x133e, 0x6665, 0x59bf, 0x2ce4, 0xb309, 0xc652, /* 0x88 */ + 0xa6d7, 0xd38c, 0x4c61, 0x393a, 0x06e0, 0x73bb, 0xec56, 0x990d, /* 0x90 */ + 0x93e2, 0xe6b9, 0x7954, 0x0c0f, 0x33d5, 0x468e, 0xd963, 0xac38, /* 0x98 */ + 0x1869, 0x6d32, 0xf2df, 0x8784, 0xb85e, 0xcd05, 0x52e8, 0x27b3, /* 0xA0 */ + 0x2d5c, 0x5807, 0xc7ea, 0xb2b1, 0x8d6b, 0xf830, 0x67dd, 0x1286, /* 0xA8 */ + 0x7203, 0x0758, 0x98b5, 0xedee, 0xd234, 0xa76f, 0x3882, 0x4dd9, /* 0xB0 */ + 0x4736, 0x326d, 0xad80, 0xd8db, 0xe701, 0x925a, 0x0db7, 0x78ec, /* 0xB8 */ + 0x104e, 0x6515, 0xfaf8, 0x8fa3, 0xb079, 0xc522, 0x5acf, 0x2f94, /* 0xC0 */ + 0x257b, 0x5020, 0xcfcd, 0xba96, 0x854c, 0xf017, 0x6ffa, 0x1aa1, /* 0xC8 */ + 0x7a24, 0x0f7f, 0x9092, 0xe5c9, 0xda13, 0xaf48, 0x30a5, 0x45fe, /* 0xD0 */ + 0x4f11, 0x3a4a, 0xa5a7, 0xd0fc, 0xef26, 0x9a7d, 0x0590, 0x70cb, /* 0xD8 */ + 0xc49a, 0xb1c1, 0x2e2c, 0x5b77, 0x64ad, 0x11f6, 0x8e1b, 0xfb40, /* 0xE0 */ + 0xf1af, 0x84f4, 0x1b19, 0x6e42, 0x5198, 0x24c3, 0xbb2e, 0xce75, /* 0xE8 */ + 0xaef0, 0xdbab, 0x4446, 0x311d, 0x0ec7, 0x7b9c, 0xe471, 0x912a, /* 0xF0 */ + 0x9bc5, 0xee9e, 0x7173, 0x0428, 0x3bf2, 0x4ea9, 0xd144, 0xa41f /* 0xF8 */ +}; + +/* This table was compiled using the polynom: 0x9949 */ +static const unsigned crc16_precompiled_9949_reverse[] = +{ + 0x0000, 0x0ED2, 0x1DA4, 0x1376, 0x3B48, 0x359A, 0x26EC, 0x283E, + 0x7690, 0x7842, 0x6B34, 0x65E6, 0x4DD8, 0x430A, 0x507C, 0x5EAE, + 0xED20, 0xE3F2, 0xF084, 0xFE56, 0xD668, 0xD8BA, 0xCBCC, 0xC51E, + 0x9BB0, 0x9562, 0x8614, 0x88C6, 0xA0F8, 0xAE2A, 0xBD5C, 0xB38E, + 0xFF73, 0xF1A1, 0xE2D7, 0xEC05, 0xC43B, 0xCAE9, 0xD99F, 0xD74D, + 0x89E3, 0x8731, 0x9447, 0x9A95, 0xB2AB, 0xBC79, 0xAF0F, 0xA1DD, + 0x1253, 0x1C81, 0x0FF7, 0x0125, 0x291B, 0x27C9, 0x34BF, 0x3A6D, + 0x64C3, 0x6A11, 0x7967, 0x77B5, 0x5F8B, 0x5159, 0x422F, 0x4CFD, + 0xDBD5, 0xD507, 0xC671, 0xC8A3, 0xE09D, 0xEE4F, 0xFD39, 0xF3EB, + 0xAD45, 0xA397, 0xB0E1, 0xBE33, 0x960D, 0x98DF, 0x8BA9, 0x857B, + 0x36F5, 0x3827, 0x2B51, 0x2583, 0x0DBD, 0x036F, 0x1019, 0x1ECB, + 0x4065, 0x4EB7, 0x5DC1, 0x5313, 0x7B2D, 0x75FF, 0x6689, 0x685B, + 0x24A6, 0x2A74, 0x3902, 0x37D0, 0x1FEE, 0x113C, 0x024A, 0x0C98, + 0x5236, 0x5CE4, 0x4F92, 0x4140, 0x697E, 0x67AC, 0x74DA, 0x7A08, + 0xC986, 0xC754, 0xD422, 0xDAF0, 0xF2CE, 0xFC1C, 0xEF6A, 0xE1B8, + 0xBF16, 0xB1C4, 0xA2B2, 0xAC60, 0x845E, 0x8A8C, 0x99FA, 0x9728, + 0x9299, 0x9C4B, 0x8F3D, 0x81EF, 0xA9D1, 0xA703, 0xB475, 0xBAA7, + 0xE409, 0xEADB, 0xF9AD, 0xF77F, 0xDF41, 0xD193, 0xC2E5, 0xCC37, + 0x7FB9, 0x716B, 0x621D, 0x6CCF, 0x44F1, 0x4A23, 0x5955, 0x5787, + 0x0929, 0x07FB, 0x148D, 0x1A5F, 0x3261, 0x3CB3, 0x2FC5, 0x2117, + 0x6DEA, 0x6338, 0x704E, 0x7E9C, 0x56A2, 0x5870, 0x4B06, 0x45D4, + 0x1B7A, 0x15A8, 0x06DE, 0x080C, 0x2032, 0x2EE0, 0x3D96, 0x3344, + 0x80CA, 0x8E18, 0x9D6E, 0x93BC, 0xBB82, 0xB550, 0xA626, 0xA8F4, + 0xF65A, 0xF888, 0xEBFE, 0xE52C, 0xCD12, 0xC3C0, 0xD0B6, 0xDE64, + 0x494C, 0x479E, 0x54E8, 0x5A3A, 0x7204, 0x7CD6, 0x6FA0, 0x6172, + 0x3FDC, 0x310E, 0x2278, 0x2CAA, 0x0494, 0x0A46, 0x1930, 0x17E2, + 0xA46C, 0xAABE, 0xB9C8, 0xB71A, 0x9F24, 0x91F6, 0x8280, 0x8C52, + 0xD2FC, 0xDC2E, 0xCF58, 0xC18A, 0xE9B4, 0xE766, 0xF410, 0xFAC2, + 0xB63F, 0xB8ED, 0xAB9B, 0xA549, 0x8D77, 0x83A5, 0x90D3, 0x9E01, + 0xC0AF, 0xCE7D, 0xDD0B, 0xD3D9, 0xFBE7, 0xF535, 0xE643, 0xE891, + 0x5B1F, 0x55CD, 0x46BB, 0x4869, 0x6057, 0x6E85, 0x7DF3, 0x7321, + 0x2D8F, 0x235D, 0x302B, 0x3EF9, 0x16C7, 0x1815, 0x0B63, 0x05B1 +}; + +/* This table was compiled using the polynom: 0x3D65 */ +static const unsigned crc16_precompiled_3D65_reverse[] = +{ + 0x0000, 0x365E, 0x6CBC, 0x5AE2, 0xD978, 0xEF26, 0xB5C4, 0x839A, + 0xFF89, 0xC9D7, 0x9335, 0xA56B, 0x26F1, 0x10AF, 0x4A4D, 0x7C13, + 0xB26B, 0x8435, 0xDED7, 0xE889, 0x6B13, 0x5D4D, 0x07AF, 0x31F1, + 0x4DE2, 0x7BBC, 0x215E, 0x1700, 0x949A, 0xA2C4, 0xF826, 0xCE78, + 0x29AF, 0x1FF1, 0x4513, 0x734D, 0xF0D7, 0xC689, 0x9C6B, 0xAA35, + 0xD626, 0xE078, 0xBA9A, 0x8CC4, 0x0F5E, 0x3900, 0x63E2, 0x55BC, + 0x9BC4, 0xAD9A, 0xF778, 0xC126, 0x42BC, 0x74E2, 0x2E00, 0x185E, + 0x644D, 0x5213, 0x08F1, 0x3EAF, 0xBD35, 0x8B6B, 0xD189, 0xE7D7, + 0x535E, 0x6500, 0x3FE2, 0x09BC, 0x8A26, 0xBC78, 0xE69A, 0xD0C4, + 0xACD7, 0x9A89, 0xC06B, 0xF635, 0x75AF, 0x43F1, 0x1913, 0x2F4D, + 0xE135, 0xD76B, 0x8D89, 0xBBD7, 0x384D, 0x0E13, 0x54F1, 0x62AF, + 0x1EBC, 0x28E2, 0x7200, 0x445E, 0xC7C4, 0xF19A, 0xAB78, 0x9D26, + 0x7AF1, 0x4CAF, 0x164D, 0x2013, 0xA389, 0x95D7, 0xCF35, 0xF96B, + 0x8578, 0xB326, 0xE9C4, 0xDF9A, 0x5C00, 0x6A5E, 0x30BC, 0x06E2, + 0xC89A, 0xFEC4, 0xA426, 0x9278, 0x11E2, 0x27BC, 0x7D5E, 0x4B00, + 0x3713, 0x014D, 0x5BAF, 0x6DF1, 0xEE6B, 0xD835, 0x82D7, 0xB489, + 0xA6BC, 0x90E2, 0xCA00, 0xFC5E, 0x7FC4, 0x499A, 0x1378, 0x2526, + 0x5935, 0x6F6B, 0x3589, 0x03D7, 0x804D, 0xB613, 0xECF1, 0xDAAF, + 0x14D7, 0x2289, 0x786B, 0x4E35, 0xCDAF, 0xFBF1, 0xA113, 0x974D, + 0xEB5E, 0xDD00, 0x87E2, 0xB1BC, 0x3226, 0x0478, 0x5E9A, 0x68C4, + 0x8F13, 0xB94D, 0xE3AF, 0xD5F1, 0x566B, 0x6035, 0x3AD7, 0x0C89, + 0x709A, 0x46C4, 0x1C26, 0x2A78, 0xA9E2, 0x9FBC, 0xC55E, 0xF300, + 0x3D78, 0x0B26, 0x51C4, 0x679A, 0xE400, 0xD25E, 0x88BC, 0xBEE2, + 0xC2F1, 0xF4AF, 0xAE4D, 0x9813, 0x1B89, 0x2DD7, 0x7735, 0x416B, + 0xF5E2, 0xC3BC, 0x995E, 0xAF00, 0x2C9A, 0x1AC4, 0x4026, 0x7678, + 0x0A6B, 0x3C35, 0x66D7, 0x5089, 0xD313, 0xE54D, 0xBFAF, 0x89F1, + 0x4789, 0x71D7, 0x2B35, 0x1D6B, 0x9EF1, 0xA8AF, 0xF24D, 0xC413, + 0xB800, 0x8E5E, 0xD4BC, 0xE2E2, 0x6178, 0x5726, 0x0DC4, 0x3B9A, + 0xDC4D, 0xEA13, 0xB0F1, 0x86AF, 0x0535, 0x336B, 0x6989, 0x5FD7, + 0x23C4, 0x159A, 0x4F78, 0x7926, 0xFABC, 0xCCE2, 0x9600, 0xA05E, + 0x6E26, 0x5878, 0x029A, 0x34C4, 0xB75E, 0x8100, 0xDBE2, 0xEDBC, + 0x91AF, 0xA7F1, 0xFD13, 0xCB4D, 0x48D7, 0x7E89, 0x246B, 0x1235 +}; + +/* This table was compiled using the polynom: 0x080F */ +static const unsigned crc16_precompiled_080F[] = +{ + 0x0000, 0x080F, 0x101E, 0x1811, 0x203C, 0x2833, 0x3022, 0x382D, + 0x4078, 0x4877, 0x5066, 0x5869, 0x6044, 0x684B, 0x705A, 0x7855, + 0x80F0, 0x88FF, 0x90EE, 0x98E1, 0xA0CC, 0xA8C3, 0xB0D2, 0xB8DD, + 0xC088, 0xC887, 0xD096, 0xD899, 0xE0B4, 0xE8BB, 0xF0AA, 0xF8A5, + 0x09EF, 0x01E0, 0x19F1, 0x11FE, 0x29D3, 0x21DC, 0x39CD, 0x31C2, + 0x4997, 0x4198, 0x5989, 0x5186, 0x69AB, 0x61A4, 0x79B5, 0x71BA, + 0x891F, 0x8110, 0x9901, 0x910E, 0xA923, 0xA12C, 0xB93D, 0xB132, + 0xC967, 0xC168, 0xD979, 0xD176, 0xE95B, 0xE154, 0xF945, 0xF14A, + 0x13DE, 0x1BD1, 0x03C0, 0x0BCF, 0x33E2, 0x3BED, 0x23FC, 0x2BF3, + 0x53A6, 0x5BA9, 0x43B8, 0x4BB7, 0x739A, 0x7B95, 0x6384, 0x6B8B, + 0x932E, 0x9B21, 0x8330, 0x8B3F, 0xB312, 0xBB1D, 0xA30C, 0xAB03, + 0xD356, 0xDB59, 0xC348, 0xCB47, 0xF36A, 0xFB65, 0xE374, 0xEB7B, + 0x1A31, 0x123E, 0x0A2F, 0x0220, 0x3A0D, 0x3202, 0x2A13, 0x221C, + 0x5A49, 0x5246, 0x4A57, 0x4258, 0x7A75, 0x727A, 0x6A6B, 0x6264, + 0x9AC1, 0x92CE, 0x8ADF, 0x82D0, 0xBAFD, 0xB2F2, 0xAAE3, 0xA2EC, + 0xDAB9, 0xD2B6, 0xCAA7, 0xC2A8, 0xFA85, 0xF28A, 0xEA9B, 0xE294, + 0x27BC, 0x2FB3, 0x37A2, 0x3FAD, 0x0780, 0x0F8F, 0x179E, 0x1F91, + 0x67C4, 0x6FCB, 0x77DA, 0x7FD5, 0x47F8, 0x4FF7, 0x57E6, 0x5FE9, + 0xA74C, 0xAF43, 0xB752, 0xBF5D, 0x8770, 0x8F7F, 0x976E, 0x9F61, + 0xE734, 0xEF3B, 0xF72A, 0xFF25, 0xC708, 0xCF07, 0xD716, 0xDF19, + 0x2E53, 0x265C, 0x3E4D, 0x3642, 0x0E6F, 0x0660, 0x1E71, 0x167E, + 0x6E2B, 0x6624, 0x7E35, 0x763A, 0x4E17, 0x4618, 0x5E09, 0x5606, + 0xAEA3, 0xA6AC, 0xBEBD, 0xB6B2, 0x8E9F, 0x8690, 0x9E81, 0x968E, + 0xEEDB, 0xE6D4, 0xFEC5, 0xF6CA, 0xCEE7, 0xC6E8, 0xDEF9, 0xD6F6, + 0x3462, 0x3C6D, 0x247C, 0x2C73, 0x145E, 0x1C51, 0x0440, 0x0C4F, + 0x741A, 0x7C15, 0x6404, 0x6C0B, 0x5426, 0x5C29, 0x4438, 0x4C37, + 0xB492, 0xBC9D, 0xA48C, 0xAC83, 0x94AE, 0x9CA1, 0x84B0, 0x8CBF, + 0xF4EA, 0xFCE5, 0xE4F4, 0xECFB, 0xD4D6, 0xDCD9, 0xC4C8, 0xCCC7, + 0x3D8D, 0x3582, 0x2D93, 0x259C, 0x1DB1, 0x15BE, 0x0DAF, 0x05A0, + 0x7DF5, 0x75FA, 0x6DEB, 0x65E4, 0x5DC9, 0x55C6, 0x4DD7, 0x45D8, + 0xBD7D, 0xB572, 0xAD63, 0xA56C, 0x9D41, 0x954E, 0x8D5F, 0x8550, + 0xFD05, 0xF50A, 0xED1B, 0xE514, 0xDD39, 0xD536, 0xCD27, 0xC528 +}; + +/** + * Generated on Fri Jul 19 17:16:42 2019 + * by pycrc v0.9.2, https://pycrc.org + * using the configuration: + * - Width = 16 + * - Poly = 0x8005 + * - XorIn = 0xffff + * - ReflectIn = True + * - XorOut = 0xffff + * - ReflectOut = True + * - Algorithm = table-driven + */ +static const unsigned crc16_usb_table[] = { + 0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241, + 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440, + 0xcc01, 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40, + 0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841, + 0xd801, 0x18c0, 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40, + 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41, + 0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641, + 0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040, + 0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240, + 0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441, + 0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41, + 0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840, + 0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41, + 0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40, + 0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681, 0x2640, + 0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041, + 0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240, + 0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441, + 0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41, + 0xaa01, 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840, + 0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41, + 0xbe01, 0x7ec0, 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40, + 0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640, + 0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041, + 0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241, + 0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481, 0x5440, + 0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40, + 0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841, + 0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40, + 0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41, + 0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641, + 0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081, 0x4040 +}; + +static const uint16_t crc16_ccitt_start = 0xFFFF; +static const uint16_t crc16_ccitt_xorout = 0xFFFF; + +static const uint16_t crc16_usb_start = 0xFFFF; +static const uint16_t crc16_usb_xorout = 0xFFFF; + +/* two types of crcs are possible: unreflected (bits shift left) and + * reflected (bits shift right). + */ +static uint16_t crc16_unreflected(const uint8_t *buf, unsigned len, + uint16_t crc_in, const unsigned table[]) +{ + /* we use guints, rather than guint16s, as they are likely to be + faster. We just ignore the top 16 bits and let them do what they want. + */ + unsigned crc16 = (unsigned)crc_in; + + while( len-- != 0 ) + crc16 = table[((crc16 >> 8) ^ *buf++) & 0xff] ^ (crc16 << 8); + + return (uint16_t)crc16; +} + +static uint16_t crc16_reflected(const uint8_t *buf, unsigned len, + uint16_t crc_in, const unsigned table[]) +{ + /* we use guints, rather than guint16s, as they are likely to be + faster. We just ignore the top 16 bits and let them do what they want. + XXX - does any time saved not zero-extending uint16_t's to 32 bits + into a register outweigh any increased cache footprint from the + larger CRC table? */ + unsigned crc16 = (unsigned)crc_in; + + while( len-- != 0 ) + crc16 = table[(crc16 ^ *buf++) & 0xff] ^ (crc16 >> 8); + + return (uint16_t)crc16; +} + +uint16_t crc16_ccitt(const uint8_t *buf, unsigned len) +{ + return crc16_reflected(buf,len,crc16_ccitt_start,crc16_ccitt_table_reverse) + ^ crc16_ccitt_xorout; +} + +uint16_t crc16_x25_ccitt_seed(const uint8_t *buf, unsigned len, uint16_t seed) +{ + return crc16_unreflected(buf,len,seed,crc16_ccitt_table); +} + +uint16_t crc16_ccitt_seed(const uint8_t *buf, unsigned len, uint16_t seed) +{ + return crc16_reflected(buf,len,seed,crc16_ccitt_table_reverse) + ^ crc16_ccitt_xorout; +} + +/* ISO14443-3, section 6.2.4: For ISO14443-A, the polynomial 0x1021 is + used, the initial register value shall be 0x6363, the final register + value is not XORed with anything. */ +uint16_t crc16_iso14443a(const uint8_t *buf, unsigned len) +{ + return crc16_reflected(buf,len, 0x6363 ,crc16_ccitt_table_reverse); +} + +uint16_t crc16_usb(const uint8_t *buf, unsigned len) +{ + return crc16_reflected(buf, len, crc16_usb_start, crc16_usb_table) + ^ crc16_usb_xorout; +} + +uint16_t crc16_0x5935(const uint8_t *buf, uint32_t len, uint16_t seed) +{ + return crc16_unreflected(buf, len, seed, crc16_precompiled_5935); +} + +uint16_t crc16_0x755B(const uint8_t *buf, uint32_t len, uint16_t seed) +{ + return crc16_unreflected(buf, len, seed, crc16_precompiled_755B); +} + +uint16_t crc16_0x9949_seed(const uint8_t *buf, unsigned len, uint16_t seed) +{ + return crc16_reflected(buf, len, seed, crc16_precompiled_9949_reverse); +} + +uint16_t crc16_0x3D65_seed(const uint8_t *buf, unsigned len, uint16_t seed) +{ + return crc16_reflected(buf, len, seed, crc16_precompiled_3D65_reverse); +} + +uint16_t crc16_0x080F_seed(const uint8_t *buf, unsigned len, uint16_t seed) +{ + uint16_t crc = seed; + + if (len > 0) + { + while (len-- > 0) + { + uint8_t data = *buf++; + crc = crc16_precompiled_080F[((crc >> 8) ^ data)] ^ (crc << 8); + } + } + + return crc; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/crc16.h b/wsutil/crc16.h new file mode 100644 index 00000000..2c1be91d --- /dev/null +++ b/wsutil/crc16.h @@ -0,0 +1,113 @@ +/** @file + * Declaration of CRC-16 routines and table + * + * 2004 Richard van der Hoff <richardv@mxtelecom.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef __CRC16_H__ +#define __CRC16_H__ + +#include <wireshark.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* Calculate the CCITT/ITU/CRC-16 16-bit CRC + + (parameters for this CRC are: + Polynomial: x^16 + x^12 + x^5 + 1 (0x1021); + Start value 0xFFFF; + XOR result with 0xFFFF; + First bit is LSB) +*/ + +/** Compute CRC16 CCITT checksum of a buffer of data. + @param buf The buffer containing the data. + @param len The number of bytes to include in the computation. + @return The CRC16 CCITT checksum. */ +WS_DLL_PUBLIC uint16_t crc16_ccitt(const uint8_t *buf, unsigned len); + +/** Compute CRC16 X.25 CCITT checksum of a buffer of data. + @param buf The buffer containing the data. + @param len The number of bytes to include in the computation. + @return The CRC16 X.25 CCITT checksum. */ +WS_DLL_PUBLIC uint16_t crc16_x25_ccitt_seed(const uint8_t *buf, unsigned len, uint16_t seed); + +/** Compute CRC16 CCITT checksum of a buffer of data. If computing the + * checksum over multiple buffers and you want to feed the partial CRC16 + * back in, remember to take the 1's complement of the partial CRC16 first. + @param buf The buffer containing the data. + @param len The number of bytes to include in the computation. + @param seed The seed to use. + @return The CRC16 CCITT checksum (using the given seed). */ +WS_DLL_PUBLIC uint16_t crc16_ccitt_seed(const uint8_t *buf, unsigned len, uint16_t seed); + +/** Compute the 16bit CRC_A value of a buffer as defined in ISO14443-3. + @param buf The buffer containing the data. + @param len The number of bytes to include in the computation. + @return the CRC16 checksum for the buffer */ +WS_DLL_PUBLIC uint16_t crc16_iso14443a(const uint8_t *buf, unsigned len); + +/** Compute the 16bit CRC value of a buffer as defined in USB Specification. + @param buf The buffer containing the data. + @param len The number of bytes to include in the computation. + @return the CRC16 checksum for the buffer */ +WS_DLL_PUBLIC uint16_t crc16_usb(const uint8_t *buf, unsigned len); + +/** Calculates a CRC16 checksum for the given buffer with the polynom + * 0x5935 using a precompiled CRC table + * @param buf a pointer to a buffer of the given length + * @param len the length of the given buffer + * @param seed The seed to use. + * @return the CRC16 checksum for the buffer + */ +WS_DLL_PUBLIC uint16_t crc16_0x5935(const uint8_t *buf, uint32_t len, uint16_t seed); + +/** Calculates a CRC16 checksum for the given buffer with the polynom + * 0x755B using a precompiled CRC table + * @param buf a pointer to a buffer of the given length + * @param len the length of the given buffer + * @param seed The seed to use. + * @return the CRC16 checksum for the buffer + */ +WS_DLL_PUBLIC uint16_t crc16_0x755B(const uint8_t *buf, uint32_t len, uint16_t seed); + +/** Computes CRC16 checksum for the given data with the polynom 0x9949 using + * precompiled CRC table + * @param buf a pointer to a buffer of the given length + * @param len the length of the given buffer + * @param seed The seed to use. + * @return the CRC16 checksum for the buffer + */ +WS_DLL_PUBLIC uint16_t crc16_0x9949_seed(const uint8_t *buf, unsigned len, uint16_t seed); + +/** Computes CRC16 checksum for the given data with the polynom 0x3D65 using + * precompiled CRC table + * @param buf a pointer to a buffer of the given length + * @param len the length of the given buffer + * @param seed The seed to use. + * @return the CRC16 checksum for the buffer + */ +WS_DLL_PUBLIC uint16_t crc16_0x3D65_seed(const uint8_t *buf, unsigned len, uint16_t seed); + +/** Computes CRC16 checksum for the given data with the polynom 0x080F using + * precompiled CRC table + * @param buf a pointer to a buffer of the given length + * @param len the length of the given buffer + * @param seed The seed to use. + * @return the CRC16 checksum for the buffer + */ +WS_DLL_PUBLIC uint16_t crc16_0x080F_seed(const uint8_t *buf, unsigned len, uint16_t seed); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* crc16.h */ diff --git a/wsutil/crc32.c b/wsutil/crc32.c new file mode 100644 index 00000000..4be3d651 --- /dev/null +++ b/wsutil/crc32.c @@ -0,0 +1,461 @@ +/* crc32.c + * CRC-32 routine + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Credits: + * + * Table from Solomon Peachy + * Routine from Chris Waters + */ + +#include "config.h" + +#include <wsutil/crc32.h> + +#define CRC32_ACCUMULATE(c,d,table) (c=(c>>8)^(table)[(c^(d))&0xFF]) + +/*****************************************************************/ +/* */ +/* CRC32C LOOKUP TABLE */ +/* +++================ */ +/* The following CRC lookup table was generated automagically */ +/* by the Rocksoft^tm Model CRC Algorithm Table Generation */ +/* Program V1.0 using the following model parameters: */ +/* */ +/* Width : 4 bytes. */ +/* Poly : 0x1EDC6F41L */ +/* Reverse : true. */ +/* */ +/* For more information on the Rocksoft^tm Model CRC Algorithm, */ +/* see the document titled "A Painless Guide to CRC Error */ +/* Detection Algorithms" by Ross Williams */ +/* (ross@guest.adelaide.edu.au.). This document is likely to be */ +/* in the FTP archive "ftp.adelaide.edu.au/pub/rocksoft". */ +/* */ +/*****************************************************************/ +#define CRC32C(c,d) CRC32_ACCUMULATE(c,d,crc32c_table) + +static const uint32_t crc32c_table[256] = { + 0x00000000U, 0xF26B8303U, 0xE13B70F7U, 0x1350F3F4U, 0xC79A971FU, + 0x35F1141CU, 0x26A1E7E8U, 0xD4CA64EBU, 0x8AD958CFU, 0x78B2DBCCU, + 0x6BE22838U, 0x9989AB3BU, 0x4D43CFD0U, 0xBF284CD3U, 0xAC78BF27U, + 0x5E133C24U, 0x105EC76FU, 0xE235446CU, 0xF165B798U, 0x030E349BU, + 0xD7C45070U, 0x25AFD373U, 0x36FF2087U, 0xC494A384U, 0x9A879FA0U, + 0x68EC1CA3U, 0x7BBCEF57U, 0x89D76C54U, 0x5D1D08BFU, 0xAF768BBCU, + 0xBC267848U, 0x4E4DFB4BU, 0x20BD8EDEU, 0xD2D60DDDU, 0xC186FE29U, + 0x33ED7D2AU, 0xE72719C1U, 0x154C9AC2U, 0x061C6936U, 0xF477EA35U, + 0xAA64D611U, 0x580F5512U, 0x4B5FA6E6U, 0xB93425E5U, 0x6DFE410EU, + 0x9F95C20DU, 0x8CC531F9U, 0x7EAEB2FAU, 0x30E349B1U, 0xC288CAB2U, + 0xD1D83946U, 0x23B3BA45U, 0xF779DEAEU, 0x05125DADU, 0x1642AE59U, + 0xE4292D5AU, 0xBA3A117EU, 0x4851927DU, 0x5B016189U, 0xA96AE28AU, + 0x7DA08661U, 0x8FCB0562U, 0x9C9BF696U, 0x6EF07595U, 0x417B1DBCU, + 0xB3109EBFU, 0xA0406D4BU, 0x522BEE48U, 0x86E18AA3U, 0x748A09A0U, + 0x67DAFA54U, 0x95B17957U, 0xCBA24573U, 0x39C9C670U, 0x2A993584U, + 0xD8F2B687U, 0x0C38D26CU, 0xFE53516FU, 0xED03A29BU, 0x1F682198U, + 0x5125DAD3U, 0xA34E59D0U, 0xB01EAA24U, 0x42752927U, 0x96BF4DCCU, + 0x64D4CECFU, 0x77843D3BU, 0x85EFBE38U, 0xDBFC821CU, 0x2997011FU, + 0x3AC7F2EBU, 0xC8AC71E8U, 0x1C661503U, 0xEE0D9600U, 0xFD5D65F4U, + 0x0F36E6F7U, 0x61C69362U, 0x93AD1061U, 0x80FDE395U, 0x72966096U, + 0xA65C047DU, 0x5437877EU, 0x4767748AU, 0xB50CF789U, 0xEB1FCBADU, + 0x197448AEU, 0x0A24BB5AU, 0xF84F3859U, 0x2C855CB2U, 0xDEEEDFB1U, + 0xCDBE2C45U, 0x3FD5AF46U, 0x7198540DU, 0x83F3D70EU, 0x90A324FAU, + 0x62C8A7F9U, 0xB602C312U, 0x44694011U, 0x5739B3E5U, 0xA55230E6U, + 0xFB410CC2U, 0x092A8FC1U, 0x1A7A7C35U, 0xE811FF36U, 0x3CDB9BDDU, + 0xCEB018DEU, 0xDDE0EB2AU, 0x2F8B6829U, 0x82F63B78U, 0x709DB87BU, + 0x63CD4B8FU, 0x91A6C88CU, 0x456CAC67U, 0xB7072F64U, 0xA457DC90U, + 0x563C5F93U, 0x082F63B7U, 0xFA44E0B4U, 0xE9141340U, 0x1B7F9043U, + 0xCFB5F4A8U, 0x3DDE77ABU, 0x2E8E845FU, 0xDCE5075CU, 0x92A8FC17U, + 0x60C37F14U, 0x73938CE0U, 0x81F80FE3U, 0x55326B08U, 0xA759E80BU, + 0xB4091BFFU, 0x466298FCU, 0x1871A4D8U, 0xEA1A27DBU, 0xF94AD42FU, + 0x0B21572CU, 0xDFEB33C7U, 0x2D80B0C4U, 0x3ED04330U, 0xCCBBC033U, + 0xA24BB5A6U, 0x502036A5U, 0x4370C551U, 0xB11B4652U, 0x65D122B9U, + 0x97BAA1BAU, 0x84EA524EU, 0x7681D14DU, 0x2892ED69U, 0xDAF96E6AU, + 0xC9A99D9EU, 0x3BC21E9DU, 0xEF087A76U, 0x1D63F975U, 0x0E330A81U, + 0xFC588982U, 0xB21572C9U, 0x407EF1CAU, 0x532E023EU, 0xA145813DU, + 0x758FE5D6U, 0x87E466D5U, 0x94B49521U, 0x66DF1622U, 0x38CC2A06U, + 0xCAA7A905U, 0xD9F75AF1U, 0x2B9CD9F2U, 0xFF56BD19U, 0x0D3D3E1AU, + 0x1E6DCDEEU, 0xEC064EEDU, 0xC38D26C4U, 0x31E6A5C7U, 0x22B65633U, + 0xD0DDD530U, 0x0417B1DBU, 0xF67C32D8U, 0xE52CC12CU, 0x1747422FU, + 0x49547E0BU, 0xBB3FFD08U, 0xA86F0EFCU, 0x5A048DFFU, 0x8ECEE914U, + 0x7CA56A17U, 0x6FF599E3U, 0x9D9E1AE0U, 0xD3D3E1ABU, 0x21B862A8U, + 0x32E8915CU, 0xC083125FU, 0x144976B4U, 0xE622F5B7U, 0xF5720643U, + 0x07198540U, 0x590AB964U, 0xAB613A67U, 0xB831C993U, 0x4A5A4A90U, + 0x9E902E7BU, 0x6CFBAD78U, 0x7FAB5E8CU, 0x8DC0DD8FU, 0xE330A81AU, + 0x115B2B19U, 0x020BD8EDU, 0xF0605BEEU, 0x24AA3F05U, 0xD6C1BC06U, + 0xC5914FF2U, 0x37FACCF1U, 0x69E9F0D5U, 0x9B8273D6U, 0x88D28022U, + 0x7AB90321U, 0xAE7367CAU, 0x5C18E4C9U, 0x4F48173DU, 0xBD23943EU, + 0xF36E6F75U, 0x0105EC76U, 0x12551F82U, 0xE03E9C81U, 0x34F4F86AU, + 0xC69F7B69U, 0xD5CF889DU, 0x27A40B9EU, 0x79B737BAU, 0x8BDCB4B9U, + 0x988C474DU, 0x6AE7C44EU, 0xBE2DA0A5U, 0x4C4623A6U, 0x5F16D052U, + 0xAD7D5351U }; + +/* + * Table for the AUTODIN/HDLC/802.x CRC. + * + * Polynomial is + * + * x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + x^8 + + * x^7 + x^5 + x^4 + x^2 + x + 1 + */ +static const uint32_t crc32_ccitt_table[256] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, + 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, + 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, + 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, + 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, + 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, + 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, + 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, + 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, + 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, + 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, + 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, + 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, + 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, + 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, + 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, + 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, + 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, + 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, + 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, + 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, + 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, + 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, + 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, + 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, + 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, + 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, + 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, + 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, + 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, + 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, + 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, + 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, + 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, + 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, + 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, + 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, + 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, + 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, + 0x2d02ef8d +}; + +/* + * Table for the MPEG-2 CRC. + * + * Polynomial is + * + * x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + x^8 + + * x^7 + x^5 + x^4 + x^2 + x + 1 + * + * (which is the same polynomial as the one above us). + * + * NOTE: this is also used for ATM AAL5. + */ +static const uint32_t crc32_mpeg2_table[256] = { + 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, + 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, + 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7, + 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, + 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, + 0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef, + 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, + 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, + 0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, + 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, + 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, + 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, + 0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, + 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, + 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, + 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, + 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050, + 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, + 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, + 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, + 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1, + 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, + 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, + 0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9, + 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, + 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, + 0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, + 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, + 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, + 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, + 0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, + 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, + 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, + 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, + 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676, + 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, + 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, + 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, + 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 +}; + +/* This table was compiled using the polynom: 0x0AA725CF*/ +static const uint32_t crc32_0AA725CF_reverse[] = { + 0x00000000U, 0xCEAA95CEU, 0x7A1CE13DU, 0xB4B674F3U, + 0xF439C27AU, 0x3A9357B4U, 0x8E252347U, 0x408FB689U, + 0x0F3A4E55U, 0xC190DB9BU, 0x7526AF68U, 0xBB8C3AA6U, + 0xFB038C2FU, 0x35A919E1U, 0x811F6D12U, 0x4FB5F8DCU, + 0x1E749CAAU, 0xD0DE0964U, 0x64687D97U, 0xAAC2E859U, + 0xEA4D5ED0U, 0x24E7CB1EU, 0x9051BFEDU, 0x5EFB2A23U, + 0x114ED2FFU, 0xDFE44731U, 0x6B5233C2U, 0xA5F8A60CU, + 0xE5771085U, 0x2BDD854BU, 0x9F6BF1B8U, 0x51C16476U, + 0x3CE93954U, 0xF243AC9AU, 0x46F5D869U, 0x885F4DA7U, + 0xC8D0FB2EU, 0x067A6EE0U, 0xB2CC1A13U, 0x7C668FDDU, + 0x33D37701U, 0xFD79E2CFU, 0x49CF963CU, 0x876503F2U, + 0xC7EAB57BU, 0x094020B5U, 0xBDF65446U, 0x735CC188U, + 0x229DA5FEU, 0xEC373030U, 0x588144C3U, 0x962BD10DU, + 0xD6A46784U, 0x180EF24AU, 0xACB886B9U, 0x62121377U, + 0x2DA7EBABU, 0xE30D7E65U, 0x57BB0A96U, 0x99119F58U, + 0xD99E29D1U, 0x1734BC1FU, 0xA382C8ECU, 0x6D285D22U, + 0x79D272A8U, 0xB778E766U, 0x03CE9395U, 0xCD64065BU, + 0x8DEBB0D2U, 0x4341251CU, 0xF7F751EFU, 0x395DC421U, + 0x76E83CFDU, 0xB842A933U, 0x0CF4DDC0U, 0xC25E480EU, + 0x82D1FE87U, 0x4C7B6B49U, 0xF8CD1FBAU, 0x36678A74U, + 0x67A6EE02U, 0xA90C7BCCU, 0x1DBA0F3FU, 0xD3109AF1U, + 0x939F2C78U, 0x5D35B9B6U, 0xE983CD45U, 0x2729588BU, + 0x689CA057U, 0xA6363599U, 0x1280416AU, 0xDC2AD4A4U, + 0x9CA5622DU, 0x520FF7E3U, 0xE6B98310U, 0x281316DEU, + 0x453B4BFCU, 0x8B91DE32U, 0x3F27AAC1U, 0xF18D3F0FU, + 0xB1028986U, 0x7FA81C48U, 0xCB1E68BBU, 0x05B4FD75U, + 0x4A0105A9U, 0x84AB9067U, 0x301DE494U, 0xFEB7715AU, + 0xBE38C7D3U, 0x7092521DU, 0xC42426EEU, 0x0A8EB320U, + 0x5B4FD756U, 0x95E54298U, 0x2153366BU, 0xEFF9A3A5U, + 0xAF76152CU, 0x61DC80E2U, 0xD56AF411U, 0x1BC061DFU, + 0x54759903U, 0x9ADF0CCDU, 0x2E69783EU, 0xE0C3EDF0U, + 0xA04C5B79U, 0x6EE6CEB7U, 0xDA50BA44U, 0x14FA2F8AU, + 0xF3A4E550U, 0x3D0E709EU, 0x89B8046DU, 0x471291A3U, + 0x079D272AU, 0xC937B2E4U, 0x7D81C617U, 0xB32B53D9U, + 0xFC9EAB05U, 0x32343ECBU, 0x86824A38U, 0x4828DFF6U, + 0x08A7697FU, 0xC60DFCB1U, 0x72BB8842U, 0xBC111D8CU, + 0xEDD079FAU, 0x237AEC34U, 0x97CC98C7U, 0x59660D09U, + 0x19E9BB80U, 0xD7432E4EU, 0x63F55ABDU, 0xAD5FCF73U, + 0xE2EA37AFU, 0x2C40A261U, 0x98F6D692U, 0x565C435CU, + 0x16D3F5D5U, 0xD879601BU, 0x6CCF14E8U, 0xA2658126U, + 0xCF4DDC04U, 0x01E749CAU, 0xB5513D39U, 0x7BFBA8F7U, + 0x3B741E7EU, 0xF5DE8BB0U, 0x4168FF43U, 0x8FC26A8DU, + 0xC0779251U, 0x0EDD079FU, 0xBA6B736CU, 0x74C1E6A2U, + 0x344E502BU, 0xFAE4C5E5U, 0x4E52B116U, 0x80F824D8U, + 0xD13940AEU, 0x1F93D560U, 0xAB25A193U, 0x658F345DU, + 0x250082D4U, 0xEBAA171AU, 0x5F1C63E9U, 0x91B6F627U, + 0xDE030EFBU, 0x10A99B35U, 0xA41FEFC6U, 0x6AB57A08U, + 0x2A3ACC81U, 0xE490594FU, 0x50262DBCU, 0x9E8CB872U, + 0x8A7697F8U, 0x44DC0236U, 0xF06A76C5U, 0x3EC0E30BU, + 0x7E4F5582U, 0xB0E5C04CU, 0x0453B4BFU, 0xCAF92171U, + 0x854CD9ADU, 0x4BE64C63U, 0xFF503890U, 0x31FAAD5EU, + 0x71751BD7U, 0xBFDF8E19U, 0x0B69FAEAU, 0xC5C36F24U, + 0x94020B52U, 0x5AA89E9CU, 0xEE1EEA6FU, 0x20B47FA1U, + 0x603BC928U, 0xAE915CE6U, 0x1A272815U, 0xD48DBDDBU, + 0x9B384507U, 0x5592D0C9U, 0xE124A43AU, 0x2F8E31F4U, + 0x6F01877DU, 0xA1AB12B3U, 0x151D6640U, 0xDBB7F38EU, + 0xB69FAEACU, 0x78353B62U, 0xCC834F91U, 0x0229DA5FU, + 0x42A66CD6U, 0x8C0CF918U, 0x38BA8DEBU, 0xF6101825U, + 0xB9A5E0F9U, 0x770F7537U, 0xC3B901C4U, 0x0D13940AU, + 0x4D9C2283U, 0x8336B74DU, 0x3780C3BEU, 0xF92A5670U, + 0xA8EB3206U, 0x6641A7C8U, 0xD2F7D33BU, 0x1C5D46F5U, + 0x5CD2F07CU, 0x927865B2U, 0x26CE1141U, 0xE864848FU, + 0xA7D17C53U, 0x697BE99DU, 0xDDCD9D6EU, 0x136708A0U, + 0x53E8BE29U, 0x9D422BE7U, 0x29F45F14U, 0xE75ECADAU +}; + +/* This table was compiled using the polynom: 0x5D6DCB */ +static const uint32_t crc32_5D6DCB[] = +{ + 0x00000000, 0x005d6dcb, 0x00badb96, 0x00e7b65d, + 0x0028dae7, 0x0075b72c, 0x00920171, 0x00cf6cba, + 0x0051b5ce, 0x000cd805, 0x00eb6e58, 0x00b60393, + 0x00796f29, 0x002402e2, 0x00c3b4bf, 0x009ed974, + 0x00a36b9c, 0x00fe0657, 0x0019b00a, 0x0044ddc1, + 0x008bb17b, 0x00d6dcb0, 0x00316aed, 0x006c0726, + 0x00f2de52, 0x00afb399, 0x004805c4, 0x0015680f, + 0x00da04b5, 0x0087697e, 0x0060df23, 0x003db2e8, + 0x001bbaf3, 0x0046d738, 0x00a16165, 0x00fc0cae, + 0x00336014, 0x006e0ddf, 0x0089bb82, 0x00d4d649, + 0x004a0f3d, 0x001762f6, 0x00f0d4ab, 0x00adb960, + 0x0062d5da, 0x003fb811, 0x00d80e4c, 0x00856387, + 0x00b8d16f, 0x00e5bca4, 0x00020af9, 0x005f6732, + 0x00900b88, 0x00cd6643, 0x002ad01e, 0x0077bdd5, + 0x00e964a1, 0x00b4096a, 0x0053bf37, 0x000ed2fc, + 0x00c1be46, 0x009cd38d, 0x007b65d0, 0x0026081b, + 0x003775e6, 0x006a182d, 0x008dae70, 0x00d0c3bb, + 0x001faf01, 0x0042c2ca, 0x00a57497, 0x00f8195c, + 0x0066c028, 0x003bade3, 0x00dc1bbe, 0x00817675, + 0x004e1acf, 0x00137704, 0x00f4c159, 0x00a9ac92, + 0x00941e7a, 0x00c973b1, 0x002ec5ec, 0x0073a827, + 0x00bcc49d, 0x00e1a956, 0x00061f0b, 0x005b72c0, + 0x00c5abb4, 0x0098c67f, 0x007f7022, 0x00221de9, + 0x00ed7153, 0x00b01c98, 0x0057aac5, 0x000ac70e, + 0x002ccf15, 0x0071a2de, 0x00961483, 0x00cb7948, + 0x000415f2, 0x00597839, 0x00bece64, 0x00e3a3af, + 0x007d7adb, 0x00201710, 0x00c7a14d, 0x009acc86, + 0x0055a03c, 0x0008cdf7, 0x00ef7baa, 0x00b21661, + 0x008fa489, 0x00d2c942, 0x00357f1f, 0x006812d4, + 0x00a77e6e, 0x00fa13a5, 0x001da5f8, 0x0040c833, + 0x00de1147, 0x00837c8c, 0x0064cad1, 0x0039a71a, + 0x00f6cba0, 0x00aba66b, 0x004c1036, 0x00117dfd, + 0x006eebcc, 0x00338607, 0x00d4305a, 0x00895d91, + 0x0046312b, 0x001b5ce0, 0x00fceabd, 0x00a18776, + 0x003f5e02, 0x006233c9, 0x00858594, 0x00d8e85f, + 0x001784e5, 0x004ae92e, 0x00ad5f73, 0x00f032b8, + 0x00cd8050, 0x0090ed9b, 0x00775bc6, 0x002a360d, + 0x00e55ab7, 0x00b8377c, 0x005f8121, 0x0002ecea, + 0x009c359e, 0x00c15855, 0x0026ee08, 0x007b83c3, + 0x00b4ef79, 0x00e982b2, 0x000e34ef, 0x00535924, + 0x0075513f, 0x00283cf4, 0x00cf8aa9, 0x0092e762, + 0x005d8bd8, 0x0000e613, 0x00e7504e, 0x00ba3d85, + 0x0024e4f1, 0x0079893a, 0x009e3f67, 0x00c352ac, + 0x000c3e16, 0x005153dd, 0x00b6e580, 0x00eb884b, + 0x00d63aa3, 0x008b5768, 0x006ce135, 0x00318cfe, + 0x00fee044, 0x00a38d8f, 0x00443bd2, 0x00195619, + 0x00878f6d, 0x00dae2a6, 0x003d54fb, 0x00603930, + 0x00af558a, 0x00f23841, 0x00158e1c, 0x0048e3d7, + 0x00599e2a, 0x0004f3e1, 0x00e345bc, 0x00be2877, + 0x007144cd, 0x002c2906, 0x00cb9f5b, 0x0096f290, + 0x00082be4, 0x0055462f, 0x00b2f072, 0x00ef9db9, + 0x0020f103, 0x007d9cc8, 0x009a2a95, 0x00c7475e, + 0x00faf5b6, 0x00a7987d, 0x00402e20, 0x001d43eb, + 0x00d22f51, 0x008f429a, 0x0068f4c7, 0x0035990c, + 0x00ab4078, 0x00f62db3, 0x00119bee, 0x004cf625, + 0x00839a9f, 0x00def754, 0x00394109, 0x00642cc2, + 0x004224d9, 0x001f4912, 0x00f8ff4f, 0x00a59284, + 0x006afe3e, 0x003793f5, 0x00d025a8, 0x008d4863, + 0x00139117, 0x004efcdc, 0x00a94a81, 0x00f4274a, + 0x003b4bf0, 0x0066263b, 0x00819066, 0x00dcfdad, + 0x00e14f45, 0x00bc228e, 0x005b94d3, 0x0006f918, + 0x00c995a2, 0x0094f869, 0x00734e34, 0x002e23ff, + 0x00b0fa8b, 0x00ed9740, 0x000a211d, 0x00574cd6, + 0x0098206c, 0x00c54da7, 0x0022fbfa, 0x007f9631 +}; + +uint32_t +crc32c_table_lookup (unsigned char pos) +{ + return crc32c_table[pos]; +} + +uint32_t +crc32_ccitt_table_lookup (unsigned char pos) +{ + return crc32_ccitt_table[pos]; +} + +uint32_t +crc32c_calculate(const void *buf, int len, uint32_t crc) +{ + const uint8_t *p = (const uint8_t *)buf; + crc = CRC32C_SWAP(crc); + while (len-- > 0) { + CRC32C(crc, *p++); + } + return CRC32C_SWAP(crc); +} + +uint32_t +crc32c_calculate_no_swap(const void *buf, int len, uint32_t crc) +{ + const uint8_t *p = (const uint8_t *)buf; + while (len-- > 0) { + CRC32C(crc, *p++); + } + + return crc; +} + +uint32_t +crc32_ccitt(const uint8_t *buf, unsigned len) +{ + return (crc32_ccitt_seed(buf, len, CRC32_CCITT_SEED)); +} + +uint32_t +crc32_ccitt_seed(const uint8_t *buf, unsigned len, uint32_t seed) +{ + unsigned i; + uint32_t crc32 = seed; + + for (i = 0; i < len; i++) + CRC32_ACCUMULATE(crc32, buf[i], crc32_ccitt_table); + + return ( ~crc32 ); +} + +uint32_t +crc32_mpeg2_seed(const uint8_t *buf, unsigned len, uint32_t seed) +{ + unsigned i; + uint32_t crc32; + + crc32 = seed; + + for (i = 0; i < len; i++) + crc32 = (crc32 << 8) ^ crc32_mpeg2_table[((crc32 >> 24) ^ buf[i]) & 0xff]; + + return ( crc32 ); +} + +uint32_t +crc32_0x0AA725CF_seed(const uint8_t *buf, unsigned len, uint32_t seed) +{ + unsigned crc32; + + crc32 = (unsigned)seed; + while( len-- != 0 ) + CRC32_ACCUMULATE(crc32, *buf++, crc32_0AA725CF_reverse); + + return (uint32_t)crc32; +} + +uint32_t +crc32_0x5D6DCB_seed(const uint8_t *buf, unsigned len, uint32_t seed) +{ + uint32_t crc = seed; + if (len > 0) + { + while (len-- > 0) + { + uint8_t data = *buf++; + /* XOR data with CRC2, look up result, then XOR that with CRC; */ + crc = crc32_5D6DCB[((crc >> 16) ^ data) & 0xff] ^ (crc << 8); + } + } + return (crc & 0x00ffffff); +} + + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 8 + * tab-width: 8 + * indent-tabs-mode: t + * End: + * + * vi: set shiftwidth=8 tabstop=8 noexpandtab: + * :indentSize=8:tabSize=8:noTabs=false: + */ diff --git a/wsutil/crc32.h b/wsutil/crc32.h new file mode 100644 index 00000000..c6891730 --- /dev/null +++ b/wsutil/crc32.h @@ -0,0 +1,106 @@ +/** @file + * Declaration of CRC-32 routine and table + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __CRC32_H__ +#define __CRC32_H__ + +#include <wireshark.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#define CRC32_CCITT_SEED 0xFFFFFFFF +#define CRC32C_PRELOAD 0xffffffff +#define CRC32_MPEG2_SEED 0xFFFFFFFF + +/* + * Byte swap fix contributed by Dave Wysochanski <davidw@netapp.com>. + */ +#define CRC32C_SWAP(crc32c_value) \ + (((crc32c_value & 0xff000000) >> 24) | \ + ((crc32c_value & 0x00ff0000) >> 8) | \ + ((crc32c_value & 0x0000ff00) << 8) | \ + ((crc32c_value & 0x000000ff) << 24)) + +/** Lookup the crc value in the crc32_ccitt_table + @param pos Position in the table. */ +WS_DLL_PUBLIC uint32_t crc32_ccitt_table_lookup (unsigned char pos); + +/** Lookup the crc value in the crc32c_table + @param pos Position in the table. */ +WS_DLL_PUBLIC uint32_t crc32c_table_lookup (unsigned char pos); + +/** Compute CRC32C checksum of a buffer of data. + @param buf The buffer containing the data. + @param len The number of bytes to include in the computation. + @param crc The preload value for the CRC32C computation. + @return The CRC32C checksum. */ +WS_DLL_PUBLIC uint32_t crc32c_calculate(const void *buf, int len, uint32_t crc); + +/** Compute CRC32C checksum of a buffer of data without swapping seed crc + or completed checksum + @param buf The buffer containing the data. + @param len The number of bytes to include in the computation. + @param crc The preload value for the CRC32C computation. + @return The CRC32C checksum. */ +WS_DLL_PUBLIC uint32_t crc32c_calculate_no_swap(const void *buf, int len, uint32_t crc); + +/** Compute CRC32 CCITT checksum of a buffer of data. + @param buf The buffer containing the data. + @param len The number of bytes to include in the computation. + @return The CRC32 CCITT checksum. */ +WS_DLL_PUBLIC uint32_t crc32_ccitt(const uint8_t *buf, unsigned len); + +/** Compute CRC32 CCITT checksum of a buffer of data. If computing the + * checksum over multiple buffers and you want to feed the partial CRC32 + * back in, remember to take the 1's complement of the partial CRC32 first. + @param buf The buffer containing the data. + @param len The number of bytes to include in the computation. + @param seed The seed to use. + @return The CRC32 CCITT checksum (using the given seed). */ +WS_DLL_PUBLIC uint32_t crc32_ccitt_seed(const uint8_t *buf, unsigned len, uint32_t seed); + +/** Compute MPEG-2 CRC32 checksum of a buffer of data. + @param buf The buffer containing the data. + @param len The number of bytes to include in the computation. + @param seed The seed to use. + @return The CRC32 MPEG-2 checksum (using the given seed). */ +WS_DLL_PUBLIC uint32_t crc32_mpeg2_seed(const uint8_t *buf, unsigned len, uint32_t seed); + +/** Computes CRC32 checksum for the given data with the polynom 0x0AA725CF using + * precompiled CRC table + * @param buf a pointer to a buffer of the given length + * @param len the length of the given buffer + * @param seed The seed to use. + * @return the CRC32 checksum for the buffer + */ +WS_DLL_PUBLIC uint32_t crc32_0x0AA725CF_seed(const uint8_t *buf, unsigned len, uint32_t seed); + +/** Computes CRC32 checksum for the given data with the polynom 0x5D6DCB using + * precompiled CRC table + * @param buf a pointer to a buffer of the given length + * @param len the length of the given buffer + * @param seed The seed to use. + * @return the CRC32 checksum for the buffer + */ +WS_DLL_PUBLIC uint32_t crc32_0x5D6DCB_seed(const uint8_t *buf, unsigned len, uint32_t seed); + +WS_DLL_PUBLIC int Dot11DecryptWepDecrypt( + const unsigned char *seed, + const size_t seed_len, + unsigned char *cypher_text, + const size_t data_len); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* crc32.h */ diff --git a/wsutil/crc5.c b/wsutil/crc5.c new file mode 100644 index 00000000..b62f26b1 --- /dev/null +++ b/wsutil/crc5.c @@ -0,0 +1,67 @@ +/* crc5.c + * CRC-5 routine + * + * 2019 Tomasz Mon <desowin@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" + +#include <wsutil/crc5.h> + +static uint8_t crc5_usb_bits(uint32_t v, int vl, uint8_t ival) +{ + /* This function is based on code posted by John Sullivan to Wireshark-dev + * mailing list on Jul 21, 2019. + * + * "One of the properties of LFSRs is that a 1 bit in the input toggles a + * completely predictable set of register bits *at any point in the + * future*. This isn't often useful for most CRC caculations on variable + * sized input, as the cost of working out which those bits are vastly + * outweighs most other methods." + * + * In USB 2.0, the CRC5 is calculated on either 11 or 19 bits inputs, + * and thus this approach is viable. + */ + uint8_t rv = ival; + static const uint8_t bvals[19] = { + 0x1e, 0x15, 0x03, 0x06, 0x0c, 0x18, 0x19, 0x1b, + 0x1f, 0x17, 0x07, 0x0e, 0x1c, 0x11, 0x0b, 0x16, + 0x05, 0x0a, 0x14 + }; + + for (int i = 0; i < vl; i++) { + if (v & (1 << i)) { + rv ^= bvals[19 - vl + i]; + } + } + return rv; +} + +uint8_t crc5_usb_11bit_input(uint16_t input) +{ + return crc5_usb_bits(input, 11, 0x02); +} + +uint8_t crc5_usb_19bit_input(uint32_t input) +{ + return crc5_usb_bits(input, 19, 0x1d); +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/crc5.h b/wsutil/crc5.h new file mode 100644 index 00000000..4f834d53 --- /dev/null +++ b/wsutil/crc5.h @@ -0,0 +1,40 @@ +/** @file + * Declaration of CRC-5 routines and table + * + * 2019 Tomasz Mon <desowin@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef __CRC5_H__ +#define __CRC5_H__ + +#include <wireshark.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** Compute the 5-bit CRC value of a input value matching the CRC-5 + defined in USB 2.0 Specification. This function calculates the CRC + on low 11 bits of the input value. High bits are ignored. + @param input Source data for which the CRC-5 should be calculated. + @return the CRC5 checksum for input value */ +WS_DLL_PUBLIC uint8_t crc5_usb_11bit_input(uint16_t input); + +/** Compute the 5-bit CRC value of a input value matching the CRC-5 + defined in USB 2.0 Specification. This function calculates the CRC + on low 19 bits of the input value. High bits are ignored. + @param input Source data for which the CRC-5 should be calculated. + @return the CRC5 checksum for input value */ +WS_DLL_PUBLIC uint8_t crc5_usb_19bit_input(uint32_t input); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __CRC5_H__ */ diff --git a/wsutil/crc6.c b/wsutil/crc6.c new file mode 100644 index 00000000..f55df00a --- /dev/null +++ b/wsutil/crc6.c @@ -0,0 +1,82 @@ +/* + * crc6.c + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* + Patch by Ross Jacobs <rossbjacobs@gmail.com>: + Fixed CRC6 0x6F lookup table + function per Wireshark bug 14875 +*/ + +#include "config.h" + +#include "crc6.h" + +/** + * Functions and types for CRC checks. + * + * Generated on Wed Jan 2 2019, + * by pycrc v0.9.1, http://www.tty1.net/pycrc/ + * using the configuration: + * Width = 6 + * Poly = 0x6f + * XorIn = 0 + * ReflectIn = False + * XorOut = 0 + * ReflectOut = False + */ +static const uint8_t crc6_table[256] = { + 0x00, 0x2f, 0x31, 0x1e, 0x0d, 0x22, 0x3c, 0x13, 0x1a, 0x35, 0x2b, 0x04, 0x17, 0x38, 0x26, 0x09, + 0x34, 0x1b, 0x05, 0x2a, 0x39, 0x16, 0x08, 0x27, 0x2e, 0x01, 0x1f, 0x30, 0x23, 0x0c, 0x12, 0x3d, + 0x07, 0x28, 0x36, 0x19, 0x0a, 0x25, 0x3b, 0x14, 0x1d, 0x32, 0x2c, 0x03, 0x10, 0x3f, 0x21, 0x0e, + 0x33, 0x1c, 0x02, 0x2d, 0x3e, 0x11, 0x0f, 0x20, 0x29, 0x06, 0x18, 0x37, 0x24, 0x0b, 0x15, 0x3a, + 0x0e, 0x21, 0x3f, 0x10, 0x03, 0x2c, 0x32, 0x1d, 0x14, 0x3b, 0x25, 0x0a, 0x19, 0x36, 0x28, 0x07, + 0x3a, 0x15, 0x0b, 0x24, 0x37, 0x18, 0x06, 0x29, 0x20, 0x0f, 0x11, 0x3e, 0x2d, 0x02, 0x1c, 0x33, + 0x09, 0x26, 0x38, 0x17, 0x04, 0x2b, 0x35, 0x1a, 0x13, 0x3c, 0x22, 0x0d, 0x1e, 0x31, 0x2f, 0x00, + 0x3d, 0x12, 0x0c, 0x23, 0x30, 0x1f, 0x01, 0x2e, 0x27, 0x08, 0x16, 0x39, 0x2a, 0x05, 0x1b, 0x34, + 0x1c, 0x33, 0x2d, 0x02, 0x11, 0x3e, 0x20, 0x0f, 0x06, 0x29, 0x37, 0x18, 0x0b, 0x24, 0x3a, 0x15, + 0x28, 0x07, 0x19, 0x36, 0x25, 0x0a, 0x14, 0x3b, 0x32, 0x1d, 0x03, 0x2c, 0x3f, 0x10, 0x0e, 0x21, + 0x1b, 0x34, 0x2a, 0x05, 0x16, 0x39, 0x27, 0x08, 0x01, 0x2e, 0x30, 0x1f, 0x0c, 0x23, 0x3d, 0x12, + 0x2f, 0x00, 0x1e, 0x31, 0x22, 0x0d, 0x13, 0x3c, 0x35, 0x1a, 0x04, 0x2b, 0x38, 0x17, 0x09, 0x26, + 0x12, 0x3d, 0x23, 0x0c, 0x1f, 0x30, 0x2e, 0x01, 0x08, 0x27, 0x39, 0x16, 0x05, 0x2a, 0x34, 0x1b, + 0x26, 0x09, 0x17, 0x38, 0x2b, 0x04, 0x1a, 0x35, 0x3c, 0x13, 0x0d, 0x22, 0x31, 0x1e, 0x00, 0x2f, + 0x15, 0x3a, 0x24, 0x0b, 0x18, 0x37, 0x29, 0x06, 0x0f, 0x20, 0x3e, 0x11, 0x02, 0x2d, 0x33, 0x1c, + 0x21, 0x0e, 0x10, 0x3f, 0x2c, 0x03, 0x1d, 0x32, 0x3b, 0x14, 0x0a, 0x25, 0x36, 0x19, 0x07, 0x28 +}; + +/** + * CRC6 is used by 3GPP (TS 25.415, TS 25.446) for header CRCs + * Poly: D^6 + D^5 + D^3 + D^2 + D^1 + 1 + * + * TS 25.415 docs: https://www.etsi.org/deliver/etsi_ts/125400_125499/125415/04.06.00_60/ts_125415v040600p.pdf + * TS 25.446 docs: https://www.etsi.org/deliver/etsi_ts/125400_125499/125446/10.01.00_60/ts_125446v100100p.pdf +*/ +uint16_t crc6_0X6F(uint16_t crc, const uint8_t *data, int data_len) +{ + uint8_t tbl_idx; + + while (data_len--) { + tbl_idx = (crc << 2) ^ *data; + crc = crc6_table[tbl_idx] & 0x3f; + data++; + } + return crc & 0x3f; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/crc6.h b/wsutil/crc6.h new file mode 100644 index 00000000..3795c87e --- /dev/null +++ b/wsutil/crc6.h @@ -0,0 +1,17 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __CRC6_H__ +#define __CRC6_H__ + +#include <wireshark.h> + +WS_DLL_PUBLIC uint16_t crc6_0X6F(uint16_t crc6, const uint8_t *data_blk_ptr, int data_blk_size); + +#endif /* __CRC6_H__ */ diff --git a/wsutil/crc7.c b/wsutil/crc7.c new file mode 100644 index 00000000..0d4e9af3 --- /dev/null +++ b/wsutil/crc7.c @@ -0,0 +1,84 @@ +/** + * crc7.c + * + * Functions and types for CRC checks. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * + * Generated on Wed Jul 11 17:24:30 2012, + * by pycrc v0.7.10, http://www.tty1.net/pycrc/ + * using the configuration: + * Width = 7 + * Poly = 0x45 + * XorIn = 0x00 + * ReflectIn = False + * XorOut = 0x00 + * ReflectOut = False + * Algorithm = table-driven + *****************************************************************************/ +#include "config.h" + +#include "crc7.h" /* include the header file generated with pycrc */ + +/** + * Static table used for the table_driven implementation. + *****************************************************************************/ +static const uint8_t crc_table[256] = { + 0x00, 0x8a, 0x9e, 0x14, 0xb6, 0x3c, 0x28, 0xa2, 0xe6, 0x6c, 0x78, 0xf2, 0x50, 0xda, 0xce, 0x44, + 0x46, 0xcc, 0xd8, 0x52, 0xf0, 0x7a, 0x6e, 0xe4, 0xa0, 0x2a, 0x3e, 0xb4, 0x16, 0x9c, 0x88, 0x02, + 0x8c, 0x06, 0x12, 0x98, 0x3a, 0xb0, 0xa4, 0x2e, 0x6a, 0xe0, 0xf4, 0x7e, 0xdc, 0x56, 0x42, 0xc8, + 0xca, 0x40, 0x54, 0xde, 0x7c, 0xf6, 0xe2, 0x68, 0x2c, 0xa6, 0xb2, 0x38, 0x9a, 0x10, 0x04, 0x8e, + 0x92, 0x18, 0x0c, 0x86, 0x24, 0xae, 0xba, 0x30, 0x74, 0xfe, 0xea, 0x60, 0xc2, 0x48, 0x5c, 0xd6, + 0xd4, 0x5e, 0x4a, 0xc0, 0x62, 0xe8, 0xfc, 0x76, 0x32, 0xb8, 0xac, 0x26, 0x84, 0x0e, 0x1a, 0x90, + 0x1e, 0x94, 0x80, 0x0a, 0xa8, 0x22, 0x36, 0xbc, 0xf8, 0x72, 0x66, 0xec, 0x4e, 0xc4, 0xd0, 0x5a, + 0x58, 0xd2, 0xc6, 0x4c, 0xee, 0x64, 0x70, 0xfa, 0xbe, 0x34, 0x20, 0xaa, 0x08, 0x82, 0x96, 0x1c, + 0xae, 0x24, 0x30, 0xba, 0x18, 0x92, 0x86, 0x0c, 0x48, 0xc2, 0xd6, 0x5c, 0xfe, 0x74, 0x60, 0xea, + 0xe8, 0x62, 0x76, 0xfc, 0x5e, 0xd4, 0xc0, 0x4a, 0x0e, 0x84, 0x90, 0x1a, 0xb8, 0x32, 0x26, 0xac, + 0x22, 0xa8, 0xbc, 0x36, 0x94, 0x1e, 0x0a, 0x80, 0xc4, 0x4e, 0x5a, 0xd0, 0x72, 0xf8, 0xec, 0x66, + 0x64, 0xee, 0xfa, 0x70, 0xd2, 0x58, 0x4c, 0xc6, 0x82, 0x08, 0x1c, 0x96, 0x34, 0xbe, 0xaa, 0x20, + 0x3c, 0xb6, 0xa2, 0x28, 0x8a, 0x00, 0x14, 0x9e, 0xda, 0x50, 0x44, 0xce, 0x6c, 0xe6, 0xf2, 0x78, + 0x7a, 0xf0, 0xe4, 0x6e, 0xcc, 0x46, 0x52, 0xd8, 0x9c, 0x16, 0x02, 0x88, 0x2a, 0xa0, 0xb4, 0x3e, + 0xb0, 0x3a, 0x2e, 0xa4, 0x06, 0x8c, 0x98, 0x12, 0x56, 0xdc, 0xc8, 0x42, 0xe0, 0x6a, 0x7e, 0xf4, + 0xf6, 0x7c, 0x68, 0xe2, 0x40, 0xca, 0xde, 0x54, 0x10, 0x9a, 0x8e, 0x04, 0xa6, 0x2c, 0x38, 0xb2 +}; + + + +/** + * Update the crc value with new data. + * + * \param crc The current crc value. + * \param data Pointer to a buffer of \a data_len bytes. + * \param data_len Number of bytes in the \a data buffer. + * \return The updated crc value. + *****************************************************************************/ +uint8_t crc7update(uint8_t crc, const unsigned char *data, int data_len) +{ + unsigned int tbl_idx; + + while (data_len--) { + tbl_idx = ((crc >> 0) ^ *data) & 0xff; + crc = (crc_table[tbl_idx] ^ (crc << (8 - 1))) & (0x7f << 1); + + data++; + } + return crc & (0x7f << 1); +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/crc7.h b/wsutil/crc7.h new file mode 100644 index 00000000..7364d043 --- /dev/null +++ b/wsutil/crc7.h @@ -0,0 +1,76 @@ +/** @file + * + * Functions and types for CRC checks. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Generated on Wed Jul 11 17:24:57 2012, + * by pycrc v0.7.10, http://www.tty1.net/pycrc/ + * using the configuration: + * Width = 7 + * Poly = 0x45 + * XorIn = 0x00 + * ReflectIn = False + * XorOut = 0x00 + * ReflectOut = False + * Algorithm = table-driven + **************************************************************************** + */ +#ifndef __CRC7__H__ +#define __CRC7__H__ + +#include <wireshark.h> + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * The definition of the used algorithm. + *****************************************************************************/ +#define CRC_ALGO_TABLE_DRIVEN 1 + +/** + * Calculate the initial crc value. + * + * \return The initial crc value. + *****************************************************************************/ +static inline uint8_t crc7init(void) +{ + return 0x00 << 1; +} + + +/** + * Update the crc value with new data. + * + * \param crc The current crc value. + * \param data Pointer to a buffer of \a data_len bytes. + * \param data_len Number of bytes in the \a data buffer. + * \return The updated crc value. + *****************************************************************************/ +WS_DLL_PUBLIC uint8_t crc7update(uint8_t crc, const unsigned char *data, int data_len); + + +/** + * Calculate the final crc value. + * + * \param crc The current crc value. + * \return The final crc value. + *****************************************************************************/ +static inline uint8_t crc7finalize(uint8_t crc) +{ + return (crc >> 1) ^ 0x00; +} + + +#ifdef __cplusplus +} /* closing brace for extern "C" */ +#endif + +#endif /* __CRC7__H__ */ diff --git a/wsutil/crc8.c b/wsutil/crc8.c new file mode 100644 index 00000000..ae150267 --- /dev/null +++ b/wsutil/crc8.c @@ -0,0 +1,204 @@ +/* crc8.c + * Implementation CRC-8 declarations and routines + * + * 2011 Roland Knall <rknall@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + + +#include "config.h" + +#include <wsutil/crc8.h> + +/* @brief Precompiled table for CRC8 values for the polynom 0x2F */ +static const uint8_t crc8_precompiled_2F[256] = +{ + 0x00, 0x2F, 0x5E, 0x71, 0xBC, 0x93, 0xE2, 0xCD, + 0x57, 0x78, 0x09, 0x26, 0xEB, 0xC4, 0xB5, 0x9A, + 0xAE, 0x81, 0xF0, 0xDF, 0x12, 0x3D, 0x4C, 0x63, + 0xF9, 0xD6, 0xA7, 0x88, 0x45, 0x6A, 0x1B, 0x34, + 0x73, 0x5C, 0x2D, 0x02, 0xCF, 0xE0, 0x91, 0xBE, + 0x24, 0x0B, 0x7A, 0x55, 0x98, 0xB7, 0xC6, 0xE9, + 0xDD, 0xF2, 0x83, 0xAC, 0x61, 0x4E, 0x3F, 0x10, + 0x8A, 0xA5, 0xD4, 0xFB, 0x36, 0x19, 0x68, 0x47, + 0xE6, 0xC9, 0xB8, 0x97, 0x5A, 0x75, 0x04, 0x2B, + 0xB1, 0x9E, 0xEF, 0xC0, 0x0D, 0x22, 0x53, 0x7C, + 0x48, 0x67, 0x16, 0x39, 0xF4, 0xDB, 0xAA, 0x85, + 0x1F, 0x30, 0x41, 0x6E, 0xA3, 0x8C, 0xFD, 0xD2, + 0x95, 0xBA, 0xCB, 0xE4, 0x29, 0x06, 0x77, 0x58, + 0xC2, 0xED, 0x9C, 0xB3, 0x7E, 0x51, 0x20, 0x0F, + 0x3B, 0x14, 0x65, 0x4A, 0x87, 0xA8, 0xD9, 0xF6, + 0x6C, 0x43, 0x32, 0x1D, 0xD0, 0xFF, 0x8E, 0xA1, + 0xE3, 0xCC, 0xBD, 0x92, 0x5F, 0x70, 0x01, 0x2E, + 0xB4, 0x9B, 0xEA, 0xC5, 0x08, 0x27, 0x56, 0x79, + 0x4D, 0x62, 0x13, 0x3C, 0xF1, 0xDE, 0xAF, 0x80, + 0x1A, 0x35, 0x44, 0x6B, 0xA6, 0x89, 0xF8, 0xD7, + 0x90, 0xBF, 0xCE, 0xE1, 0x2C, 0x03, 0x72, 0x5D, + 0xC7, 0xE8, 0x99, 0xB6, 0x7B, 0x54, 0x25, 0x0A, + 0x3E, 0x11, 0x60, 0x4F, 0x82, 0xAD, 0xDC, 0xF3, + 0x69, 0x46, 0x37, 0x18, 0xD5, 0xFA, 0x8B, 0xA4, + 0x05, 0x2A, 0x5B, 0x74, 0xB9, 0x96, 0xE7, 0xC8, + 0x52, 0x7D, 0x0C, 0x23, 0xEE, 0xC1, 0xB0, 0x9F, + 0xAB, 0x84, 0xF5, 0xDA, 0x17, 0x38, 0x49, 0x66, + 0xFC, 0xD3, 0xA2, 0x8D, 0x40, 0x6F, 0x1E, 0x31, + 0x76, 0x59, 0x28, 0x07, 0xCA, 0xE5, 0x94, 0xBB, + 0x21, 0x0E, 0x7F, 0x50, 0x9D, 0xB2, 0xC3, 0xEC, + 0xD8, 0xF7, 0x86, 0xA9, 0x64, 0x4B, 0x3A, 0x15, + 0x8F, 0xA0, 0xD1, 0xFE, 0x33, 0x1C, 0x6D, 0x42 +}; + +/* @brief Precompiled table for CRC8 values for the polynom 0x37 */ +static const unsigned char crc8_precompiled_37[256] = +{ + 0x00, 0x37, 0x6e, 0x59, 0xdc, 0xeb, 0xb2, 0x85, + 0x8f, 0xb8, 0xe1, 0xd6, 0x53, 0x64, 0x3d, 0x0a, + 0x29, 0x1e, 0x47, 0x70, 0xf5, 0xc2, 0x9b, 0xac, + 0xa6, 0x91, 0xc8, 0xff, 0x7a, 0x4d, 0x14, 0x23, + 0x52, 0x65, 0x3c, 0x0b, 0x8e, 0xb9, 0xe0, 0xd7, + 0xdd, 0xea, 0xb3, 0x84, 0x01, 0x36, 0x6f, 0x58, + 0x7b, 0x4c, 0x15, 0x22, 0xa7, 0x90, 0xc9, 0xfe, + 0xf4, 0xc3, 0x9a, 0xad, 0x28, 0x1f, 0x46, 0x71, + 0xa4, 0x93, 0xca, 0xfd, 0x78, 0x4f, 0x16, 0x21, + 0x2b, 0x1c, 0x45, 0x72, 0xf7, 0xc0, 0x99, 0xae, + 0x8d, 0xba, 0xe3, 0xd4, 0x51, 0x66, 0x3f, 0x08, + 0x02, 0x35, 0x6c, 0x5b, 0xde, 0xe9, 0xb0, 0x87, + 0xf6, 0xc1, 0x98, 0xaf, 0x2a, 0x1d, 0x44, 0x73, + 0x79, 0x4e, 0x17, 0x20, 0xa5, 0x92, 0xcb, 0xfc, + 0xdf, 0xe8, 0xb1, 0x86, 0x03, 0x34, 0x6d, 0x5a, + 0x50, 0x67, 0x3e, 0x09, 0x8c, 0xbb, 0xe2, 0xd5, + 0x7f, 0x48, 0x11, 0x26, 0xa3, 0x94, 0xcd, 0xfa, + 0xf0, 0xc7, 0x9e, 0xa9, 0x2c, 0x1b, 0x42, 0x75, + 0x56, 0x61, 0x38, 0x0f, 0x8a, 0xbd, 0xe4, 0xd3, + 0xd9, 0xee, 0xb7, 0x80, 0x05, 0x32, 0x6b, 0x5c, + 0x2d, 0x1a, 0x43, 0x74, 0xf1, 0xc6, 0x9f, 0xa8, + 0xa2, 0x95, 0xcc, 0xfb, 0x7e, 0x49, 0x10, 0x27, + 0x04, 0x33, 0x6a, 0x5d, 0xd8, 0xef, 0xb6, 0x81, + 0x8b, 0xbc, 0xe5, 0xd2, 0x57, 0x60, 0x39, 0x0e, + 0xdb, 0xec, 0xb5, 0x82, 0x07, 0x30, 0x69, 0x5e, + 0x54, 0x63, 0x3a, 0x0d, 0x88, 0xbf, 0xe6, 0xd1, + 0xf2, 0xc5, 0x9c, 0xab, 0x2e, 0x19, 0x40, 0x77, + 0x7d, 0x4a, 0x13, 0x24, 0xa1, 0x96, 0xcf, 0xf8, + 0x89, 0xbe, 0xe7, 0xd0, 0x55, 0x62, 0x3b, 0x0c, + 0x06, 0x31, 0x68, 0x5f, 0xda, 0xed, 0xb4, 0x83, + 0xa0, 0x97, 0xce, 0xf9, 0x7c, 0x4b, 0x12, 0x25, + 0x2f, 0x18, 0x41, 0x76, 0xf3, 0xc4, 0x9d, 0xaa, +}; + +/* @brief Precompiled table for CRC8 values for the polynom 0x3B */ +static const unsigned char crc8_precompiled_3b[256] = +{ + 0x00, 0x3b, 0x76, 0x4d, 0xec, 0xd7, 0x9a, 0xa1, + 0xe3, 0xd8, 0x95, 0xae, 0x0f, 0x34, 0x79, 0x42, + 0xfd, 0xc6, 0x8b, 0xb0, 0x11, 0x2a, 0x67, 0x5c, + 0x1e, 0x25, 0x68, 0x53, 0xf2, 0xc9, 0x84, 0xbf, + 0xc1, 0xfa, 0xb7, 0x8c, 0x2d, 0x16, 0x5b, 0x60, + 0x22, 0x19, 0x54, 0x6f, 0xce, 0xf5, 0xb8, 0x83, + 0x3c, 0x07, 0x4a, 0x71, 0xd0, 0xeb, 0xa6, 0x9d, + 0xdf, 0xe4, 0xa9, 0x92, 0x33, 0x08, 0x45, 0x7e, + 0xb9, 0x82, 0xcf, 0xf4, 0x55, 0x6e, 0x23, 0x18, + 0x5a, 0x61, 0x2c, 0x17, 0xb6, 0x8d, 0xc0, 0xfb, + 0x44, 0x7f, 0x32, 0x09, 0xa8, 0x93, 0xde, 0xe5, + 0xa7, 0x9c, 0xd1, 0xea, 0x4b, 0x70, 0x3d, 0x06, + 0x78, 0x43, 0x0e, 0x35, 0x94, 0xaf, 0xe2, 0xd9, + 0x9b, 0xa0, 0xed, 0xd6, 0x77, 0x4c, 0x01, 0x3a, + 0x85, 0xbe, 0xf3, 0xc8, 0x69, 0x52, 0x1f, 0x24, + 0x66, 0x5d, 0x10, 0x2b, 0x8a, 0xb1, 0xfc, 0xc7, + 0x49, 0x72, 0x3f, 0x04, 0xa5, 0x9e, 0xd3, 0xe8, + 0xaa, 0x91, 0xdc, 0xe7, 0x46, 0x7d, 0x30, 0x0b, + 0xb4, 0x8f, 0xc2, 0xf9, 0x58, 0x63, 0x2e, 0x15, + 0x57, 0x6c, 0x21, 0x1a, 0xbb, 0x80, 0xcd, 0xf6, + 0x88, 0xb3, 0xfe, 0xc5, 0x64, 0x5f, 0x12, 0x29, + 0x6b, 0x50, 0x1d, 0x26, 0x87, 0xbc, 0xf1, 0xca, + 0x75, 0x4e, 0x03, 0x38, 0x99, 0xa2, 0xef, 0xd4, + 0x96, 0xad, 0xe0, 0xdb, 0x7a, 0x41, 0x0c, 0x37, + 0xf0, 0xcb, 0x86, 0xbd, 0x1c, 0x27, 0x6a, 0x51, + 0x13, 0x28, 0x65, 0x5e, 0xff, 0xc4, 0x89, 0xb2, + 0x0d, 0x36, 0x7b, 0x40, 0xe1, 0xda, 0x97, 0xac, + 0xee, 0xd5, 0x98, 0xa3, 0x02, 0x39, 0x74, 0x4f, + 0x31, 0x0a, 0x47, 0x7c, 0xdd, 0xe6, 0xab, 0x90, + 0xd2, 0xe9, 0xa4, 0x9f, 0x3e, 0x05, 0x48, 0x73, + 0xcc, 0xf7, 0xba, 0x81, 0x20, 0x1b, 0x56, 0x6d, + 0x2f, 0x14, 0x59, 0x62, 0xc3, 0xf8, 0xb5, 0x8e, +}; +/** Calculates a CRC8 checksum for the given buffer with the polynom + * stored in the crc_table + * @param buf a pointer to a buffer of the given length + * @param len the length of the given buffer + * @param seed The seed to use. + * @param crc_table a table storing 256 entries for crc8 checksums + * @return the CRC8 checksum for the buffer + */ +static uint8_t crc8_precompiled(const uint8_t *buf, uint32_t len, uint8_t seed, const uint8_t crc_table[]) +{ + uint8_t crc; + + crc = seed; + while(len-- > 0) + crc = crc_table[(uint8_t)(*buf++) ^ crc]; + + return crc; +} + +/** Calculates a CRC8 checksum for the given buffer with the polynom + * 0x2F using the precompiled CRC table + * @param buf a pointer to a buffer of the given length + * @param len the length of the given buffer + * @param seed The seed to use. + * @return the CRC8 checksum for the buffer + */ +uint8_t crc8_0x2F(const uint8_t *buf, uint32_t len, uint8_t seed) +{ + return crc8_precompiled(buf, len, seed, crc8_precompiled_2F); +} + +/** Calculates a CRC8 checksum for the given buffer with the polynom + * 0x37 using the precompiled CRC table + * @param buf a pointer to a buffer of the given length + * @param len the length of the given buffer + * @param seed The seed to use. + * @return the CRC8 checksum for the buffer + */ +uint8_t crc8_0x37(const uint8_t *buf, uint32_t len, uint8_t seed) +{ + uint8_t crc = seed; + while (len-- > 0) + { + crc = crc8_precompiled_37[(crc ^ *buf++)]; + } + return (crc); +} + +/** Calculates a CRC8 checksum for the given buffer with the polynom + * 0x3B using the precompiled CRC table + * @param buf a pointer to a buffer of the given length + * @param len the length of the given buffer + * @param seed The seed to use. + * @return the CRC8 checksum for the buffer + */ +uint8_t crc8_0x3B(const uint8_t *buf, uint32_t len, uint8_t seed) +{ + uint8_t crc = seed; + while (len-- > 0) + { + crc = crc8_precompiled_3b[(crc ^ *buf++)]; + } + return (crc); +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/crc8.h b/wsutil/crc8.h new file mode 100644 index 00000000..8695640b --- /dev/null +++ b/wsutil/crc8.h @@ -0,0 +1,53 @@ +/** @file + * Declaration of CRC-8 routine and tables + * + * 2011 Roland Knall <rknall@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __CRC8_H__ +#define __CRC8_H__ + +#include <wireshark.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** Calculates a CRC8 checksum for the given buffer with the polynom + * 0x2F using the precompiled CRC table + * @param buf a pointer to a buffer of the given length + * @param len the length of the given buffer + * @param seed The seed to use. + * @return the CRC8 checksum for the buffer + */ +WS_DLL_PUBLIC uint8_t crc8_0x2F(const uint8_t *buf, uint32_t len, uint8_t seed); + +/** Calculates a CRC8 checksum for the given buffer with the polynom + * 0x37 using the precompiled CRC table + * @param buf a pointer to a buffer of the given length + * @param len the length of the given buffer + * @param seed The seed to use. + * @return the CRC8 checksum for the buffer + */ +WS_DLL_PUBLIC uint8_t crc8_0x37(const uint8_t *buf, uint32_t len, uint8_t seed); + +/** Calculates a CRC8 checksum for the given buffer with the polynom + * 0x3B using the precompiled CRC table + * @param buf a pointer to a buffer of the given length + * @param len the length of the given buffer + * @param seed The seed to use. + * @return the CRC8 checksum for the buffer + */ +WS_DLL_PUBLIC uint8_t crc8_0x3B(const uint8_t *buf, uint32_t len, uint8_t seed); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* crc8.h */ diff --git a/wsutil/curve25519.c b/wsutil/curve25519.c new file mode 100644 index 00000000..d40c0cfa --- /dev/null +++ b/wsutil/curve25519.c @@ -0,0 +1,102 @@ +/* curve25519.c + * NaCl/Sodium-compatible API for Curve25519 cryptography. + * + * Copyright (c) 2018, Peter Wu <peter@lekensteyn.nl> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "curve25519.h" +#include <gcrypt.h> + +static inline void +copy_and_reverse(unsigned char *dest, const unsigned char *src, size_t n) +{ + for (size_t i = 0; i < n; i++) { + dest[n - 1 - i] = src[i]; + } +} + +static int +x25519_mpi(unsigned char *q, const unsigned char *n, gcry_mpi_t mpi_p) +{ + unsigned char priv_be[32]; + unsigned char result_be[32]; + size_t result_len = 0; + gcry_mpi_t mpi = NULL; + gcry_ctx_t ctx = NULL; + gcry_mpi_point_t P = NULL; + gcry_mpi_point_t Q = NULL; + int r = -1; + + /* Default to infinity (all zeroes). */ + memset(q, 0, 32); + + /* Keys are in little-endian, but gcry_mpi_scan expects big endian. Convert + * keys and ensure that the result is a valid Curve25519 secret scalar. */ + copy_and_reverse(priv_be, n, 32); + priv_be[0] &= 127; + priv_be[0] |= 64; + priv_be[31] &= 248; + gcry_mpi_scan(&mpi, GCRYMPI_FMT_USG, priv_be, 32, NULL); + + if (gcry_mpi_ec_new(&ctx, NULL, "Curve25519")) { + /* Should not happen, possibly out-of-memory. */ + goto leave; + } + + /* Compute Q = nP */ + Q = gcry_mpi_point_new(0); + P = gcry_mpi_point_set(NULL, mpi_p, NULL, GCRYMPI_CONST_ONE); + gcry_mpi_ec_mul(Q, mpi, P, ctx); + + /* Note: mpi is reused to store the result. */ + if (gcry_mpi_ec_get_affine(mpi, NULL, Q, ctx)) { + /* Infinity. */ + goto leave; + } + + if (gcry_mpi_print(GCRYMPI_FMT_USG, result_be, 32, &result_len, mpi)) { + /* Should not happen, possibly out-of-memory. */ + goto leave; + } + copy_and_reverse(q, result_be, result_len); + r = 0; + +leave: + gcry_mpi_point_release(P); + gcry_mpi_point_release(Q); + gcry_ctx_release(ctx); + gcry_mpi_release(mpi); + /* XXX erase priv_be and result_be */ + return r; +} + +int +crypto_scalarmult_curve25519(unsigned char *q, const unsigned char *n, + const unsigned char *p) +{ + unsigned char p_be[32]; + gcry_mpi_t mpi_p = NULL; + + copy_and_reverse(p_be, p, 32); + /* Clear unused bit. */ + p_be[0] &= 0x7f; + gcry_mpi_scan(&mpi_p, GCRYMPI_FMT_USG, p_be, 32, NULL); + int r = x25519_mpi(q, n, mpi_p); + gcry_mpi_release(mpi_p); + return r; +} + +int +crypto_scalarmult_curve25519_base(unsigned char *q, const unsigned char *n) +{ + gcry_mpi_t mpi_basepoint_x = gcry_mpi_set_ui(NULL, 9); + int r = x25519_mpi(q, n, mpi_basepoint_x); + gcry_mpi_release(mpi_basepoint_x); + return r; +} diff --git a/wsutil/curve25519.h b/wsutil/curve25519.h new file mode 100644 index 00000000..1eda0200 --- /dev/null +++ b/wsutil/curve25519.h @@ -0,0 +1,34 @@ +/** @file + * NaCl/Sodium-compatible API for Curve25519 cryptography. + * + * Copyright (c) 2018, Peter Wu <peter@lekensteyn.nl> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __CURVE25519_H__ +#define __CURVE25519_H__ + +#include <wireshark.h> + +/* + * Computes Q = X25519(n, P). In other words, given the secret key n, the public + * key P, compute the shared secret Q. Each key is 32 bytes long. + * Returns 0 on success or -1 on failure. + */ +WS_DLL_PUBLIC +int crypto_scalarmult_curve25519(unsigned char *q, const unsigned char *n, + const unsigned char *p); + +/* + * Computes the Curve25519 32-byte public key Q from the 32-byte secret key n. + * Returns 0 on success or -1 on failure. + */ +WS_DLL_PUBLIC +int crypto_scalarmult_curve25519_base(unsigned char *q, const unsigned char *n); + +#endif /* __CURVE25519_H__ */ diff --git a/wsutil/dot11decrypt_wep.c b/wsutil/dot11decrypt_wep.c new file mode 100644 index 00000000..37473750 --- /dev/null +++ b/wsutil/dot11decrypt_wep.c @@ -0,0 +1,86 @@ +/* dot11decrypt_wep.c + * + * Copyright (c) 2002-2005 Sam Leffler, Errno Consulting + * Copyright (c) 2006 CACE Technologies, Davis (California) + * All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "config.h" + +/************************************************************************/ +/* File includes */ + +#include "crc32.h" + +/************************************************************************/ +/* Note: copied from net80211/ieee80211_airpdcap_tkip.c */ +#define S_SWAP(a,b) { uint8_t t = S[a]; S[a] = S[b]; S[b] = t; } + +/* Note: copied from FreeBSD source code, RELENG 6, */ +/* sys/net80211/ieee80211_crypto_wep.c, 391 */ +int Dot11DecryptWepDecrypt( + const unsigned char *seed, + const size_t seed_len, + unsigned char *cypher_text, + const size_t data_len) +{ + uint32_t i, j, k, crc; + uint8_t S[256]; + uint8_t icv[4]; + size_t buflen; + + /* Generate key stream (RC4 Pseudo-Random Number Generator) */ + for (i = 0; i < 256; i++) + S[i] = (uint8_t)i; + for (j = i = 0; i < 256; i++) { + j = (j + S[i] + seed[i % seed_len]) & 0xff; + S_SWAP(i, j); + } + + /* Apply RC4 to data and compute CRC32 over decrypted data */ + crc = ~(uint32_t)0; + buflen = data_len; + + for (i = j = k = 0; k < buflen; k++) { + i = (i + 1) & 0xff; + j = (j + S[i]) & 0xff; + S_SWAP(i, j); + *cypher_text ^= S[(S[i] + S[j]) & 0xff]; + crc = crc32_ccitt_table_lookup((crc ^ *cypher_text) & 0xff) ^ (crc >> 8); + cypher_text++; + } + + crc = ~crc; + + /* Encrypt little-endian CRC32 and verify that it matches with the received ICV */ + icv[0] = (uint8_t)crc; + icv[1] = (uint8_t)(crc >> 8); + icv[2] = (uint8_t)(crc >> 16); + icv[3] = (uint8_t)(crc >> 24); + for (k = 0; k < 4; k++) { + i = (i + 1) & 0xff; + j = (j + S[i]) & 0xff; + S_SWAP(i, j); + if ((icv[k] ^ S[(S[i] + S[j]) & 0xff]) != *cypher_text++) { + /* ICV mismatch - drop frame */ + return 1/*DOT11DECRYPT_RET_UNSUCCESS*/; + } + } + + return 0/*DOT11DECRYPT_RET_SUCCESS*/; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 8 + * tab-width: 8 + * indent-tabs-mode: t + * End: + * + * vi: set shiftwidth=8 tabstop=8 noexpandtab: + * :indentSize=8:tabSize=8:noTabs=false: + */ diff --git a/wsutil/eax.c b/wsutil/eax.c new file mode 100644 index 00000000..208cf4e4 --- /dev/null +++ b/wsutil/eax.c @@ -0,0 +1,249 @@ +/* eax.c + * Encryption and decryption routines implementing the EAX' encryption mode + * Copyright 2010, Edward J. Beroset, edward.j.beroset@us.elster.com + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#include "config.h" +#include "eax.h" +#include <stdlib.h> +#include <string.h> +/* Use libgcrypt for cipher libraries. */ +#include <gcrypt.h> + +typedef struct { + uint8_t L[EAX_SIZEOF_KEY]; + uint8_t D[EAX_SIZEOF_KEY]; + uint8_t Q[EAX_SIZEOF_KEY]; +} eax_s; + +static eax_s instance; + +/* these are defined as macros so they'll be easy to redo in assembly if desired */ +#define BLK_CPY(dst, src) { memcpy(dst, src, EAX_SIZEOF_KEY); } +#define BLK_XOR(dst, src) { int z; for (z=0; z < EAX_SIZEOF_KEY; z++) dst[z] ^= src[z]; } +static void Dbl(uint8_t *out, const uint8_t *in); +static void CTR(const uint8_t *ws, uint8_t *pK, uint8_t *pN, uint16_t SizeN); +static void CMAC(uint8_t *pK, uint8_t *ws, const uint8_t *pN, uint16_t SizeN); +static void dCMAC(uint8_t *pK, uint8_t *ws, const uint8_t *pN, uint16_t SizeN, const uint8_t *pC, uint16_t SizeC); +void AesEncrypt(unsigned char msg[EAX_SIZEOF_KEY], unsigned char key[EAX_SIZEOF_KEY]); + +/*! + Decrypts cleartext data using EAX' mode (see ANSI Standard C12.22-2008). + + @param[in] pN pointer to cleartext (canonified form) + @param[in] pK pointer to secret key + @param[in,out] pC pointer to ciphertext + @param[in] SizeN byte length of cleartext (pN) buffer + @param[in] SizeK byte length of secret key (pK) + @param[in] SizeC byte length of ciphertext (pC) buffer + @param[in] pMac four-byte Message Authentication Code + @param[in] Mode EAX_MODE_CLEARTEXT_AUTH or EAX_MODE_CIPHERTEXT_AUTH + @return true if message has been authenticated; false if not + authenticated, invalid Mode or error + */ +bool Eax_Decrypt(uint8_t *pN, uint8_t *pK, uint8_t *pC, + uint32_t SizeN, uint32_t SizeK, uint32_t SizeC, MAC_T *pMac, + uint8_t Mode) +{ + uint8_t wsn[EAX_SIZEOF_KEY]; + uint8_t wsc[EAX_SIZEOF_KEY]; + int i; + + /* key size must match this implementation */ + if (SizeK != EAX_SIZEOF_KEY) + return false; + + /* the key is new */ + for (i = 0; i < EAX_SIZEOF_KEY; i++) + instance.L[i] = 0; + AesEncrypt(instance.L, pK); + Dbl(instance.D, instance.L); + Dbl(instance.Q, instance.D); + /* the key is set up */ + /* first copy the nonce into our working space */ + BLK_CPY(wsn, instance.D); + if (Mode == EAX_MODE_CLEARTEXT_AUTH) { + dCMAC(pK, wsn, pN, SizeN, pC, SizeC); + } else { + CMAC(pK, wsn, pN, SizeN); + } + /* + * In authentication mode the inputs are: pN, pK (and associated sizes), + * the result is the 4 byte MAC. + */ + if (Mode == EAX_MODE_CLEARTEXT_AUTH) + { + return (memcmp(pMac, &wsn[EAX_SIZEOF_KEY-sizeof(*pMac)], sizeof(*pMac)) ? false : true); + + } + + /* + * In cipher mode the inputs are: pN, pK, pP (and associated sizes), + * the results are pC (and its size) along with the 4 byte MAC. + */ + else if (Mode == EAX_MODE_CIPHERTEXT_AUTH) + { + if (SizeC == 0) + return (memcmp(pMac, &wsn[EAX_SIZEOF_KEY-sizeof(*pMac)], sizeof(*pMac)) ? false : true); + { + /* first copy the nonce into our working space */ + BLK_CPY(wsc, instance.Q); + CMAC(pK, wsc, pC, SizeC); + BLK_XOR(wsc, wsn); + } + if (memcmp(pMac, &wsc[EAX_SIZEOF_KEY-sizeof(*pMac)], sizeof(*pMac)) == 0) + { + CTR(wsn, pK, pC, SizeC); + return true; + } + } + return false; +} + +/* set up D or Q from L */ +static void Dbl(uint8_t *out, const uint8_t *in) +{ + int i; + uint8_t carry = 0; + + /* this might be a lot more efficient in assembly language */ + for (i=0; i < EAX_SIZEOF_KEY; i++) + { + out[i] = ( in[i] << 1 ) | carry; + carry = (in[i] & 0x80) ? 1 : 0; + } + if (carry) + out[0] ^= 0x87; +} + +static void CMAC(uint8_t *pK, uint8_t *ws, const uint8_t *pN, uint16_t SizeN) +{ + dCMAC(pK, ws, pN, SizeN, NULL, 0); +} + +static void dCMAC(uint8_t *pK, uint8_t *ws, const uint8_t *pN, uint16_t SizeN, const uint8_t *pC, uint16_t SizeC) +{ + gcry_cipher_hd_t cipher_hd; + uint8_t *work; + uint8_t *ptr; + uint16_t SizeT = SizeN + SizeC; + uint16_t worksize = SizeT; + + /* worksize must be an integral multiple of 16 */ + if (SizeT & 0xf) { + worksize += 0x10 - (worksize & 0xf); + } + work = (uint8_t *)g_malloc(worksize); + if (work == NULL) { + return; + } + memcpy(work, pN, SizeN); + if (pC != NULL) { + memcpy(&work[SizeN], pC, SizeC); + } + /* + * pad the data if necessary, and XOR Q or D, depending on + * whether data was padded or not + */ + if (worksize != SizeT) { + work[SizeT] = 0x80; + for (ptr = &work[SizeT+1]; ptr < &work[worksize]; ptr++) + *ptr = 0; + ptr= &work[worksize-0x10]; + BLK_XOR(ptr, instance.Q); + } else { + ptr = &work[worksize-0x10]; + BLK_XOR(ptr, instance.D); + } + /* open the cipher */ + if (gcry_cipher_open(&cipher_hd, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_CBC,0)){/* GCRY_CIPHER_CBC_MAC)) { */ + g_free(work); + return; + } + if (gcry_cipher_setkey(cipher_hd, pK, EAX_SIZEOF_KEY)) { + g_free(work); + gcry_cipher_close(cipher_hd); + return; + } + if (gcry_cipher_setiv(cipher_hd, ws, EAX_SIZEOF_KEY)) { + g_free(work); + gcry_cipher_close(cipher_hd); + return; + } + if (gcry_cipher_encrypt(cipher_hd, work, worksize, work, worksize)) { + g_free(work); + gcry_cipher_close(cipher_hd); + return; + } + memcpy(ws, ptr, EAX_SIZEOF_KEY); + + g_free(work); + gcry_cipher_close(cipher_hd); + return; +} + +static void CTR(const uint8_t *ws, uint8_t *pK, uint8_t *pN, uint16_t SizeN) +{ + gcry_cipher_hd_t cipher_hd; + uint8_t ctr[EAX_SIZEOF_KEY]; + + BLK_CPY(ctr, ws); + ctr[12] &= 0x7f; + ctr[14] &= 0x7f; + /* open the cipher */ + if (gcry_cipher_open(&cipher_hd, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_CTR, 0)) { + return; + } + if (gcry_cipher_setkey(cipher_hd, pK, EAX_SIZEOF_KEY)) { + gcry_cipher_close(cipher_hd); + return; + } + if (gcry_cipher_setctr(cipher_hd, ctr, EAX_SIZEOF_KEY)) { + gcry_cipher_close(cipher_hd); + return; + } + if (gcry_cipher_encrypt(cipher_hd, pN, SizeN, pN, SizeN)) { + gcry_cipher_close(cipher_hd); + return; + } + gcry_cipher_close(cipher_hd); + return; +} + +void AesEncrypt(unsigned char msg[EAX_SIZEOF_KEY], unsigned char key[EAX_SIZEOF_KEY]) +{ + gcry_cipher_hd_t cipher_hd; + + /* open the cipher */ + if (gcry_cipher_open(&cipher_hd, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_ECB, 0)) { + return; + } + if (gcry_cipher_setkey(cipher_hd, key, EAX_SIZEOF_KEY)) { + gcry_cipher_close(cipher_hd); + return; + } + if (gcry_cipher_encrypt(cipher_hd, msg, EAX_SIZEOF_KEY, msg, EAX_SIZEOF_KEY)) { + gcry_cipher_close(cipher_hd); + return; + } + gcry_cipher_close(cipher_hd); + return; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/eax.h b/wsutil/eax.h new file mode 100644 index 00000000..ccf4dc62 --- /dev/null +++ b/wsutil/eax.h @@ -0,0 +1,46 @@ +/** @file + * Encryption and decryption routines implementing the EAX' encryption mode + * Copyright 2010, Edward J. Beroset, edward.j.beroset@us.elster.com + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef _EAX_H +#define _EAX_H + +#include <wireshark.h> + +typedef struct tagMAC_T +{ + uint8_t Mac[4]; +} MAC_T; + +#define EAX_MODE_CLEARTEXT_AUTH 1 +#define EAX_MODE_CIPHERTEXT_AUTH 2 + +#define EAX_SIZEOF_KEY 16 + +/*! + Decrypts cleartext data using EAX' mode (see ANSI Standard C12.22-2008). + + @param[in] pN pointer to cleartext (canonified form) + @param[in] pK pointer to secret key + @param[in,out] pC pointer to ciphertext + @param[in] SizeN byte length of cleartext (pN) buffer + @param[in] SizeK byte length of secret key (pK) + @param[in] SizeC byte length of ciphertext (pC) buffer + @param[in] pMac four-byte Message Authentication Code + @param[in] Mode EAX_MODE_CLEARTEXT_AUTH or EAX_MODE_CIPHERTEXT_AUTH + @return true if message has been authenticated; false if not + authenticated, invalid Mode or error + */ +WS_DLL_PUBLIC +bool Eax_Decrypt(uint8_t *pN, uint8_t *pK, uint8_t *pC, + uint32_t SizeN, uint32_t SizeK, uint32_t SizeC, MAC_T *pMac, + uint8_t Mode); + +#endif diff --git a/wsutil/epochs.h b/wsutil/epochs.h new file mode 100644 index 00000000..17d62728 --- /dev/null +++ b/wsutil/epochs.h @@ -0,0 +1,67 @@ +/** @file + * + * Definitions of epoch values for various absolute time types. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 2006 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __EPOCHS_H__ +#define __EPOCHS_H__ + +#include <glib.h> + +/* + * Deltas between the epochs for various non-UN*X time stamp formats and + * the January 1, 1970, 00:00:00 (proleptic?) UTC epoch for the UN*X time + * stamp format. + */ + +/* + * 1900-01-01 00:00:00 (proleptic?) UTC. + * Used by a number of time formats. + */ +#define EPOCH_DELTA_1900_01_01_00_00_00_UTC 2208988800U + +/* + * 1904-01-01 00:00:00 (proleptic?) UTC. + * Used in the classic Mac OS, and by formats, such as MPEG-4 Part 14 (MP4), + * which is based on Apple's QuickTime format. + */ +#define EPOCH_DELTA_1904_01_01_00_00_00_UTC 2082844800U + +/* + * 1601-01-01 (proleptic Gregorian) 00:00:00 (proleptic?) UTC. + * The Windows NT epoch, used in a number of places, as it is + * the start of a 400 year Gregorian cycle. + * + * This is + * + * 369*365.25*24*60*60-(3*24*60*60+6*60*60) + * + * or equivalently, + * + * (89*4*365.25+(3*4+1)*365)*24*60*60 + * + * 1970-1601 is 369; 365.25 is the average length of a year in days, + * including leap years. + * + * 369 = 4*92 + 1, so there are 92 groups of 4 consecutive years plus + * one leftover year, 1969, with 365 days. + * + * All but three of the groups of 4 consecutive years average 365.25 days + * per year, as they have one leap year in the group. However, 1700, 1800, + * and 1900 were not leap years, as, while they're all evenly divisible by 4, + * they're also evenly divisible by 100, but not evenly divisible by 400. + * + * So we have 89 groups of 4 consecutive years that average 365.25 + * days per year, 3 groups of 4 consecutive years that average 365 days + * (as they lack a leap year), and one leftover year, 1969, that is + * 365 days long. + */ +#define EPOCH_DELTA_1601_01_01_00_00_00_UTC G_GUINT64_CONSTANT(11644473600) + +#endif /* __EPOCHS_H__ */ diff --git a/wsutil/exported_pdu_tlvs.h b/wsutil/exported_pdu_tlvs.h new file mode 100644 index 00000000..4850be8e --- /dev/null +++ b/wsutil/exported_pdu_tlvs.h @@ -0,0 +1,198 @@ +/** @file + * + * Definitions for exported_pdu TLVs + * Copyright 2013, Anders Broman <anders-broman@ericsson.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef EXPORTED_PDU_TLVS_H +#define EXPORTED_PDU_TLVS_H + +/** + * This is the format of the link-layer header of packets of type + * LINKTYPE_WIRESHARK_UPPER_PDU in pcap and pcapng files. + * + * It is a sequence of TLVs; at least one TLV MUST indicate what protocol is + * in the PDU following the TLVs. + * + * Each TLV contains, in order: + * + * a 2-byte big-endian type field; + * a 2-byte big-endian length field; + * a value, the length of which is indicated by the value of + * the length field (that value does not include the length + * of the type or length fields themselves). + * + * Buffer layout: + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Option Code | Option Length | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * / Option Value / + * / variable length, aligned to 32 bits / + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * / / + * / . . . other options . . . / + * / / + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Option Code == opt_endofopt | Option Length == 0 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * The list of TLVs may begin with a TLV of type EXP_PDU_TAG_OPTIONS_LENGTH; + * its value is a 4-byte integer value, giving the length of all TLVs + * following that TLV (i.e., the length does not include the length of + * the EXP_PDU_TAG_OPTIONS_LENGTH TLV). This tag is deprecated; it is + * not guaranteed to be present, and code reading packets should not + * require it to be present. + * + * The last TLV is of type EXP_PDU_TAG_END_OF_OPT; it has a length + * of 0, and the value is zero-length. + * + * For string values, a string may have zero, one, or more null bytes + * at the end; code that reads the string value must not assume that + * there are, or are not, null bytes at the end. Null bytes are included + * in the length field, but are not part of the string value. + * + * For integral values, the values are in big-endian format. + */ + +/* Tag values + * + * Do NOT add new values to this list without asking + * wireshark-dev[AT]wireshark.org for a value. Otherwise, you run the risk of + * using a value that's already being used for some other purpose, and of + * having tools that read exported_pdu captures not being able to handle + * captures with your new tag value, with no hope that they will ever be + * changed to do so (as that would destroy their ability to read captures + * using that value for that other purpose). + */ +#define EXP_PDU_TAG_END_OF_OPT 0 /**< End-of-options Tag. */ +/* 1 - 9 reserved */ +#define EXP_PDU_TAG_OPTIONS_LENGTH 10 /**< Total length of the options excluding this TLV + * Deprecated - do not use + */ +#define EXP_PDU_TAG_LINKTYPE 11 /**< Deprecated - do not use */ +#define EXP_PDU_TAG_DISSECTOR_NAME 12 /**< The value part should be an ASCII non NULL terminated string + * of the registered dissector used by Wireshark e.g "sip" + * Will be used to call the next dissector. + * NOTE: this is NOT a protocol name; + * a given protocol may have multiple + * dissectors, if, for example, the + * protocol headers depend on the + * protocol being used to transport + * the protocol in question. + */ +#define EXP_PDU_TAG_HEUR_DISSECTOR_NAME 13 /**< The value part should be an ASCII non NULL terminated string + * containing the heuristic dissector unique short name given + * during registration, e.g "sip_udp" + * Will be used to call the next dissector. + */ +#define EXP_PDU_TAG_DISSECTOR_TABLE_NAME 14 /**< The value part should be an ASCII non NULL terminated string + * containing the dissector table name given + * during registration, e.g "gsm_map.v3.arg.opcode" + * Will be used to call the next dissector. + */ + +/* For backwards source compatibility */ +#define EXP_PDU_TAG_PROTO_NAME EXP_PDU_TAG_DISSECTOR_NAME +#define EXP_PDU_TAG_HEUR_PROTO_NAME EXP_PDU_TAG_HEUR_DISSECTOR_NAME + +/* Add protocol type related tags here. + * NOTE Only one protocol type tag may be present in a packet, the first one + * found will be used*/ +/* 13 - 19 reserved */ +#define EXP_PDU_TAG_IPV4_SRC 20 /**< IPv4 source address - 4 bytes */ +#define EXP_PDU_TAG_IPV4_DST 21 /**< IPv4 destination address - 4 bytes */ +#define EXP_PDU_TAG_IPV6_SRC 22 /**< IPv6 source address - 16 bytes */ +#define EXP_PDU_TAG_IPV6_DST 23 /**< IPv6 destination address - 16 bytes */ + +/* Port type values for EXP_PDU_TAG_PORT_TYPE; these do not necessarily + * correspond to port type values inside libwireshark. */ +#define EXP_PDU_PT_NONE 0 +#define EXP_PDU_PT_SCTP 1 +#define EXP_PDU_PT_TCP 2 +#define EXP_PDU_PT_UDP 3 +#define EXP_PDU_PT_DCCP 4 +#define EXP_PDU_PT_IPX 5 +#define EXP_PDU_PT_NCP 6 +#define EXP_PDU_PT_EXCHG 7 +#define EXP_PDU_PT_DDP 8 +#define EXP_PDU_PT_SBCCS 9 +#define EXP_PDU_PT_IDP 10 +#define EXP_PDU_PT_TIPC 11 +#define EXP_PDU_PT_USB 12 +#define EXP_PDU_PT_I2C 13 +#define EXP_PDU_PT_IBQP 14 +#define EXP_PDU_PT_BLUETOOTH 15 +#define EXP_PDU_PT_TDMOP 16 +#define EXP_PDU_PT_IWARP_MPA 17 +#define EXP_PDU_PT_MCTP 18 + +#define EXP_PDU_TAG_PORT_TYPE 24 /**< part type - 4 bytes, EXP_PDU_PT value */ +#define EXP_PDU_TAG_SRC_PORT 25 /**< source port - 4 bytes (even for protocols with 2-byte ports) */ +#define EXP_PDU_TAG_DST_PORT 26 /**< destination port - 4 bytes (even for protocols with 2-byte ports) */ + +#define EXP_PDU_TAG_SS7_OPC 28 +#define EXP_PDU_TAG_SS7_DPC 29 + +#define EXP_PDU_TAG_ORIG_FNO 30 + +#define EXP_PDU_TAG_DVBCI_EVT 31 + +#define EXP_PDU_TAG_DISSECTOR_TABLE_NAME_NUM_VAL 32 /**< value part is the numeric value to be used calling the dissector table + * given with tag EXP_PDU_TAG_DISSECTOR_TABLE_NAME, must follow immediately after the table tag. + */ + +#define EXP_PDU_TAG_COL_PROT_TEXT 33 /**< UTF-8 text string to put in COL_PROTOCOL, one use case is in conjunction with dissector tables where + * COL_PROTOCOL might not be filled in. + */ + + +/**< value part is structure passed into TCP subdissectors. The field + begins with a 2-byte version number; if the version number value is + 1, the value part is in the form: + + version 2 bytes - xport PDU version of structure (for backwards/forwards compatibility) + seq 4 bytes - Sequence number of first byte in the data + nxtseq 4 bytes - Sequence number of first byte after data + lastackseq 4 bytes - Sequence number of last ack + is_reassembled 1 byte - Non-zero if this is reassembled data + flags 2 bytes - TCP flags + urgent_pointer 2 bytes - Urgent pointer value for the current packet + + All multi-byte values are in big-endian format. There is no alignment + padding between values, so seq. nxtseq, and lastackseq are not aligned + on 4-byte boundaries, andflags and urgent_pointer are not aligned on + 2-byte boundaries. +*/ +#define EXP_PDU_TAG_TCP_INFO_DATA 34 + +#define EXP_PDU_TAG_P2P_DIRECTION 35 /**< The packet direction (P2P_DIR_SENT, P2P_DIR_RECV). */ + +#define EXP_PDU_TAG_COL_INFO_TEXT 36 /**< UTF-8 text string to put in COL_INFO, useful when puting meta data into the packet list. + */ + +#define EXP_PDU_TAG_USER_DATA_PDU 37 /**< Raw user data PDU which can be dissected as any protocol. */ + +#define EXP_PDU_TAG_IPV4_LEN 4 +#define EXP_PDU_TAG_IPV6_LEN 16 + +#define EXP_PDU_TAG_PORT_TYPE_LEN 4 +#define EXP_PDU_TAG_PORT_LEN 4 + +#define EXP_PDU_TAG_SS7_OPC_LEN 8 /* 4 bytes PC, 2 bytes standard type, 1 byte NI, 1 byte padding */ +#define EXP_PDU_TAG_SS7_DPC_LEN 8 /* 4 bytes PC, 2 bytes standard type, 1 byte NI, 1 byte padding */ + +#define EXP_PDU_TAG_ORIG_FNO_LEN 4 + +#define EXP_PDU_TAG_DVBCI_EVT_LEN 1 + +#define EXP_PDU_TAG_DISSECTOR_TABLE_NUM_VAL_LEN 4 + +#endif /* EXPORTED_PDU_TLVS_H */ diff --git a/wsutil/feature_list.c b/wsutil/feature_list.c new file mode 100644 index 00000000..f0c30d66 --- /dev/null +++ b/wsutil/feature_list.c @@ -0,0 +1,56 @@ +/* feature_list.c + * Routines for gathering and handling lists of present/absent features + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <stdbool.h> + +#include "config.h" + +#include <wsutil/feature_list.h> + +void +with_feature(feature_list l, const char *fmt, ...) +{ + va_list arg; + GString *msg = g_string_new("+"); + va_start(arg, fmt); + g_string_append_vprintf(msg, fmt, arg); + va_end(arg); + *l = g_list_prepend(*l, g_string_free(msg, false)); +} + +void +without_feature(feature_list l, const char *fmt, ...) +{ + va_list arg; + GString *msg = g_string_new("-"); + va_start(arg, fmt); + g_string_append_vprintf(msg, fmt, arg); + va_end(arg); + *l = g_list_prepend(*l, g_string_free(msg, false)); +} + +static int +feature_sort_alpha(gconstpointer a, gconstpointer b) +{ + return g_ascii_strcasecmp((char *)a + 1, (char *)b + 1); +} + +void +sort_features(feature_list l) +{ + *l = g_list_sort(*l, feature_sort_alpha); +} + +void +free_features(feature_list l) +{ + g_list_free_full(*l, g_free); + *l = NULL; +} diff --git a/wsutil/feature_list.h b/wsutil/feature_list.h new file mode 100644 index 00000000..a9c2d8ec --- /dev/null +++ b/wsutil/feature_list.h @@ -0,0 +1,77 @@ +/** @file + * Declarations of routines for gathering and handling lists of + * present/absent features (usually actually dependencies) + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WSUTIL_FEATURE_LIST_H__ +#define __WSUTIL_FEATURE_LIST_H__ + +#include <glib.h> +#include "ws_symbol_export.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * Handle to a list of features/dependencies. + * Semi-opaque. Functions which gather the list of features + * will be passed one of these to use with + * `with_feature()`/`without_feature()` (below). + */ +typedef GList **feature_list; + +/* + * The format of entries in a feature_list is a char* starting with a + * '+' or '-' character indicating if the feature is respectively + * present or absent, followed by the unchanged feature description. + * This allows the insert order of features to be preserved, + * while still preserving the present/absent status in a simple way. + */ + + +/* + * Pointer to a function which gathers a list of features. + */ +typedef void(*gather_feature_func)(feature_list l); + +/* + * Add an indicator to the given feature_list that the named + * feature is present. + */ +WS_DLL_PUBLIC +void with_feature(feature_list l, const char *fmt, ...) G_GNUC_PRINTF(2,3); + +/* + * Add an indicator to the given feature_list that the named + * feature is absent. + */ +WS_DLL_PUBLIC +void without_feature(feature_list l, const char *fmt, ...) G_GNUC_PRINTF(2,3); + +/* + * Sort the given feature list, alphabetically by feature name. + * (The leading '+' or '-' is not factored into the sort.) + * Currently unused. + */ +WS_DLL_PUBLIC +void sort_features(feature_list l); + +/* + * Free the memory used by the feature list, + * and reset its pointer to NULL. + */ +WS_DLL_PUBLIC +void free_features(feature_list l); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __WSUTIL_FEATURE_LIST_H__ */ diff --git a/wsutil/file_util.c b/wsutil/file_util.c new file mode 100644 index 00000000..4fd55180 --- /dev/null +++ b/wsutil/file_util.c @@ -0,0 +1,700 @@ +/* file_util.c + * + * (Originally part of the Wiretap Library, now part of the Wireshark + * utility library) + * Copyright (c) 1998 by Gilbert Ramirez <gram@alumni.rice.edu> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* + * File wrapper functions to replace the file functions from GLib like + * g_open(). + * + * With MSVC, code using the C support library from one version of MSVC + * cannot use file descriptors or FILE *'s returned from code using + * the C support library from another version of MSVC. + * + * We therefore provide our own versions of the routines to open files, + * so that they're built to use the same C support library as our code + * that reads them. + * + * (If both were built to use the Universal CRT: + * + * http://blogs.msdn.com/b/vcblog/archive/2015/03/03/introducing-the-universal-crt.aspx + * + * this would not be a problem.) + * + * DO NOT USE THESE FUNCTIONS DIRECTLY, USE ws_open() AND ALIKE FUNCTIONS + * FROM file_util.h INSTEAD!!! + * + * The following code is stripped down code copied from the GLib file + * glib/gstdio.h - stripped down because this is used only on Windows + * and we use only wide char functions. + * + * In addition, we have our own ws_stdio_stat64(), which uses + * _wstati64(), so that we can get file sizes for files > 4 GB in size. + * + * XXX - is there any reason why we supply our own versions of routines + * that *don't* return file descriptors, other than ws_stdio_stat64()? + * Is there an issue with UTF-16 support in _wmkdir() with some versions + * of the C runtime, so that if GLib is built to use that version, it + * won't handle UTF-16 paths? + */ + +#ifndef _WIN32 +#error "This is only for Windows" +#endif + +#include "config.h" + +#include <glib.h> + +#include <windows.h> +#include <winsock2.h> +#include <errno.h> +#include <wchar.h> +#include <tchar.h> +#include <stdlib.h> + +#include "file_util.h" +#include "ws_attributes.h" + +static char *program_path = NULL; +static char *system_path = NULL; +static char *npcap_path = NULL; + +/** + * g_open: + * @filename: a pathname in the GLib file name encoding (UTF-8 on Windows) + * @flags: as in open() + * @mode: as in open() + * + * A wrapper for the POSIX open() function. The open() function is + * used to convert a pathname into a file descriptor. Note that on + * POSIX systems file descriptors are implemented by the operating + * system. On Windows, it's the C library that implements open() and + * file descriptors. The actual Windows API for opening files is + * something different. + * + * See the C library manual for more details about open(). + * + * Returns: a new file descriptor, or -1 if an error occurred. The + * return value can be used exactly like the return value from open(). + * + * Since: 2.6 + */ +int +ws_stdio_open (const char *filename, int flags, int mode) +{ + wchar_t *wfilename = g_utf8_to_utf16 (filename, -1, NULL, NULL, NULL); + int retval; + int save_errno; + + if (wfilename == NULL) + { + errno = EINVAL; + return -1; + } + + retval = _wopen (wfilename, flags, mode); + save_errno = errno; + + g_free (wfilename); + + errno = save_errno; + return retval; +} + + +/** + * g_rename: + * @oldfilename: a pathname in the GLib file name encoding (UTF-8 on Windows) + * @newfilename: a pathname in the GLib file name encoding + * + * A wrapper for the POSIX rename() function. The rename() function + * renames a file, moving it between directories if required. + * + * See your C library manual for more details about how rename() works + * on your system. Note in particular that on Win9x it is not possible + * to rename a file if a file with the new name already exists. Also + * it is not possible in general on Windows to rename an open file. + * + * Returns: 0 if the renaming succeeded, -1 if an error occurred + * + * Since: 2.6 + */ +int +ws_stdio_rename (const char *oldfilename, const char *newfilename) +{ + wchar_t *woldfilename = g_utf8_to_utf16 (oldfilename, -1, NULL, NULL, NULL); + wchar_t *wnewfilename; + int retval; + int save_errno = 0; + + if (woldfilename == NULL) + { + errno = EINVAL; + return -1; + } + + wnewfilename = g_utf8_to_utf16 (newfilename, -1, NULL, NULL, NULL); + + if (wnewfilename == NULL) + { + g_free (woldfilename); + errno = EINVAL; + return -1; + } + + if (MoveFileExW (woldfilename, wnewfilename, MOVEFILE_REPLACE_EXISTING)) + retval = 0; + else + { + retval = -1; + switch (GetLastError ()) + { +#define CASE(a,b) case ERROR_##a: save_errno = b; break + CASE (FILE_NOT_FOUND, ENOENT); + CASE (PATH_NOT_FOUND, ENOENT); + CASE (ACCESS_DENIED, EACCES); + CASE (NOT_SAME_DEVICE, EXDEV); + CASE (LOCK_VIOLATION, EACCES); + CASE (SHARING_VIOLATION, EACCES); + CASE (FILE_EXISTS, EEXIST); + CASE (ALREADY_EXISTS, EEXIST); +#undef CASE + default: save_errno = EIO; + } + } + + g_free (woldfilename); + g_free (wnewfilename); + + errno = save_errno; + return retval; +} + +/** + * g_mkdir: + * @filename: a pathname in the GLib file name encoding (UTF-8 on Windows) + * @mode: permissions to use for the newly created directory + * + * A wrapper for the POSIX mkdir() function. The mkdir() function + * attempts to create a directory with the given name and permissions. + * + * See the C library manual for more details about mkdir(). + * + * Returns: 0 if the directory was successfully created, -1 if an error + * occurred + * + * Since: 2.6 + */ +int +ws_stdio_mkdir (const char *filename, int mode _U_) +{ + wchar_t *wfilename = g_utf8_to_utf16 (filename, -1, NULL, NULL, NULL); + int retval; + int save_errno; + + if (wfilename == NULL) + { + errno = EINVAL; + return -1; + } + + retval = _wmkdir (wfilename); + save_errno = errno; + + g_free (wfilename); + + errno = save_errno; + return retval; +} + +/** + * g_stat: + * @filename: a pathname in the GLib file name encoding (UTF-8 on Windows) + * @buf: a pointer to a <structname>stat</structname> struct, which + * will be filled with the file information + * + * A wrapper for the POSIX stat() function. The stat() function + * returns information about a file. + * + * See the C library manual for more details about stat(). + * + * Returns: 0 if the information was successfully retrieved, -1 if an error + * occurred + * + * Since: 2.6 + */ +int +ws_stdio_stat64 (const char *filename, ws_statb64 *buf) +{ + wchar_t *wfilename = g_utf8_to_utf16 (filename, -1, NULL, NULL, NULL); + int retval; + int save_errno; + size_t len; + + if (wfilename == NULL) + { + errno = EINVAL; + return -1; + } + + len = wcslen (wfilename); + while (len > 0 && G_IS_DIR_SEPARATOR (wfilename[len-1])) + len--; + if (len > 0 && + (!g_path_is_absolute (filename) || len > (size_t) (g_path_skip_root (filename) - filename))) + wfilename[len] = '\0'; + + retval = _wstati64 (wfilename, buf); + save_errno = errno; + + g_free (wfilename); + + errno = save_errno; + return retval; +} +/** + * g_unlink: + * @filename: a pathname in the GLib file name encoding (UTF-8 on Windows) + * + * A wrapper for the POSIX unlink() function. The unlink() function + * deletes a name from the filesystem. If this was the last link to the + * file and no processes have it opened, the diskspace occupied by the + * file is freed. + * + * See your C library manual for more details about unlink(). Note + * that on Windows, it is in general not possible to delete files that + * are open to some process, or mapped into memory. + * + * Returns: 0 if the name was successfully deleted, -1 if an error + * occurred + * + * Since: 2.6 + */ + +int +ws_stdio_unlink (const char *filename) +{ + wchar_t *wfilename = g_utf8_to_utf16 (filename, -1, NULL, NULL, NULL); + int retval; + int save_errno; + + if (wfilename == NULL) + { + errno = EINVAL; + return -1; + } + + retval = _wunlink (wfilename); + save_errno = errno; + + g_free (wfilename); + + errno = save_errno; + return retval; +} + +/** + * g_remove: + * @filename: a pathname in the GLib file name encoding (UTF-8 on Windows) + * + * A wrapper for the POSIX remove() function. The remove() function + * deletes a name from the filesystem. + * + * See your C library manual for more details about how remove() works + * on your system. On Unix, remove() removes also directories, as it + * calls unlink() for files and rmdir() for directories. On Windows, + * although remove() in the C library only works for files, this + * function tries first remove() and then if that fails rmdir(), and + * thus works for both files and directories. Note however, that on + * Windows, it is in general not possible to remove a file that is + * open to some process, or mapped into memory. + * + * If this function fails on Windows you can't infer too much from the + * errno value. rmdir() is tried regardless of what caused remove() to + * fail. Any errno value set by remove() will be overwritten by that + * set by rmdir(). + * + * Returns: 0 if the file was successfully removed, -1 if an error + * occurred + * + * Since: 2.6 + */ +int +ws_stdio_remove (const char *filename) +{ + wchar_t *wfilename = g_utf8_to_utf16 (filename, -1, NULL, NULL, NULL); + int retval; + int save_errno; + + if (wfilename == NULL) + { + errno = EINVAL; + return -1; + } + + retval = _wremove (wfilename); + if (retval == -1) + retval = _wrmdir (wfilename); + save_errno = errno; + + g_free (wfilename); + + errno = save_errno; + return retval; +} + +/** + * g_fopen: + * @filename: a pathname in the GLib file name encoding (UTF-8 on Windows) + * @mode: a string describing the mode in which the file should be + * opened + * + * A wrapper for the POSIX fopen() function. The fopen() function opens + * a file and associates a new stream with it. + * + * See the C library manual for more details about fopen(). + * + * Returns: A <type>FILE</type> pointer if the file was successfully + * opened, or %NULL if an error occurred + * + * Since: 2.6 + */ +FILE * +ws_stdio_fopen (const char *filename, const char *mode) +{ + wchar_t *wfilename = g_utf8_to_utf16 (filename, -1, NULL, NULL, NULL); + wchar_t *wmode; + FILE *retval; + int save_errno; + + if (wfilename == NULL) + { + errno = EINVAL; + return NULL; + } + + wmode = g_utf8_to_utf16 (mode, -1, NULL, NULL, NULL); + + if (wmode == NULL) + { + g_free (wfilename); + errno = EINVAL; + return NULL; + } + + retval = _wfopen (wfilename, wmode); + save_errno = errno; + + g_free (wfilename); + g_free (wmode); + + errno = save_errno; + return retval; +} + +/** + * g_freopen: + * @filename: a pathname in the GLib file name encoding (UTF-8 on Windows) + * @mode: a string describing the mode in which the file should be + * opened + * @stream: an existing stream which will be reused, or %NULL + * + * A wrapper for the POSIX freopen() function. The freopen() function + * opens a file and associates it with an existing stream. + * + * See the C library manual for more details about freopen(). + * + * Returns: A <type>FILE</type> pointer if the file was successfully + * opened, or %NULL if an error occurred. + * + * Since: 2.6 + */ +FILE * +ws_stdio_freopen (const char *filename, const char *mode, FILE *stream) +{ + wchar_t *wfilename = g_utf8_to_utf16 (filename, -1, NULL, NULL, NULL); + wchar_t *wmode; + FILE *retval; + int save_errno; + + if (wfilename == NULL) + { + errno = EINVAL; + return NULL; + } + + wmode = g_utf8_to_utf16 (mode, -1, NULL, NULL, NULL); + + if (wmode == NULL) + { + g_free (wfilename); + errno = EINVAL; + return NULL; + } + + retval = _wfreopen (wfilename, wmode, stream); + save_errno = errno; + + g_free (wfilename); + g_free (wmode); + + errno = save_errno; + return retval; +} + + +/* DLL loading */ +static bool +init_dll_load_paths(void) +{ + TCHAR path_w[MAX_PATH]; + + if (program_path && system_path && npcap_path) + return true; + + /* XXX - Duplicate code in filesystem.c:configuration_init */ + if (GetModuleFileName(NULL, path_w, MAX_PATH) == 0 || GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + return false; + } + + if (!program_path) { + char *app_path; + app_path = g_utf16_to_utf8(path_w, -1, NULL, NULL, NULL); + /* We could use PathRemoveFileSpec here but we'd have to link to Shlwapi.dll */ + program_path = g_path_get_dirname(app_path); + g_free(app_path); + } + + if (GetSystemDirectory(path_w, MAX_PATH) == 0) { + return false; + } + + if (!system_path) { + system_path = g_utf16_to_utf8(path_w, -1, NULL, NULL, NULL); + } + + _tcscat_s(path_w, MAX_PATH, _T("\\Npcap")); + + if (!npcap_path) { + npcap_path = g_utf16_to_utf8(path_w, -1, NULL, NULL, NULL); + } + + if (program_path && system_path && npcap_path) + return true; + + return false; +} + +bool +ws_init_dll_search_path(void) +{ + bool dll_dir_set = false; + wchar_t *program_path_w; + + /* Remove the current directory from the default DLL search path. */ + SetDllDirectory(_T("")); + + if (init_dll_load_paths()) { + /* Ensure that extcap executables can find wsutil, etc. */ + program_path_w = g_utf8_to_utf16(program_path, -1, NULL, NULL, NULL); + dll_dir_set = SetDllDirectory(program_path_w); + g_free(program_path_w); + } + + return dll_dir_set; +} + +/* + * Internally g_module_open uses LoadLibrary on Windows and returns an + * HMODULE cast to a GModule *. However there's no guarantee that this + * will always be the case, so we call LoadLibrary and g_module_open + * separately. + */ + +void * +ws_load_library(const char *library_name) +{ + char *full_path; + wchar_t *full_path_w; + HMODULE dll_h; + + if (!init_dll_load_paths() || !library_name) + return NULL; + + /* First try the program directory */ + full_path = g_strconcat(program_path, G_DIR_SEPARATOR_S, library_name, NULL); + full_path_w = g_utf8_to_utf16(full_path, -1, NULL, NULL, NULL); + + if (full_path && full_path_w) { + dll_h = LoadLibraryW(full_path_w); + if (dll_h) { + g_free(full_path); + g_free(full_path_w); + return dll_h; + } + } + + /* Next try the system directory */ + full_path = g_strconcat(system_path, G_DIR_SEPARATOR_S, library_name, NULL); + full_path_w = g_utf8_to_utf16(full_path, -1, NULL, NULL, NULL); + + if (full_path && full_path_w) { + dll_h = LoadLibraryW(full_path_w); + if (dll_h) { + g_free(full_path); + g_free(full_path_w); + return dll_h; + } + } + + return NULL; +} + +static GModule * +load_npcap_module(const char *full_path, GModuleFlags flags) +{ + /* + * Npcap's wpcap.dll requires packet.dll from the same directory. Either + * SetDllDirectory or SetCurrentDirectory could make this work, but it + * interferes with other uses of these settings. LoadLibraryEx is ideal as + * it can be configured to put the directory containing the DLL to the + * search path. Unfortunately g_module_open uses LoadLibrary internally, so + * as a workaround manually load the Npcap libraries first and then use + * g_module_open to obtain a GModule for the loaded library. + */ + + wchar_t *wpath = g_utf8_to_utf16(full_path, -1, NULL, NULL, NULL); + HMODULE module = LoadLibraryEx(wpath, NULL, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR); + g_free(wpath); + if (!module) { + return NULL; + } + GModule *mod = g_module_open(full_path, flags); + FreeLibrary(module); + return mod; +} + +GModule * +load_wpcap_module(void) +{ + char *module_name = "wpcap.dll"; + char *full_path; + GModule *mod; + GModuleFlags flags = 0; + + if (!init_dll_load_paths()) + return NULL; + + /* First try the program directory */ + full_path = g_strconcat(program_path, G_DIR_SEPARATOR_S, module_name, NULL); + + if (full_path) { + mod = g_module_open(full_path, flags); + g_free(full_path); + if (mod) { + return mod; + } + } + + /* Next try the Npcap directory */ + full_path = g_strconcat(npcap_path, G_DIR_SEPARATOR_S, module_name, NULL); + + if (full_path) { + mod = load_npcap_module(full_path, flags); + g_free(full_path); + if (mod) { + return mod; + } + } + + /* At last try the system directory */ + full_path = g_strconcat(system_path, G_DIR_SEPARATOR_S, module_name, NULL); + + if (full_path) { + mod = g_module_open(full_path, flags); + g_free(full_path); + if (mod) { + return mod; + } + } + + return NULL; +} + +/** Create or open a "Wireshark is running" mutex. + */ +#define WIRESHARK_IS_RUNNING_UUID "9CA78EEA-EA4D-4490-9240-FC01FCEF464B" + +static HANDLE local_running_mutex = NULL; +static HANDLE global_running_mutex = NULL; + +void create_app_running_mutex(void) { + SECURITY_DESCRIPTOR sec_descriptor; + SECURITY_ATTRIBUTES sec_attributes; + SECURITY_ATTRIBUTES *sa; + memset(&sec_descriptor, 0, sizeof(SECURITY_DESCRIPTOR)); + if (!InitializeSecurityDescriptor(&sec_descriptor, SECURITY_DESCRIPTOR_REVISION) || + !SetSecurityDescriptorDacl(&sec_descriptor, true, NULL, false)) { + /* + * We couldn't set up the security descriptor, so use the default + * security attributes when creating the mutexes. + */ + sa = NULL; + } else { + /* + * We could set it up, so set up some attributes that refer + * to it. + */ + memset(&sec_attributes, 0, sizeof(SECURITY_ATTRIBUTES)); + sec_attributes.nLength = sizeof(SECURITY_ATTRIBUTES); + sec_attributes.lpSecurityDescriptor = &sec_descriptor; + sec_attributes.bInheritHandle = true; + sa = &sec_attributes; + } + local_running_mutex = CreateMutex(sa, false, _T("Wireshark-is-running-{") _T(WIRESHARK_IS_RUNNING_UUID) _T("}")); + global_running_mutex = CreateMutex(sa, false, _T("Global\\Wireshark-is-running-{") _T(WIRESHARK_IS_RUNNING_UUID) _T("}")); +} + +void close_app_running_mutex(void) { + if (local_running_mutex) { + CloseHandle(local_running_mutex); + local_running_mutex = NULL; + } + if (global_running_mutex) { + CloseHandle(global_running_mutex); + global_running_mutex = NULL; + } +} + +int ws_close_if_possible(int fd) { + fd_set rfds; + struct timeval tv = { 0, 1 }; + int retval; + + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + + retval = select(1, &rfds, NULL, NULL, &tv); + if (retval > -1) + return _close(fd); + + return -1; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/file_util.h b/wsutil/file_util.h new file mode 100644 index 00000000..768182e8 --- /dev/null +++ b/wsutil/file_util.h @@ -0,0 +1,241 @@ +/** @file + * File utility definitions + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __FILE_UTIL_H__ +#define __FILE_UTIL_H__ + +#include <stdbool.h> + +#include "ws_symbol_export.h" + +#ifdef _WIN32 +#include <io.h> /* for _read(), _write(), etc. */ +#include <gmodule.h> +#endif + +#include <fcntl.h> /* for open() */ + +#ifdef HAVE_UNISTD_H +#include <unistd.h> /* for read(), write(), close(), etc. */ +#endif + +#include <sys/stat.h> /* for stat() and struct stat */ + +#include <stdio.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* We set a larger IO Buffer size for the capture files */ +#define IO_BUF_SIZE (64 * 1024) + +/* + * Visual C++ on Win32 systems doesn't define these. (Old UNIX systems don't + * define them either.) + * + * Visual C++ on Win32 systems doesn't define S_IFIFO, it defines _S_IFIFO. + */ +#ifndef S_ISREG +#define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) +#endif +#ifndef S_IFIFO +#define S_IFIFO _S_IFIFO +#endif +#ifndef S_ISFIFO +#define S_ISFIFO(mode) (((mode) & S_IFMT) == S_IFIFO) +#endif +#ifndef S_ISDIR +#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#endif + +#ifdef _WIN32 + +/* + * The structure to pass to ws_stat64() and ws_fstat64(). + */ +#define ws_statb64 struct _stat64 + +/* Win32 (and Win64): we use UTF-8 for filenames and pathnames throughout + * the code, so file functions must convert filenames and pathnames from + * UTF-8 to UTF-16 as we use NT Unicode (Win9x - now unsupported - used + * locale-based encoding here). Microsoft's UN*X-style wrappers don't + * do that - they expect locale-based encodings - so we need our own + * wrappers. (We don't use the wrappers from GLib as that would, at + * least for the wrappers that return file descriptors or take them + * as arguments, require that we use the version of the C runtime with + * which the GLib binaries were built, and we can't guarantee to do that.) + * + * Note also that ws_stdio_rename() uses MoveFileEx() with + * MOVEFILE_REPLACE_EXISTING, so that it acts like UN*X rename(), + * removing the target if necessary. + */ + +WS_DLL_PUBLIC int ws_stdio_open (const char *filename, int flags, int mode); +WS_DLL_PUBLIC int ws_stdio_rename (const char *oldfilename, const char *newfilename); +WS_DLL_PUBLIC int ws_stdio_mkdir (const char *filename, int mode); +WS_DLL_PUBLIC int ws_stdio_stat64 (const char *filename, ws_statb64 *buf); +WS_DLL_PUBLIC int ws_stdio_unlink (const char *filename); +WS_DLL_PUBLIC int ws_stdio_remove (const char *filename); + +WS_DLL_PUBLIC FILE * ws_stdio_fopen (const char *filename, const char *mode); +WS_DLL_PUBLIC FILE * ws_stdio_freopen (const char *filename, const char *mode, FILE *stream); + +#define ws_open ws_stdio_open +#define ws_rename ws_stdio_rename +#define ws_mkdir ws_stdio_mkdir +#define ws_stat64 ws_stdio_stat64 +#define ws_unlink ws_stdio_unlink +#define ws_remove ws_stdio_remove +#define ws_fopen ws_stdio_fopen +#define ws_freopen ws_stdio_freopen + +/* + * These routines don't take pathnames, so they don't require + * pathname-converting wrappers on Windows. + */ + +typedef unsigned int ws_file_size_t; +typedef signed int ws_file_ssize_t; + +#define ws_read _read +#define ws_write _write +#define ws_close _close +#define ws_dup _dup +#define ws_fseek64 _fseeki64 /* use _fseeki64 for 64-bit offset support */ +#define ws_fstat64 _fstati64 /* use _fstati64 for 64-bit size support */ +#define ws_ftell64 _ftelli64 /* use _ftelli64 for 64-bit offset support */ +#define ws_lseek64 _lseeki64 /* use _lseeki64 for 64-bit offset support */ +#define ws_fdopen _fdopen +#define ws_fileno _fileno +#define ws_isatty _isatty +#define ws_getc_unlocked _fgetc_nolock + +/* + * Other CRT functions. getpid probably belongs in sys_util.h or proc_util.h + * but neither yet exist. + */ +#define ws_getpid _getpid +#define ws_umask _umask + +/* DLL loading */ + +/** Try to remove the current directory from the DLL search path. + * SetDllDirectory is tried, then SetCurrentDirectory(program_dir) + * + * @return true if we were able to call SetDllDirectory, false otherwise. + */ +WS_DLL_PUBLIC +bool ws_init_dll_search_path(void); + +/** Load a DLL using LoadLibrary. + * Only the system and program directories are searched. + * + * @param library_name The name of the DLL. + * @return A handle to the DLL if found, NULL on failure. + */ + +WS_DLL_PUBLIC +void *ws_load_library(const char *library_name); + +/** Load wpcap.dll using g_module_open. + * Only the system and program directories are searched. + * + * @return A handle to the DLL if found, NULL on failure. + */ +WS_DLL_PUBLIC +GModule *load_wpcap_module(void); + +/** Create or open a "Wireshark is running" mutex. + * Create or open a mutex which signals that Wireshark or its associated + * executables is running. Used by the installer to test for a running application. + */ +WS_DLL_PUBLIC void create_app_running_mutex(void); + +/** Close our "Wireshark is running" mutex. + */ +WS_DLL_PUBLIC void close_app_running_mutex(void); + +/** Close a file descriptor if it is not open + */ +WS_DLL_PUBLIC int ws_close_if_possible(int fd); + +#else /* _WIN32 */ + +/* + * The structure to pass to ws_fstat64(). + */ +#define ws_statb64 struct stat + +/* Not Windows, presumed to be UN*X-compatible */ +#define ws_open open +#define ws_rename rename +#define ws_mkdir(dir,mode) mkdir(dir,mode) +#define ws_stat64 stat +#define ws_unlink unlink +#define ws_remove remove +#define ws_fopen fopen +#define ws_freopen freopen + +typedef size_t ws_file_size_t; +typedef ssize_t ws_file_ssize_t; + +#define ws_read read +#define ws_write write +#ifdef __cplusplus +/* + * Just in case this is used in a class with a close method or member. + */ +#define ws_close ::close +#else +#define ws_close close +#endif + +#define ws_close_if_possible ws_close + +#define ws_dup dup +#ifdef HAVE_FSEEKO +#define ws_fseek64 fseeko /* AC_SYS_LARGEFILE should make off_t 64-bit */ +#define ws_ftell64 ftello /* AC_SYS_LARGEFILE should make off_t 64-bit */ +#else +#define ws_fseek64(fh,offset,whence) fseek(fh,(long)(offset),whence) +#define ws_ftell64 ftell +#endif +#define ws_fstat64 fstat /* AC_SYS_LARGEFILE should make off_t 64-bit */ +#define ws_lseek64 lseek /* AC_SYS_LARGEFILE should make off_t 64-bit */ +#define ws_fdopen fdopen +#define ws_fileno fileno +#define ws_isatty isatty +#define ws_getc_unlocked getc_unlocked +#define O_BINARY 0 /* Win32 needs the O_BINARY flag for open() */ + +/* Other CRT functions */ +#define ws_getpid getpid +#define ws_umask umask + +#endif /* _WIN32 */ + +/* directory handling */ +#define WS_DIR GDir +#define WS_DIRENT const char +#define ws_dir_open g_dir_open +#define ws_dir_read_name g_dir_read_name +#define ws_dir_get_name(dirent) dirent +#define ws_dir_rewind g_dir_rewind +#define ws_dir_close g_dir_close + +/* XXX - remove include "sys/stat.h" from files that include this header */ +/* XXX - update docs (e.g. README.developer) */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __FILE_UTIL_H__ */ diff --git a/wsutil/filesystem.c b/wsutil/filesystem.c new file mode 100644 index 00000000..065cdd3c --- /dev/null +++ b/wsutil/filesystem.c @@ -0,0 +1,2722 @@ +/* filesystem.c + * Filesystem utility routines + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" +#include "filesystem.h" + +#define WS_LOG_DOMAIN LOG_DOMAIN_WSUTIL + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#ifdef _WIN32 +#include <windows.h> +#include <tchar.h> +#include <shlobj.h> +#include <wsutil/unicode-utils.h> +#else /* _WIN32 */ +#ifdef ENABLE_APPLICATION_BUNDLE +#include <mach-o/dyld.h> +#endif +#ifdef __linux__ +#include <sys/utsname.h> +#endif +#ifdef __FreeBSD__ +#include <sys/types.h> +#include <sys/sysctl.h> +#endif +#ifdef HAVE_DLGET +#include <dlfcn.h> +#endif +#include <pwd.h> +#endif /* _WIN32 */ + +#include <wsutil/report_message.h> +#include <wsutil/privileges.h> +#include <wsutil/file_util.h> +#include <wsutil/utf8_entities.h> + +#include <wiretap/wtap.h> /* for WTAP_ERR_SHORT_WRITE */ + +#include "path_config.h" + +#define PROFILES_DIR "profiles" +#define PLUGINS_DIR_NAME "plugins" +#define EXTCAP_DIR_NAME "extcap" +#define PROFILES_INFO_NAME "profile_files.txt" + +#define _S G_DIR_SEPARATOR_S + +/* + * Application configuration namespace. Used to construct configuration + * paths and environment variables. + * XXX We might want to use the term "application flavor" instead, with + * "packet" and "log" flavors. + */ +enum configuration_namespace_e { + CONFIGURATION_NAMESPACE_UNINITIALIZED, + CONFIGURATION_NAMESPACE_WIRESHARK, + CONFIGURATION_NAMESPACE_LOGRAY +}; +enum configuration_namespace_e configuration_namespace = CONFIGURATION_NAMESPACE_UNINITIALIZED; + +#define CONFIGURATION_NAMESPACE_PROPER (configuration_namespace == CONFIGURATION_NAMESPACE_WIRESHARK ? "Wireshark" : "Logray") +#define CONFIGURATION_NAMESPACE_LOWER (configuration_namespace == CONFIGURATION_NAMESPACE_WIRESHARK ? "wireshark" : "logray") +#define CONFIGURATION_ENVIRONMENT_VARIABLE(suffix) (configuration_namespace == CONFIGURATION_NAMESPACE_WIRESHARK ? "WIRESHARK_" suffix : "LOGRAY_" suffix) + +char *persconffile_dir = NULL; +char *datafile_dir = NULL; +char *persdatafile_dir = NULL; +char *persconfprofile = NULL; +char *doc_dir = NULL; + +/* Directory from which the executable came. */ +static char *progfile_dir = NULL; +static char *install_prefix = NULL; + +static bool do_store_persconffiles = false; +static GHashTable *profile_files = NULL; + +/* + * Given a pathname, return a pointer to the last pathname separator + * character in the pathname, or NULL if the pathname contains no + * separators. + */ +char * +find_last_pathname_separator(const char *path) +{ + char *separator; + +#ifdef _WIN32 + char c; + + /* + * We have to scan for '\' or '/'. + * Get to the end of the string. + */ + separator = strchr(path, '\0'); /* points to ending '\0' */ + while (separator > path) { + c = *--separator; + if (c == '\\' || c == '/') + return separator; /* found it */ + } + + /* + * OK, we didn't find any, so no directories - but there might + * be a drive letter.... + */ + return strchr(path, ':'); +#else + separator = strrchr(path, '/'); + return separator; +#endif +} + +/* + * Given a pathname, return the last component. + */ +const char * +get_basename(const char *path) +{ + const char *filename; + + ws_assert(path != NULL); + filename = find_last_pathname_separator(path); + if (filename == NULL) { + /* + * There're no directories, drive letters, etc. in the + * name; the pathname *is* the file name. + */ + filename = path; + } else { + /* + * Skip past the pathname or drive letter separator. + */ + filename++; + } + return filename; +} + +/* + * Given a pathname, return a string containing everything but the + * last component. NOTE: this overwrites the pathname handed into + * it.... + */ +char * +get_dirname(char *path) +{ + char *separator; + + ws_assert(path != NULL); + separator = find_last_pathname_separator(path); + if (separator == NULL) { + /* + * There're no directories, drive letters, etc. in the + * name; there is no directory path to return. + */ + return NULL; + } + + /* + * Get rid of the last pathname separator and the final file + * name following it. + */ + *separator = '\0'; + + /* + * "path" now contains the pathname of the directory containing + * the file/directory to which it referred. + */ + return path; +} + +/* + * Given a pathname, return: + * + * the errno, if an attempt to "stat()" the file fails; + * + * EISDIR, if the attempt succeeded and the file turned out + * to be a directory; + * + * 0, if the attempt succeeded and the file turned out not + * to be a directory. + */ + +int +test_for_directory(const char *path) +{ + ws_statb64 statb; + + if (ws_stat64(path, &statb) < 0) + return errno; + + if (S_ISDIR(statb.st_mode)) + return EISDIR; + else + return 0; +} + +int +test_for_fifo(const char *path) +{ + ws_statb64 statb; + + if (ws_stat64(path, &statb) < 0) + return errno; + + if (S_ISFIFO(statb.st_mode)) + return ESPIPE; + else + return 0; +} + +#ifdef ENABLE_APPLICATION_BUNDLE +/* + * Directory of the application bundle in which we're contained, + * if we're contained in an application bundle. Otherwise, NULL. + * + * Note: Table 2-5 "Subdirectories of the Contents directory" of + * + * https://developer.apple.com/library/mac/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html#//apple_ref/doc/uid/10000123i-CH101-SW1 + * + * says that the "Frameworks" directory + * + * Contains any private shared libraries and frameworks used by the + * executable. The frameworks in this directory are revision-locked + * to the application and cannot be superseded by any other, even + * newer, versions that may be available to the operating system. In + * other words, the frameworks included in this directory take precedence + * over any other similarly named frameworks found in other parts of + * the operating system. For information on how to add private + * frameworks to your application bundle, see Framework Programming Guide. + * + * so if we were to ship with any frameworks (e.g. Qt) we should + * perhaps put them in a Frameworks directory rather than under + * Resources. + * + * It also says that the "PlugIns" directory + * + * Contains loadable bundles that extend the basic features of your + * application. You use this directory to include code modules that + * must be loaded into your applicationbs process space in order to + * be used. You would not use this directory to store standalone + * executables. + * + * Our plugins are just raw .so/.dylib files; I don't know whether by + * "bundles" they mean application bundles (i.e., directory hierarchies) + * or just "bundles" in the Mach-O sense (which are an image type that + * can be loaded with dlopen() but not linked as libraries; our plugins + * are, I think, built as dylibs and can be loaded either way). + * + * And it says that the "SharedSupport" directory + * + * Contains additional non-critical resources that do not impact the + * ability of the application to run. You might use this directory to + * include things like document templates, clip art, and tutorials + * that your application expects to be present but that do not affect + * the ability of your application to run. + * + * I don't think I'd put the files that currently go under Resources/share + * into that category; they're not, for example, sample Lua scripts that + * don't actually get run by Wireshark, they're configuration/data files + * for Wireshark whose absence might not prevent Wireshark from running + * but that would affect how it behaves when run. + */ +static char *appbundle_dir; +#endif + +/* + * true if we're running from the build directory and we aren't running + * with special privileges. + */ +static bool running_in_build_directory_flag = false; + +/* + * Set our configuration namespace. This will be used for top-level + * configuration directory names and environment variable prefixes. + */ +static void +set_configuration_namespace(const char *namespace_name) +{ + + if (configuration_namespace != CONFIGURATION_NAMESPACE_UNINITIALIZED) { + return; + } + + if (!namespace_name || g_ascii_strcasecmp(namespace_name, "wireshark") == 0) + { + configuration_namespace = CONFIGURATION_NAMESPACE_WIRESHARK; + } + else if (g_ascii_strcasecmp(namespace_name, "logray") == 0) + { + configuration_namespace = CONFIGURATION_NAMESPACE_LOGRAY; + } + else + { + ws_error("Unknown configuration namespace %s", namespace_name); + } + + ws_debug("Using configuration namespace %s.", CONFIGURATION_NAMESPACE_PROPER); +} + +const char * +get_configuration_namespace(void) +{ + return CONFIGURATION_NAMESPACE_PROPER; +} + +bool is_packet_configuration_namespace(void) +{ + return configuration_namespace != CONFIGURATION_NAMESPACE_LOGRAY; +} + +#ifndef _WIN32 +/* + * Get the pathname of the executable using various platform- + * dependent mechanisms for various UN*Xes. + * + * These calls all should return something independent of the argv[0] + * passed to the program, so it shouldn't be fooled by an argv[0] + * that doesn't match the executable path. + * + * We don't use dladdr() because: + * + * not all UN*Xes necessarily have dladdr(); + * + * those that do have it don't necessarily have dladdr(main) + * return information about the executable image; + * + * those that do have a dladdr() where dladdr(main) returns + * information about the executable image don't necessarily + * have a mechanism by which the executable image can get + * its own path from the kernel (either by a call or by it + * being handed to it along with argv[] and the environment), + * so they just fall back on getting it from argv[0], which we + * already have code to do; + * + * those that do have such a mechanism don't necessarily use + * it in dladdr(), and, instead, just fall back on getting it + * from argv[0]; + * + * so the only places where it's worth bothering to use dladdr() + * are platforms where dladdr(main) return information about the + * executable image by getting it from the kernel rather than + * by looking at argv[0], and where we can't get at that information + * ourselves, and we haven't seen any indication that there are any + * such platforms. + * + * In particular, some dynamic linkers supply a dladdr() such that + * dladdr(main) just returns something derived from argv[0], so + * just using dladdr(main) is the wrong thing to do if there's + * another mechanism that can get you a more reliable version of + * the executable path. + * + * So, on platforms where we know of a mechanism to get that path + * (where getting that path doesn't involve argv[0], which is not + * guaranteed to reflect the path to the binary), this routine + * attempsts to use that platform's mechanism. On other platforms, + * it just returns NULL. + * + * This is not guaranteed to return an absolute path; if it doesn't, + * our caller must prepend the current directory if it's a path. + * + * This is not guaranteed to return the "real path"; it might return + * something with symbolic links in the path. Our caller must + * use realpath() if they want the real thing, but that's also true of + * something obtained by looking at argv[0]. + */ +#define xx_free free /* hack so checkAPIs doesn't complain */ +static const char * +get_current_executable_path(void) +{ +#if defined(ENABLE_APPLICATION_BUNDLE) + static char *executable_path; + uint32_t path_buf_size; + + if (executable_path) { + return executable_path; + } + + path_buf_size = PATH_MAX; + executable_path = (char *)g_malloc(path_buf_size); + if (_NSGetExecutablePath(executable_path, &path_buf_size) == -1) { + executable_path = (char *)g_realloc(executable_path, path_buf_size); + if (_NSGetExecutablePath(executable_path, &path_buf_size) == -1) + return NULL; + } + /* + * Resolve our path so that it's possible to symlink the executables + * in our application bundle. + */ + char *rp_execpath = realpath(executable_path, NULL); + if (rp_execpath) { + g_free(executable_path); + executable_path = g_strdup(rp_execpath); + xx_free(rp_execpath); + } + return executable_path; +#elif defined(__linux__) + /* + * In older versions of GNU libc's dynamic linker, as used on Linux, + * dladdr(main) supplies a path based on argv[0], so we use + * /proc/self/exe instead; there are Linux distributions with + * kernels that support /proc/self/exe and those older versions + * of the dynamic linker, and this will get a better answer on + * those versions. + * + * It only works on Linux 2.2 or later, so we just give up on + * earlier versions. + * + * XXX - are there OS versions that support "exe" but not "self"? + */ + struct utsname name; + static char executable_path[PATH_MAX + 1]; + ssize_t r; + + if (uname(&name) == -1) + return NULL; + if (strncmp(name.release, "1.", 2) == 0) + return NULL; /* Linux 1.x */ + if (strcmp(name.release, "2.0") == 0 || + strncmp(name.release, "2.0.", 4) == 0 || + strcmp(name.release, "2.1") == 0 || + strncmp(name.release, "2.1.", 4) == 0) + return NULL; /* Linux 2.0.x or 2.1.x */ + if ((r = readlink("/proc/self/exe", executable_path, PATH_MAX)) == -1) + return NULL; + executable_path[r] = '\0'; + return executable_path; +#elif defined(__FreeBSD__) && defined(KERN_PROC_PATHNAME) + /* + * In older versions of FreeBSD's dynamic linker, dladdr(main) + * supplies a path based on argv[0], so we use the KERN_PROC_PATHNAME + * sysctl instead; there are, I think, versions of FreeBSD + * that support the sysctl that have and those older versions + * of the dynamic linker, and this will get a better answer on + * those versions. + */ + int mib[4]; + char *executable_path; + size_t path_buf_size; + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PATHNAME; + mib[3] = -1; + path_buf_size = PATH_MAX; + executable_path = (char *)g_malloc(path_buf_size); + if (sysctl(mib, 4, executable_path, &path_buf_size, NULL, 0) == -1) { + if (errno != ENOMEM) + return NULL; + executable_path = (char *)g_realloc(executable_path, path_buf_size); + if (sysctl(mib, 4, executable_path, &path_buf_size, NULL, 0) == -1) + return NULL; + } + return executable_path; +#elif defined(__NetBSD__) + /* + * In all versions of NetBSD's dynamic linker as of 2013-08-12, + * dladdr(main) supplies a path based on argv[0], so we use + * /proc/curproc/exe instead. + * + * XXX - are there OS versions that support "exe" but not "curproc" + * or "self"? Are there any that support "self" but not "curproc"? + */ + static char executable_path[PATH_MAX + 1]; + ssize_t r; + + if ((r = readlink("/proc/curproc/exe", executable_path, PATH_MAX)) == -1) + return NULL; + executable_path[r] = '\0'; + return executable_path; +#elif defined(__DragonFly__) + /* + * In older versions of DragonFly BSD's dynamic linker, dladdr(main) + * supplies a path based on argv[0], so we use /proc/curproc/file + * instead; it appears to be supported by all versions of DragonFly + * BSD. + */ + static char executable_path[PATH_MAX + 1]; + ssize_t r; + + if ((r = readlink("/proc/curproc/file", executable_path, PATH_MAX)) == -1) + return NULL; + executable_path[r] = '\0'; + return executable_path; +#elif defined(HAVE_GETEXECNAME) + /* + * Solaris, with getexecname(). + * It appears that getexecname() dates back to at least Solaris 8, + * but /proc/{pid}/path is first documented in the Solaris 10 documentation, + * so we use getexecname() if available, rather than /proc/self/path/a.out + * (which isn't documented, but appears to be a symlink to the + * executable image file). + */ + return getexecname(); +#elif defined(HAVE_DLGET) + /* + * HP-UX 11, with dlget(); use dlget() and dlgetname(). + * See + * + * https://web.archive.org/web/20081025174755/http://h21007.www2.hp.com/portal/site/dspp/menuitem.863c3e4cbcdc3f3515b49c108973a801?ciid=88086d6e1de021106d6e1de02110275d6e10RCRD#two + */ + struct load_module_desc desc; + + if (dlget(-2, &desc, sizeof(desc)) != NULL) + return dlgetname(&desc, sizeof(desc), NULL, NULL, NULL); + else + return NULL; +#else + /* Fill in your favorite UN*X's code here, if there is something */ + return NULL; +#endif +} +#endif /* _WIN32 */ + +static void trim_progfile_dir(void) +{ + char *progfile_last_dir = find_last_pathname_separator(progfile_dir); + + if (! (progfile_last_dir && strncmp(progfile_last_dir + 1, "extcap", sizeof("extcap")) == 0)) { + return; + } + + *progfile_last_dir = '\0'; + char *extcap_progfile_dir = progfile_dir; + progfile_dir = g_strdup(extcap_progfile_dir); + g_free(extcap_progfile_dir); +} + +#if !defined(_WIN32) || defined(HAVE_MSYSTEM) +static char * +trim_last_dir_from_path(const char *_path) +{ + char *path = ws_strdup(_path); + char *last_dir = find_last_pathname_separator(path); + if (last_dir) { + *last_dir = '\0'; + } + return path; +} +#endif + +/* + * Construct the path name of a non-extcap Wireshark executable file, + * given the program name. The executable name doesn't include ".exe"; + * append it on Windows, so that callers don't have to worry about that. + * + * This presumes that all non-extcap executables are in the same directory. + * + * The returned file name was g_malloc()'d so it must be g_free()d when the + * caller is done with it. + */ +char * +get_executable_path(const char *program_name) +{ + /* + * Fail if we don't know what directory contains the executables. + */ + if (progfile_dir == NULL) + return NULL; + +#ifdef _WIN32 + return ws_strdup_printf("%s\\%s.exe", progfile_dir, program_name); +#else + return ws_strdup_printf("%s/%s", progfile_dir, program_name); +#endif +} + +/* + * Get the pathname of the directory from which the executable came, + * and save it for future use. Returns NULL on success, and a + * g_mallocated string containing an error on failure. + */ +#ifdef _WIN32 +char * +configuration_init_w32(const char* arg0 _U_) +{ + TCHAR prog_pathname_w[_MAX_PATH+2]; + char *prog_pathname; + DWORD error; + TCHAR *msg_w; + unsigned char *msg; + size_t msglen; + + /* + * Attempt to get the full pathname of the currently running + * program. + */ + if (GetModuleFileName(NULL, prog_pathname_w, G_N_ELEMENTS(prog_pathname_w)) != 0 && GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + /* + * XXX - Should we use g_utf16_to_utf8()? + */ + prog_pathname = utf_16to8(prog_pathname_w); + /* + * We got it; strip off the last component, which would be + * the file name of the executable, giving us the pathname + * of the directory where the executable resides. + */ + progfile_dir = g_path_get_dirname(prog_pathname); + if (progfile_dir != NULL) { + trim_progfile_dir(); + /* we succeeded */ + } else { + /* + * OK, no. What do we do now? + */ + return ws_strdup_printf("No \\ in executable pathname \"%s\"", + prog_pathname); + } + } else { + /* + * Oh, well. Return an indication of the error. + */ + error = GetLastError(); + if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, error, 0, (LPTSTR) &msg_w, 0, NULL) == 0) { + /* + * Gak. We can't format the message. + */ + return ws_strdup_printf("GetModuleFileName failed: %lu (FormatMessage failed: %lu)", + error, GetLastError()); + } + msg = utf_16to8(msg_w); + LocalFree(msg_w); + /* + * "FormatMessage()" "helpfully" sticks CR/LF at the + * end of the message. Get rid of it. + */ + msglen = strlen(msg); + if (msglen >= 2) { + msg[msglen - 1] = '\0'; + msg[msglen - 2] = '\0'; + } + return ws_strdup_printf("GetModuleFileName failed: %s (%lu)", + msg, error); + } + +#ifdef HAVE_MSYSTEM + /* + * We already have the program_dir. Find the installation prefix. + * This is one level up from the bin_dir. If the program_dir does + * not end with "bin" then assume we are running in the build directory + * and the "installation prefix" (staging directory) is the same as + * the program_dir. + */ + if (g_str_has_suffix(progfile_dir, _S"bin")) { + install_prefix = trim_last_dir_from_path(progfile_dir); + } + else { + install_prefix = g_strdup(progfile_dir); + running_in_build_directory_flag = true; + } +#endif /* HAVE_MSYSTEM */ + + return NULL; +} + +#else /* !_WIN32 */ + +char * +configuration_init_posix(const char* arg0) +{ + const char *execname; + char *prog_pathname; + char *curdir; + long path_max; + const char *pathstr; + const char *path_start, *path_end; + size_t path_component_len, path_len; + char *retstr; + char *path; + char *dir_end; + + /* Hard-coded value used if we cannot obtain the path of the running executable. */ + install_prefix = g_strdup(INSTALL_PREFIX); + + /* + * Check whether XXX_RUN_FROM_BUILD_DIRECTORY is set in the + * environment; if so, set running_in_build_directory_flag if we + * weren't started with special privileges. (If we were started + * with special privileges, it's not safe to allow the user to point + * us to some other directory; running_in_build_directory_flag, when + * set, causes us to look for plugins and the like in the build + * directory.) + */ + const char *run_from_envar = CONFIGURATION_ENVIRONMENT_VARIABLE("RUN_FROM_BUILD_DIRECTORY"); + if (g_getenv(run_from_envar) != NULL + && !started_with_special_privs()) { + running_in_build_directory_flag = true; + } + + execname = get_current_executable_path(); + if (execname == NULL) { + /* + * OK, guess based on argv[0]. + */ + execname = arg0; + } + + /* + * Try to figure out the directory in which the currently running + * program resides, given something purporting to be the executable + * name (from an OS mechanism or from the argv[0] it was started with). + * That might be the absolute path of the program, or a path relative + * to the current directory of the process that started it, or + * just a name for the program if it was started from the command + * line and was searched for in $PATH. It's not guaranteed to be + * any of those, however, so there are no guarantees.... + */ + if (execname[0] == '/') { + /* + * It's an absolute path. + */ + prog_pathname = g_strdup(execname); + } else if (strchr(execname, '/') != NULL) { + /* + * It's a relative path, with a directory in it. + * Get the current directory, and combine it + * with that directory. + */ + path_max = pathconf(".", _PC_PATH_MAX); + if (path_max == -1) { + /* + * We have no idea how big a buffer to + * allocate for the current directory. + */ + return ws_strdup_printf("pathconf failed: %s\n", + g_strerror(errno)); + } + curdir = (char *)g_malloc(path_max); + if (getcwd(curdir, path_max) == NULL) { + /* + * It failed - give up, and just stick + * with DATA_DIR. + */ + g_free(curdir); + return ws_strdup_printf("getcwd failed: %s\n", + g_strerror(errno)); + } + path = ws_strdup_printf("%s/%s", curdir, execname); + g_free(curdir); + prog_pathname = path; + } else { + /* + * It's just a file name. + * Search the path for a file with that name + * that's executable. + */ + prog_pathname = NULL; /* haven't found it yet */ + pathstr = g_getenv("PATH"); + path_start = pathstr; + if (path_start != NULL) { + while (*path_start != '\0') { + path_end = strchr(path_start, ':'); + if (path_end == NULL) + path_end = path_start + strlen(path_start); + path_component_len = path_end - path_start; + path_len = path_component_len + 1 + + strlen(execname) + 1; + path = (char *)g_malloc(path_len); + memcpy(path, path_start, path_component_len); + path[path_component_len] = '\0'; + (void) g_strlcat(path, "/", path_len); + (void) g_strlcat(path, execname, path_len); + if (access(path, X_OK) == 0) { + /* + * Found it! + */ + prog_pathname = path; + break; + } + + /* + * That's not it. If there are more + * path components to test, try them. + */ + if (*path_end == ':') + path_end++; + path_start = path_end; + g_free(path); + } + if (prog_pathname == NULL) { + /* + * Program not found in path. + */ + return ws_strdup_printf("\"%s\" not found in \"%s\"", + execname, pathstr); + } + } else { + /* + * PATH isn't set. + * XXX - should we pick a default? + */ + return g_strdup("PATH isn't set"); + } + } + + /* + * OK, we have what we think is the pathname + * of the program. + * + * First, find the last "/" in the directory, + * as that marks the end of the directory pathname. + */ + dir_end = strrchr(prog_pathname, '/'); + if (dir_end != NULL) { + /* + * Found it. Strip off the last component, + * as that's the path of the program. + */ + *dir_end = '\0'; + + /* + * Is there a "/run" at the end? + */ + dir_end = strrchr(prog_pathname, '/'); + if (dir_end != NULL) { + if (!started_with_special_privs()) { + /* + * Check for the CMake output directory. As people may name + * their directories "run" (really?), also check for the + * CMakeCache.txt file before assuming a CMake output dir. + */ + if (strcmp(dir_end, "/run") == 0) { + char *cmake_file; + cmake_file = ws_strdup_printf("%.*s/CMakeCache.txt", + (int)(dir_end - prog_pathname), + prog_pathname); + if (file_exists(cmake_file)) + running_in_build_directory_flag = true; + g_free(cmake_file); + } +#ifdef ENABLE_APPLICATION_BUNDLE + { + /* + * Scan up the path looking for a component + * named "Contents". If we find it, we assume + * we're in a bundle, and that the top-level + * directory of the bundle is the one containing + * "Contents". + * + * Not all executables are in the Contents/MacOS + * directory, so we can't just check for those + * in the path and strip them off. + * + * XXX - should we assume that it's either + * Contents/MacOS or Resources/bin? + */ + char *component_end, *p; + + component_end = strchr(prog_pathname, '\0'); + p = component_end; + for (;;) { + while (p >= prog_pathname && *p != '/') + p--; + if (p == prog_pathname) { + /* + * We're looking at the first component of + * the pathname now, so we're definitely + * not in a bundle, even if we're in + * "/Contents". + */ + break; + } + if (strncmp(p, "/Contents", component_end - p) == 0) { + /* Found it. */ + appbundle_dir = (char *)g_malloc(p - prog_pathname + 1); + memcpy(appbundle_dir, prog_pathname, p - prog_pathname); + appbundle_dir[p - prog_pathname] = '\0'; + break; + } + component_end = p; + p--; + } + } +#endif + } + } + + /* + * OK, we have the path we want. + */ + progfile_dir = prog_pathname; + trim_progfile_dir(); + } else { + /* + * This "shouldn't happen"; we apparently + * have no "/" in the pathname. + * Just free up prog_pathname. + */ + retstr = ws_strdup_printf("No / found in \"%s\"", prog_pathname); + g_free(prog_pathname); + return retstr; + } + + /* + * We already have the program_dir. Find the installation prefix. + * This is one level up from the bin_dir. If the program_dir does + * not end with "bin" then assume we are running in the build directory + * and the "installation prefix" (staging directory) is the same as + * the program_dir. + */ + g_free(install_prefix); + if (g_str_has_suffix(progfile_dir, _S"bin")) { + install_prefix = trim_last_dir_from_path(progfile_dir); + } + else { + install_prefix = g_strdup(progfile_dir); + running_in_build_directory_flag = true; + } + + return NULL; +} +#endif /* ?_WIN32 */ + +char * +configuration_init(const char* arg0, const char *namespace_name) +{ + set_configuration_namespace(namespace_name); + +#ifdef _WIN32 + return configuration_init_w32(arg0); +#else + return configuration_init_posix(arg0); +#endif +} + +/* + * Get the directory in which the program resides. + */ +const char * +get_progfile_dir(void) +{ + return progfile_dir; +} + +/* + * Get the directory in which the global configuration and data files are + * stored. + * + * On Windows, we use the directory in which the executable for this + * process resides. + * + * On macOS (when executed from an app bundle), use a directory within + * that app bundle. + * + * Otherwise, if the program was executed from the build directory, use the + * directory in which the executable for this process resides. In all other + * cases, use the DATA_DIR value that was set at compile time. + * + * XXX - if we ever make libwireshark a real library, used by multiple + * applications (more than just TShark and versions of Wireshark with + * various UIs), should the configuration files belong to the library + * (and be shared by all those applications) or to the applications? + * + * If they belong to the library, that could be done on UNIX by the + * configure script, but it's trickier on Windows, as you can't just + * use the pathname of the executable. + * + * If they belong to the application, that could be done on Windows + * by using the pathname of the executable, but we'd have to have it + * passed in as an argument, in some call, on UNIX. + * + * Note that some of those configuration files might be used by code in + * libwireshark, some of them might be used by dissectors (would they + * belong to libwireshark, the application, or a separate library?), + * and some of them might be used by other code (the Wireshark preferences + * file includes resolver preferences that control the behavior of code + * in libwireshark, dissector preferences, and UI preferences, for + * example). + */ +const char * +get_datafile_dir(void) +{ + if (datafile_dir != NULL) + return datafile_dir; + + const char *data_dir_envar = CONFIGURATION_ENVIRONMENT_VARIABLE("DATA_DIR"); + if (g_getenv(data_dir_envar) && !started_with_special_privs()) { + /* + * The user specified a different directory for data files + * and we aren't running with special privileges. + * Let {WIRESHARK,LOGRAY}_DATA_DIR take precedence. + * XXX - We might be able to dispense with the priv check + */ + datafile_dir = g_strdup(g_getenv(data_dir_envar)); + return datafile_dir; + } + +#if defined(HAVE_MSYSTEM) + if (running_in_build_directory_flag) { + datafile_dir = g_strdup(install_prefix); + } else { + datafile_dir = g_build_filename(install_prefix, DATA_DIR, (char *)NULL); + } +#elif defined(_WIN32) + /* + * Do we have the pathname of the program? If so, assume we're + * running an installed version of the program. If we fail, + * we don't change "datafile_dir", and thus end up using the + * default. + * + * XXX - does NSIS put the installation directory into + * "\HKEY_LOCAL_MACHINE\SOFTWARE\Wireshark\InstallDir"? + * If so, perhaps we should read that from the registry, + * instead. + */ + if (progfile_dir != NULL) { + /* + * Yes, we do; use that. + */ + datafile_dir = g_strdup(progfile_dir); + } else { + /* + * No, we don't. + * Fall back on the default installation directory. + */ + datafile_dir = g_strdup("C:\\Program Files\\Wireshark\\"); + } +#else +#ifdef ENABLE_APPLICATION_BUNDLE + /* + * If we're running from an app bundle and weren't started + * with special privileges, use the Contents/Resources/share/wireshark + * subdirectory of the app bundle. + * + * (appbundle_dir is not set to a non-null value if we're + * started with special privileges, so we need only check + * it; we don't need to call started_with_special_privs().) + */ + else if (appbundle_dir != NULL) { + datafile_dir = ws_strdup_printf("%s/Contents/Resources/share/%s", + appbundle_dir, CONFIGURATION_NAMESPACE_LOWER); + } +#endif + else if (running_in_build_directory_flag && progfile_dir != NULL) { + /* + * We're (probably) being run from the build directory and + * weren't started with special privileges. + * + * (running_in_build_directory_flag is never set to true + * if we're started with special privileges, so we need + * only check it; we don't need to call started_with_special_privs().) + * + * Data files (dtds/, radius/, etc.) are copied to the build + * directory during the build which also contains executables. A special + * exception is macOS (when built with an app bundle). + */ + datafile_dir = g_strdup(progfile_dir); + } else { + datafile_dir = g_build_filename(install_prefix, DATA_DIR, (char *)NULL); + } +#endif + return datafile_dir; +} + +const char * +get_doc_dir(void) +{ + if (doc_dir != NULL) + return doc_dir; + + /* No environment variable for this. */ + if (false) { + ; + } + +#if defined(HAVE_MSYSTEM) + if (running_in_build_directory_flag) { + doc_dir = g_strdup(install_prefix); + } else { + doc_dir = g_build_filename(install_prefix, DOC_DIR, (char *)NULL); + } +#elif defined(_WIN32) + if (progfile_dir != NULL) { + doc_dir = g_strdup(progfile_dir); + } else { + /* Fall back on the default installation directory. */ + doc_dir = g_strdup("C:\\Program Files\\Wireshark\\"); + } +#else +#ifdef ENABLE_APPLICATION_BUNDLE + /* + * If we're running from an app bundle and weren't started + * with special privileges, use the Contents/Resources/share/wireshark + * subdirectory of the app bundle. + * + * (appbundle_dir is not set to a non-null value if we're + * started with special privileges, so we need only check + * it; we don't need to call started_with_special_privs().) + */ + else if (appbundle_dir != NULL) { + doc_dir = ws_strdup_printf("%s/Contents/Resources/%s", + appbundle_dir, DOC_DIR); + } +#endif + else if (running_in_build_directory_flag && progfile_dir != NULL) { + /* + * We're (probably) being run from the build directory and + * weren't started with special privileges. + */ + doc_dir = g_strdup(progfile_dir); + } else { + doc_dir = g_build_filename(install_prefix, DOC_DIR, (char *)NULL); + } +#endif + return doc_dir; +} + +/* + * Find the directory where the plugins are stored. + * + * On Windows, we use the plugin\{VERSION} subdirectory of the datafile + * directory, where {VERSION} is the version number of this version of + * Wireshark. + * + * On UN*X: + * + * if we appear to be run from the build directory, we use the + * "plugin" subdirectory of the datafile directory; + * + * otherwise, if the WIRESHARK_PLUGIN_DIR environment variable is + * set and we aren't running with special privileges, we use the + * value of that environment variable; + * + * otherwise, if we're running from an app bundle in macOS, we + * use the Contents/PlugIns/wireshark subdirectory of the app bundle; + * + * otherwise, we use the PLUGIN_DIR value supplied by the + * configure script. + */ +static char *plugin_dir = NULL; +static char *plugin_dir_with_version = NULL; +static char *plugin_pers_dir = NULL; +static char *plugin_pers_dir_with_version = NULL; +static char *extcap_pers_dir = NULL; + +static void +init_plugin_dir(void) +{ + const char *plugin_dir_envar = CONFIGURATION_ENVIRONMENT_VARIABLE("PLUGIN_DIR"); + if (g_getenv(plugin_dir_envar) && !started_with_special_privs()) { + /* + * The user specified a different directory for plugins + * and we aren't running with special privileges. + * Let {WIRESHARK,LOGRAY}_PLUGIN_DIR take precedence. + */ + plugin_dir = g_strdup(g_getenv(plugin_dir_envar)); + return; + } + +#if defined(HAVE_PLUGINS) || defined(HAVE_LUA) +#if defined(HAVE_MSYSTEM) + if (running_in_build_directory_flag) { + plugin_dir = g_build_filename(install_prefix, "plugins", (char *)NULL); + } else { + plugin_dir = g_build_filename(install_prefix, PLUGIN_DIR, (char *)NULL); + } +#elif defined(_WIN32) + /* + * On Windows, the data file directory is the installation + * directory; the plugins are stored under it. + * + * Assume we're running the installed version of Wireshark; + * on Windows, the data file directory is the directory + * in which the Wireshark binary resides. + */ + plugin_dir = g_build_filename(get_datafile_dir(), "plugins", (char *)NULL); + + /* + * Make sure that pathname refers to a directory. + */ + if (test_for_directory(plugin_dir) != EISDIR) { + /* + * Either it doesn't refer to a directory or it + * refers to something that doesn't exist. + * + * Assume that means we're running a version of + * Wireshark we've built in a build directory, + * in which case {datafile dir}\plugins is the + * top-level plugins source directory, and use + * that directory and set the "we're running in + * a build directory" flag, so the plugin + * scanner will check all subdirectories of that + * directory for plugins. + */ + g_free(plugin_dir); + plugin_dir = g_build_filename(get_datafile_dir(), "plugins", (char *)NULL); + running_in_build_directory_flag = true; + } +#else +#ifdef ENABLE_APPLICATION_BUNDLE + /* + * If we're running from an app bundle and weren't started + * with special privileges, use the Contents/PlugIns/wireshark + * subdirectory of the app bundle. + * + * (appbundle_dir is not set to a non-null value if we're + * started with special privileges, so we need only check + * it; we don't need to call started_with_special_privs().) + */ + else if (appbundle_dir != NULL) { + plugin_dir = g_build_filename(appbundle_dir, "Contents/PlugIns", + CONFIGURATION_NAMESPACE_LOWER, (char *)NULL); + } +#endif + else if (running_in_build_directory_flag) { + /* + * We're (probably) being run from the build directory and + * weren't started with special privileges, so we'll use + * the "plugins" subdirectory of the directory where the program + * we're running is (that's the build directory). + */ + plugin_dir = g_build_filename(get_progfile_dir(), "plugins", (char *)NULL); + } else { + plugin_dir = g_build_filename(install_prefix, PLUGIN_DIR, (char *)NULL); + } +#endif +#endif /* defined(HAVE_PLUGINS) || defined(HAVE_LUA) */ +} + +static void +init_plugin_pers_dir(void) +{ +#if defined(HAVE_PLUGINS) || defined(HAVE_LUA) +#ifdef _WIN32 + plugin_pers_dir = get_persconffile_path(PLUGINS_DIR_NAME, false); +#else + plugin_pers_dir = g_build_filename(g_get_home_dir(), ".local/lib", + CONFIGURATION_NAMESPACE_LOWER, PLUGINS_DIR_NAME, (char *)NULL); +#endif +#endif /* defined(HAVE_PLUGINS) || defined(HAVE_LUA) */ +} + +/* + * Get the directory in which the plugins are stored. + */ +const char * +get_plugins_dir(void) +{ + if (!plugin_dir) + init_plugin_dir(); + return plugin_dir; +} + +const char * +get_plugins_dir_with_version(void) +{ + if (!plugin_dir) + init_plugin_dir(); + if (plugin_dir && !plugin_dir_with_version) + plugin_dir_with_version = g_build_filename(plugin_dir, PLUGIN_PATH_ID, (char *)NULL); + return plugin_dir_with_version; +} + +/* Get the personal plugin dir */ +const char * +get_plugins_pers_dir(void) +{ + if (!plugin_pers_dir) + init_plugin_pers_dir(); + return plugin_pers_dir; +} + +const char * +get_plugins_pers_dir_with_version(void) +{ + if (!plugin_pers_dir) + init_plugin_pers_dir(); + if (plugin_pers_dir && !plugin_pers_dir_with_version) + plugin_pers_dir_with_version = g_build_filename(plugin_pers_dir, PLUGIN_PATH_ID, (char *)NULL); + return plugin_pers_dir_with_version; +} + +/* + * Find the directory where the extcap hooks are stored. + * + * If the WIRESHARK_EXTCAP_DIR environment variable is set and we are not + * running with special privileges, use that. Otherwise: + * + * On Windows, we use the "extcap" subdirectory of the datafile directory. + * + * On UN*X: + * + * if we appear to be run from the build directory, we use the + * "extcap" subdirectory of the build directory. + * + * otherwise, if we're running from an app bundle in macOS, we + * use the Contents/MacOS/extcap subdirectory of the app bundle; + * + * otherwise, we use the EXTCAP_DIR value supplied by CMake. + */ +static char *extcap_dir = NULL; + +static void +init_extcap_dir(void) +{ + const char *extcap_dir_envar = CONFIGURATION_ENVIRONMENT_VARIABLE("EXTCAP_DIR"); + if (g_getenv(extcap_dir_envar) && !started_with_special_privs()) { + /* + * The user specified a different directory for extcap hooks + * and we aren't running with special privileges. + */ + extcap_dir = g_strdup(g_getenv(extcap_dir_envar)); + } + +#if defined(HAVE_MSYSTEM) + else if (running_in_build_directory_flag) { + extcap_dir = g_build_filename(install_prefix, "extcap", (char *)NULL); + } else { + extcap_dir = g_build_filename(install_prefix, EXTCAP_DIR, (char *)NULL); + } +#elif defined(_WIN32) + else { + /* + * On Windows, the data file directory is the installation + * directory; the extcap hooks are stored under it. + * + * Assume we're running the installed version of Wireshark; + * on Windows, the data file directory is the directory + * in which the Wireshark binary resides. + */ + extcap_dir = g_build_filename(get_datafile_dir(), "extcap", (char *)NULL); + } +#else + else if (running_in_build_directory_flag) { + /* + * We're (probably) being run from the build directory and + * weren't started with special privileges, so we'll use + * the "extcap hooks" subdirectory of the directory where the program + * we're running is (that's the build directory). + */ + extcap_dir = g_build_filename(get_progfile_dir(), "extcap", (char *)NULL); + } +#ifdef ENABLE_APPLICATION_BUNDLE + else if (appbundle_dir != NULL) { + /* + * If we're running from an app bundle and weren't started + * with special privileges, use the Contents/MacOS/extcap + * subdirectory of the app bundle. + * + * (appbundle_dir is not set to a non-null value if we're + * started with special privileges, so we need only check + * it; we don't need to call started_with_special_privs().) + */ + extcap_dir = g_build_filename(appbundle_dir, "Contents/MacOS/extcap", (char *)NULL); + } +#endif + else { + extcap_dir = g_build_filename(install_prefix, EXTCAP_DIR, (char *)NULL); + } +#endif +} + +static void +init_extcap_pers_dir(void) +{ +#ifdef _WIN32 + extcap_pers_dir = get_persconffile_path(EXTCAP_DIR_NAME, false); +#else + extcap_pers_dir = g_build_filename(g_get_home_dir(), ".local/lib", + CONFIGURATION_NAMESPACE_LOWER, EXTCAP_DIR_NAME, (char *)NULL); +#endif +} + +/* + * Get the directory in which the extcap hooks are stored. + * + */ +const char * +get_extcap_dir(void) +{ + if (!extcap_dir) + init_extcap_dir(); + return extcap_dir; +} + +/* Get the personal plugin dir */ +const char * +get_extcap_pers_dir(void) +{ + if (!extcap_pers_dir) + init_extcap_pers_dir(); + return extcap_pers_dir; +} + +/* + * Get the flag indicating whether we're running from a build + * directory. + */ +bool +running_in_build_directory(void) +{ + return running_in_build_directory_flag; +} + +/* + * Get the directory in which files that, at least on UNIX, are + * system files (such as "/etc/ethers") are stored; on Windows, + * there's no "/etc" directory, so we get them from the global + * configuration and data file directory. + */ +const char * +get_systemfile_dir(void) +{ +#ifdef _WIN32 + return get_datafile_dir(); +#else + return "/etc"; +#endif +} + +void +set_profile_name(const char *profilename) +{ + g_free (persconfprofile); + + if (profilename && strlen(profilename) > 0 && + strcmp(profilename, DEFAULT_PROFILE) != 0) { + persconfprofile = g_strdup (profilename); + } else { + /* Default Profile */ + persconfprofile = NULL; + } +} + +const char * +get_profile_name(void) +{ + if (persconfprofile) { + return persconfprofile; + } else { + return DEFAULT_PROFILE; + } +} + +bool +is_default_profile(void) +{ + return (!persconfprofile || strcmp(persconfprofile, DEFAULT_PROFILE) == 0) ? true : false; +} + +bool +has_global_profiles(void) +{ + WS_DIR *dir; + WS_DIRENT *file; + char *global_dir = get_global_profiles_dir(); + char *filename; + bool has_global = false; + + if ((test_for_directory(global_dir) == EISDIR) && + ((dir = ws_dir_open(global_dir, 0, NULL)) != NULL)) + { + while ((file = ws_dir_read_name(dir)) != NULL) { + filename = ws_strdup_printf ("%s%s%s", global_dir, G_DIR_SEPARATOR_S, + ws_dir_get_name(file)); + if (test_for_directory(filename) == EISDIR) { + has_global = true; + g_free (filename); + break; + } + g_free (filename); + } + ws_dir_close(dir); + } + g_free(global_dir); + return has_global; +} + +void +profile_store_persconffiles(bool store) +{ + if (store) { + profile_files = g_hash_table_new (g_str_hash, g_str_equal); + } + do_store_persconffiles = store; +} + +void +profile_register_persconffile(const char *filename) +{ + if (do_store_persconffiles && !g_hash_table_lookup (profile_files, filename)) { + /* Store filenames so we know which filenames belongs to a configuration profile */ + g_hash_table_insert (profile_files, g_strdup(filename), g_strdup(filename)); + } +} + +/* + * Get the directory in which personal configuration files reside. + * + * On Windows, it's "Wireshark", under %APPDATA% or, if %APPDATA% isn't set, + * it's "%USERPROFILE%\Application Data" (which is what %APPDATA% normally + * is on Windows 2000). + * + * On UNIX-compatible systems, we first look in XDG_CONFIG_HOME/wireshark + * and, if that doesn't exist, ~/.wireshark, for backwards compatibility. + * If neither exists, we use XDG_CONFIG_HOME/wireshark, so that the directory + * is initially created as XDG_CONFIG_HOME/wireshark. We use that regardless + * of whether the user is running under an XDG desktop or not, so that + * if the user's home directory is on a server and shared between + * different desktop environments on different machines, they can all + * share the same configuration file directory. + * + * XXX - what about stuff that shouldn't be shared between machines, + * such as plugins in the form of shared loadable images? + */ +static const char * +get_persconffile_dir_no_profile(void) +{ + const char *env; + + /* Return the cached value, if available */ + if (persconffile_dir != NULL) + return persconffile_dir; + + /* + * See if the user has selected an alternate environment. + */ + const char *config_dir_envar = CONFIGURATION_ENVIRONMENT_VARIABLE("CONFIG_DIR"); + env = g_getenv(config_dir_envar); +#ifdef _WIN32 + if (env == NULL) { + /* for backward compatibility */ + env = g_getenv("WIRESHARK_APPDATA"); + } +#endif + if (env != NULL) { + persconffile_dir = g_strdup(env); + return persconffile_dir; + } + +#ifdef _WIN32 + /* + * Use %APPDATA% or %USERPROFILE%, so that configuration + * files are stored in the user profile, rather than in + * the home directory. The Windows convention is to store + * configuration information in the user profile, and doing + * so means you can use Wireshark even if the home directory + * is an inaccessible network drive. + */ + env = g_getenv("APPDATA"); + const char *persconf_namespace = CONFIGURATION_NAMESPACE_PROPER; + if (env != NULL) { + /* + * Concatenate %APPDATA% with "\Wireshark" or "\Logray". + */ + persconffile_dir = g_build_filename(env, persconf_namespace, NULL); + return persconffile_dir; + } + + /* + * OK, %APPDATA% wasn't set, so use %USERPROFILE%\Application Data. + */ + env = g_getenv("USERPROFILE"); + if (env != NULL) { + persconffile_dir = g_build_filename(env, "Application Data", persconf_namespace, NULL); + return persconffile_dir; + } + + /* + * Give up and use "C:". + */ + persconffile_dir = g_build_filename("C:", persconf_namespace, NULL); + return persconffile_dir; +#else + char *xdg_path, *path; + struct passwd *pwd; + const char *homedir; + + /* + * Check if XDG_CONFIG_HOME/wireshark exists and is a directory. + */ + xdg_path = g_build_filename(g_get_user_config_dir(), + CONFIGURATION_NAMESPACE_LOWER, NULL); + if (g_file_test(xdg_path, G_FILE_TEST_IS_DIR)) { + persconffile_dir = xdg_path; + return persconffile_dir; + } + + /* + * It doesn't exist, or it does but isn't a directory, so try + * ~/.wireshark. + * + * If $HOME is set, use that for ~. + * + * (Note: before GLib 2.36, g_get_home_dir() didn't look at $HOME, + * but we always want to do so, so we don't use g_get_home_dir().) + */ + homedir = g_getenv("HOME"); + if (homedir == NULL) { + /* + * It's not set. + * + * Get their home directory from the password file. + * If we can't even find a password file entry for them, + * use "/tmp". + */ + pwd = getpwuid(getuid()); + if (pwd != NULL) { + homedir = pwd->pw_dir; + } else { + homedir = "/tmp"; + } + } + path = g_build_filename(homedir, + configuration_namespace == CONFIGURATION_NAMESPACE_WIRESHARK ? ".wireshark" : ".logray", + NULL); + if (g_file_test(path, G_FILE_TEST_IS_DIR)) { + g_free(xdg_path); + persconffile_dir = path; + return persconffile_dir; + } + + /* + * Neither are directories that exist; use the XDG path, so we'll + * create that as necessary. + */ + g_free(path); + persconffile_dir = xdg_path; + return persconffile_dir; +#endif +} + +void +set_persconffile_dir(const char *p) +{ + g_free(persconffile_dir); + persconffile_dir = g_strdup(p); +} + +char * +get_profiles_dir(void) +{ + return ws_strdup_printf ("%s%s%s", get_persconffile_dir_no_profile (), + G_DIR_SEPARATOR_S, PROFILES_DIR); +} + +int +create_profiles_dir(char **pf_dir_path_return) +{ + char *pf_dir_path; + ws_statb64 s_buf; + + /* + * Create the "Default" personal configuration files directory, if necessary. + */ + if (create_persconffile_profile (NULL, pf_dir_path_return) == -1) { + return -1; + } + + /* + * Check if profiles directory exists. + * If not then create it. + */ + pf_dir_path = get_profiles_dir (); + if (ws_stat64(pf_dir_path, &s_buf) != 0) { + if (errno != ENOENT) { + /* Some other problem; give up now. */ + *pf_dir_path_return = pf_dir_path; + return -1; + } + + /* + * It doesn't exist; try to create it. + */ + int ret = ws_mkdir(pf_dir_path, 0755); + if (ret == -1) { + *pf_dir_path_return = pf_dir_path; + return ret; + } + } + g_free(pf_dir_path); + + return 0; +} + +char * +get_global_profiles_dir(void) +{ + return ws_strdup_printf ("%s%s%s", get_datafile_dir(), + G_DIR_SEPARATOR_S, PROFILES_DIR); +} + +static char * +get_persconffile_dir(const char *profilename) +{ + char *persconffile_profile_dir = NULL, *profile_dir; + + if (profilename && strlen(profilename) > 0 && + strcmp(profilename, DEFAULT_PROFILE) != 0) { + profile_dir = get_profiles_dir(); + persconffile_profile_dir = ws_strdup_printf ("%s%s%s", profile_dir, + G_DIR_SEPARATOR_S, profilename); + g_free(profile_dir); + } else { + persconffile_profile_dir = g_strdup (get_persconffile_dir_no_profile ()); + } + + return persconffile_profile_dir; +} + +char * +get_profile_dir(const char *profilename, bool is_global) +{ + char *profile_dir; + + if (is_global) { + if (profilename && strlen(profilename) > 0 && + strcmp(profilename, DEFAULT_PROFILE) != 0) + { + char *global_path = get_global_profiles_dir(); + profile_dir = g_build_filename(global_path, profilename, NULL); + g_free(global_path); + } else { + profile_dir = g_strdup(get_datafile_dir()); + } + } else { + /* + * If we didn't supply a profile name, i.e. if profilename is + * null, get_persconffile_dir() returns the default profile. + */ + profile_dir = get_persconffile_dir(profilename); + } + + return profile_dir; +} + +bool +profile_exists(const char *profilename, bool global) +{ + char *path = NULL; + bool exists; + + /* + * If we're looking up a global profile, we must have a + * profile name. + */ + if (global && !profilename) + return false; + + path = get_profile_dir(profilename, global); + exists = (test_for_directory(path) == EISDIR) ? true : false; + + g_free(path); + return exists; +} + +static int +delete_directory (const char *directory, char **pf_dir_path_return) +{ + WS_DIR *dir; + WS_DIRENT *file; + char *filename; + int ret = 0; + + if ((dir = ws_dir_open(directory, 0, NULL)) != NULL) { + while ((file = ws_dir_read_name(dir)) != NULL) { + filename = ws_strdup_printf ("%s%s%s", directory, G_DIR_SEPARATOR_S, + ws_dir_get_name(file)); + if (test_for_directory(filename) != EISDIR) { + ret = ws_remove(filename); +#if 0 + } else { + /* The user has manually created a directory in the profile directory */ + /* I do not want to delete the directory recursively yet */ + ret = delete_directory (filename, pf_dir_path_return); +#endif + } + if (ret != 0) { + *pf_dir_path_return = filename; + break; + } + g_free (filename); + } + ws_dir_close(dir); + } + + if (ret == 0 && (ret = ws_remove(directory)) != 0) { + *pf_dir_path_return = g_strdup (directory); + } + + return ret; +} + +/* Copy files from one directory to another. Does not recursively copy directories */ +static int +copy_directory(const char *from_dir, const char *to_dir, char **pf_filename_return) +{ + int ret = 0; + char *from_file, *to_file; + const char *filename; + WS_DIR *dir; + WS_DIRENT *file; + + if ((dir = ws_dir_open(from_dir, 0, NULL)) != NULL) { + while ((file = ws_dir_read_name(dir)) != NULL) { + filename = ws_dir_get_name(file); + from_file = ws_strdup_printf ("%s%s%s", from_dir, G_DIR_SEPARATOR_S, filename); + if (test_for_directory(from_file) != EISDIR) { + to_file = ws_strdup_printf ("%s%s%s", to_dir, G_DIR_SEPARATOR_S, filename); + if (!copy_file_binary_mode(from_file, to_file)) { + *pf_filename_return = g_strdup(filename); + g_free (from_file); + g_free (to_file); + ret = -1; + break; + } + g_free (to_file); +#if 0 + } else { + /* The user has manually created a directory in the profile + * directory. Do not copy the directory recursively (yet?) + */ +#endif + } + g_free (from_file); + } + ws_dir_close(dir); + } + + return ret; +} + +static int +reset_default_profile(char **pf_dir_path_return) +{ + char *profile_dir = get_persconffile_dir(NULL); + char *filename, *del_file; + GList *files, *file; + int ret = 0; + + files = g_hash_table_get_keys(profile_files); + file = g_list_first(files); + while (file) { + filename = (char *)file->data; + del_file = ws_strdup_printf("%s%s%s", profile_dir, G_DIR_SEPARATOR_S, filename); + + if (file_exists(del_file)) { + ret = ws_remove(del_file); + if (ret != 0) { + *pf_dir_path_return = profile_dir; + g_free(del_file); + break; + } + } + + g_free(del_file); + file = g_list_next(file); + } + g_list_free(files); + + g_free(profile_dir); + return ret; +} + +int +delete_persconffile_profile(const char *profilename, char **pf_dir_path_return) +{ + if (strcmp(profilename, DEFAULT_PROFILE) == 0) { + return reset_default_profile(pf_dir_path_return); + } + + char *profile_dir = get_persconffile_dir(profilename); + int ret = 0; + + if (test_for_directory (profile_dir) == EISDIR) { + ret = delete_directory (profile_dir, pf_dir_path_return); + } + + g_free(profile_dir); + return ret; +} + +int +rename_persconffile_profile(const char *fromname, const char *toname, + char **pf_from_dir_path_return, char **pf_to_dir_path_return) +{ + char *from_dir = get_persconffile_dir(fromname); + char *to_dir = get_persconffile_dir(toname); + int ret = 0; + + ret = ws_rename (from_dir, to_dir); + if (ret != 0) { + *pf_from_dir_path_return = from_dir; + *pf_to_dir_path_return = to_dir; + return ret; + } + + g_free (from_dir); + g_free (to_dir); + + return 0; +} + +/* + * Create the directory that holds personal configuration files, if + * necessary. If we attempted to create it, and failed, return -1 and + * set "*pf_dir_path_return" to the pathname of the directory we failed + * to create (it's g_mallocated, so our caller should free it); otherwise, + * return 0. + */ +int +create_persconffile_profile(const char *profilename, char **pf_dir_path_return) +{ + char *pf_dir_path; +#ifdef _WIN32 + char *pf_dir_path_copy, *pf_dir_parent_path; + size_t pf_dir_parent_path_len; + int save_errno; +#endif + ws_statb64 s_buf; + int ret; + + if (profilename) { + /* + * Create the personal profiles directory, if necessary. + */ + if (create_profiles_dir(pf_dir_path_return) == -1) { + return -1; + } + } + + pf_dir_path = get_persconffile_dir(profilename); + if (ws_stat64(pf_dir_path, &s_buf) != 0) { + if (errno != ENOENT) { + /* Some other problem; give up now. */ + *pf_dir_path_return = pf_dir_path; + return -1; + } +#ifdef _WIN32 + /* + * Does the parent directory of that directory + * exist? %APPDATA% may not exist even though + * %USERPROFILE% does. + * + * We check for the existence of the directory + * by first checking whether the parent directory + * is just a drive letter and, if it's not, by + * doing a "stat()" on it. If it's a drive letter, + * or if the "stat()" succeeds, we assume it exists. + */ + pf_dir_path_copy = g_strdup(pf_dir_path); + pf_dir_parent_path = get_dirname(pf_dir_path_copy); + pf_dir_parent_path_len = strlen(pf_dir_parent_path); + if (pf_dir_parent_path_len > 0 + && pf_dir_parent_path[pf_dir_parent_path_len - 1] != ':' + && ws_stat64(pf_dir_parent_path, &s_buf) != 0) { + /* + * Not a drive letter and the stat() failed. + */ + if (errno != ENOENT) { + /* Some other problem; give up now. */ + *pf_dir_path_return = pf_dir_path; + save_errno = errno; + g_free(pf_dir_path_copy); + errno = save_errno; + return -1; + } + /* + * No, it doesn't exist - make it first. + */ + ret = ws_mkdir(pf_dir_parent_path, 0755); + if (ret == -1) { + *pf_dir_path_return = pf_dir_parent_path; + save_errno = errno; + g_free(pf_dir_path); + errno = save_errno; + return -1; + } + } + g_free(pf_dir_path_copy); + ret = ws_mkdir(pf_dir_path, 0755); +#else + ret = g_mkdir_with_parents(pf_dir_path, 0755); +#endif + } else { + /* + * Something with that pathname exists; if it's not + * a directory, we'll get an error if we try to put + * something in it, so we don't fail here, we wait + * for that attempt to fail. + */ + ret = 0; + } + if (ret == -1) + *pf_dir_path_return = pf_dir_path; + else + g_free(pf_dir_path); + + return ret; +} + +const GHashTable * +allowed_profile_filenames(void) +{ + return profile_files; +} + +int +create_persconffile_dir(char **pf_dir_path_return) +{ + return create_persconffile_profile(persconfprofile, pf_dir_path_return); +} + +int +copy_persconffile_profile(const char *toname, const char *fromname, bool from_global, + char **pf_filename_return, char **pf_to_dir_path_return, char **pf_from_dir_path_return) +{ + int ret = 0; + char *from_dir; + char *to_dir = get_persconffile_dir(toname); + char *from_file, *to_file; + const char *filename; + GHashTableIter files; + void * file; + + from_dir = get_profile_dir(fromname, from_global); + + if (!profile_files || do_store_persconffiles) { + /* Either the profile_files hashtable does not exist yet + * (this is very early in startup) or we are still adding + * files to it. Just copy all the non-directories. + */ + ret = copy_directory(from_dir, to_dir, pf_filename_return); + } else { + + g_hash_table_iter_init(&files, profile_files); + while (g_hash_table_iter_next(&files, &file, NULL)) { + filename = (const char *)file; + from_file = ws_strdup_printf ("%s%s%s", from_dir, G_DIR_SEPARATOR_S, filename); + to_file = ws_strdup_printf ("%s%s%s", to_dir, G_DIR_SEPARATOR_S, filename); + + if (file_exists(from_file) && !copy_file_binary_mode(from_file, to_file)) { + *pf_filename_return = g_strdup(filename); + g_free (from_file); + g_free (to_file); + ret = -1; + break; + } + + g_free (to_file); + g_free (from_file); + } + } + + if (ret != 0) { + *pf_to_dir_path_return = to_dir; + *pf_from_dir_path_return = from_dir; + } else { + g_free (to_dir); + g_free (from_dir); + } + + return ret; +} + +/* + * Get the (default) directory in which personal data is stored. + * + * On Win32, this is the "My Documents" folder in the personal profile. + * On UNIX this is simply the current directory, unless that's "/", + * which it will be, for example, when Wireshark is run from the + * Finder in macOS, in which case we use the user's home directory. + */ +/* XXX - should this and the get_home_dir() be merged? */ +extern const char * +get_persdatafile_dir(void) +{ + /* Return the cached value, if available */ + if (persdatafile_dir != NULL) + return persdatafile_dir; + +#ifdef _WIN32 + TCHAR tszPath[MAX_PATH]; + + /* + * Hint: SHGetFolderPath is not available on MSVC 6 - without + * Platform SDK + */ + if (SHGetSpecialFolderPath(NULL, tszPath, CSIDL_PERSONAL, false)) { + persdatafile_dir = g_utf16_to_utf8(tszPath, -1, NULL, NULL, NULL); + return persdatafile_dir; + } else { + return ""; + } +#else + /* + * Get the current directory. + */ + persdatafile_dir = g_get_current_dir(); + if (persdatafile_dir == NULL) { + /* XXX - can this fail? */ + /* + * g_get_home_dir() returns a const gchar *; g_strdup() it + * so that it's something that can be freed. + */ + persdatafile_dir = g_strdup(g_get_home_dir()); + } else if (strcmp(persdatafile_dir, "/") == 0) { + g_free(persdatafile_dir); + /* + * See above. + */ + persdatafile_dir = g_strdup(g_get_home_dir()); + } + return persdatafile_dir; +#endif +} + +void +set_persdatafile_dir(const char *p) +{ + g_free(persdatafile_dir); + persdatafile_dir = g_strdup(p); +} + +/* + * Construct the path name of a personal configuration file, given the + * file name. + * + * On Win32, if "for_writing" is false, we check whether the file exists + * and, if not, construct a path name relative to the ".wireshark" + * subdirectory of the user's home directory, and check whether that + * exists; if it does, we return that, so that configuration files + * from earlier versions can be read. + * + * The returned file name was g_malloc()'d so it must be g_free()d when the + * caller is done with it. + */ +char * +get_persconffile_path(const char *filename, bool from_profile) +{ + char *path, *dir = NULL; + + if (from_profile) { + /* Store filenames so we know which filenames belongs to a configuration profile */ + profile_register_persconffile(filename); + + dir = get_persconffile_dir(persconfprofile); + } else { + dir = get_persconffile_dir(NULL); + } + path = g_build_filename(dir, filename, NULL); + + g_free(dir); + return path; +} + +/* + * Construct the path name of a global configuration file, given the + * file name. + * + * The returned file name was g_malloc()'d so it must be g_free()d when the + * caller is done with it. + */ +char * +get_datafile_path(const char *filename) +{ + if (running_in_build_directory_flag && !strcmp(filename, "hosts")) { + /* We're running in the build directory and the requested file is a + * generated (or a test) file. Return the file name in the build + * directory (not in the source/data directory). + * (Oh the things we do to keep the source directory pristine...) + */ + return g_build_filename(get_progfile_dir(), filename, (char *)NULL); + } else { + return g_build_filename(get_datafile_dir(), filename, (char *)NULL); + } +} + +/* + * Construct the path name of a global documentation file, given the + * file name. + * + * The returned file name was g_malloc()'d so it must be g_free()d when the + * caller is done with it. + */ +char * +get_docfile_path(const char *filename) +{ + if (running_in_build_directory_flag) { + /* We're running in the build directory and the requested file is a + * generated (or a test) file. Return the file name in the build + * directory (not in the source/data directory). + * (Oh the things we do to keep the source directory pristine...) + */ + return g_build_filename(get_progfile_dir(), filename, (char *)NULL); + } else { + return g_build_filename(get_doc_dir(), filename, (char *)NULL); + } +} + +/* + * Return an error message for UNIX-style errno indications on open or + * create operations. + */ +const char * +file_open_error_message(int err, bool for_writing) +{ + const char *errmsg; + static char errmsg_errno[1024+1]; + + switch (err) { + + case ENOENT: + if (for_writing) + errmsg = "The path to the file \"%s\" doesn't exist."; + else + errmsg = "The file \"%s\" doesn't exist."; + break; + + case EACCES: + if (for_writing) + errmsg = "You don't have permission to create or write to the file \"%s\"."; + else + errmsg = "You don't have permission to read the file \"%s\"."; + break; + + case EISDIR: + errmsg = "\"%s\" is a directory (folder), not a file."; + break; + + case ENOSPC: + errmsg = "The file \"%s\" could not be created because there is no space left on the file system."; + break; + +#ifdef EDQUOT + case EDQUOT: + errmsg = "The file \"%s\" could not be created because you are too close to, or over, your disk quota."; + break; +#endif + + case EINVAL: + errmsg = "The file \"%s\" could not be created because an invalid filename was specified."; + break; + +#ifdef ENAMETOOLONG + case ENAMETOOLONG: + /* XXX Make sure we truncate on a character boundary. */ + errmsg = "The file name \"%.80s" UTF8_HORIZONTAL_ELLIPSIS "\" is too long."; + break; +#endif + + case ENOMEM: + /* + * The problem probably has nothing to do with how much RAM the + * user has on their machine, so don't confuse them by saying + * "memory". The problem is probably either virtual address + * space or swap space. + */ +#if GLIB_SIZEOF_VOID_P == 4 + /* + * ILP32; we probably ran out of virtual address space. + */ +#define ENOMEM_REASON "it can't be handled by a 32-bit application" +#else + /* + * LP64 or LLP64; we probably ran out of swap space. + */ +#if defined(_WIN32) + /* + * You need to make the pagefile bigger. + */ +#define ENOMEM_REASON "the pagefile is too small" +#elif defined(ENABLE_APPLICATION_BUNDLE) + /* + * dynamic_pager couldn't, or wouldn't, create more swap files. + */ +#define ENOMEM_REASON "your system ran out of swap file space" +#else + /* + * Either you have a fixed swap partition or a fixed swap file, + * and it needs to be made bigger. + * + * This is UN*X, but it's not macOS, so we assume the user is + * *somewhat* nerdy. + */ +#define ENOMEM_REASON "your system is out of swap space" +#endif +#endif /* GLIB_SIZEOF_VOID_P == 4 */ + if (for_writing) + errmsg = "The file \"%s\" could not be created because " ENOMEM_REASON "."; + else + errmsg = "The file \"%s\" could not be opened because " ENOMEM_REASON "."; + break; + + default: + snprintf(errmsg_errno, sizeof(errmsg_errno), + "The file \"%%s\" could not be %s: %s.", + for_writing ? "created" : "opened", + g_strerror(err)); + errmsg = errmsg_errno; + break; + } + return errmsg; +} + +/* + * Return an error message for UNIX-style errno indications on write + * operations. + */ +const char * +file_write_error_message(int err) +{ + const char *errmsg; + static char errmsg_errno[1024+1]; + + switch (err) { + + case ENOSPC: + errmsg = "The file \"%s\" could not be saved because there is no space left on the file system."; + break; + +#ifdef EDQUOT + case EDQUOT: + errmsg = "The file \"%s\" could not be saved because you are too close to, or over, your disk quota."; + break; +#endif + + default: + snprintf(errmsg_errno, sizeof(errmsg_errno), + "An error occurred while writing to the file \"%%s\": %s.", + g_strerror(err)); + errmsg = errmsg_errno; + break; + } + return errmsg; +} + + +bool +file_exists(const char *fname) +{ + ws_statb64 file_stat; + + if (!fname) { + return false; + } + + if (ws_stat64(fname, &file_stat) != 0 && errno == ENOENT) { + return false; + } else { + return true; + } +} + +bool config_file_exists_with_entries(const char *fname, char comment_char) +{ + bool start_of_line = true; + bool has_entries = false; + FILE *file; + int c; + + if (!fname) { + return false; + } + + if ((file = ws_fopen(fname, "r")) == NULL) { + return false; + } + + do { + c = ws_getc_unlocked(file); + if (start_of_line && c != comment_char && !g_ascii_isspace(c) && g_ascii_isprint(c)) { + has_entries = true; + break; + } + if (c == '\n' || !g_ascii_isspace(c)) { + start_of_line = (c == '\n'); + } + } while (c != EOF); + + fclose(file); + return has_entries; +} + +/* + * Check that the from file is not the same as to file + * We do it here so we catch all cases ... + * Unfortunately, the file requester gives us an absolute file + * name and the read file name may be relative (if supplied on + * the command line), so we can't just compare paths. From Joerg Mayer. + */ +bool +files_identical(const char *fname1, const char *fname2) +{ + /* Two different implementations, because: + * + * - _fullpath is not available on UN*X, so we can't get full + * paths and compare them (which wouldn't work with hard links + * in any case); + * + * - st_ino isn't filled in with a meaningful value on Windows. + */ +#ifdef _WIN32 + char full1[MAX_PATH], full2[MAX_PATH]; + + /* + * Get the absolute full paths of the file and compare them. + * That won't work if you have hard links, but those aren't + * much used on Windows, even though NTFS supports them. + * + * XXX - will _fullpath work with UNC? + */ + if( _fullpath( full1, fname1, MAX_PATH ) == NULL ) { + return false; + } + + if( _fullpath( full2, fname2, MAX_PATH ) == NULL ) { + return false; + } + + if(strcmp(full1, full2) == 0) { + return true; + } else { + return false; + } +#else + ws_statb64 filestat1, filestat2; + + /* + * Compare st_dev and st_ino. + */ + if (ws_stat64(fname1, &filestat1) == -1) + return false; /* can't get info about the first file */ + if (ws_stat64(fname2, &filestat2) == -1) + return false; /* can't get info about the second file */ + return (filestat1.st_dev == filestat2.st_dev && + filestat1.st_ino == filestat2.st_ino); +#endif +} + +bool +file_needs_reopen(int fd, const char* filename) +{ +#ifdef _WIN32 + /* Windows handles st_dev in a way unsuitable here: + * * _fstat() simply casts the file descriptor (ws_fileno(fp)) to unsigned + * and assigns this value to st_dev and st_rdev + * * _wstat() converts drive letter (eg. C) to number (A=0, B=1, C=2, ...) + * and assigns such number to st_dev and st_rdev + * + * The st_ino parameter is simply zero as there is no specific assignment + * to it in the Universal CRT source code. + * + * Thus instead of using fstat(), use Windows specific API. + */ + + HANDLE open_handle = (HANDLE)_get_osfhandle(fd); + HANDLE current_handle = CreateFile(utf_8to16(filename), FILE_READ_ATTRIBUTES, + FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, NULL); + BY_HANDLE_FILE_INFORMATION open_info, current_info; + + if (current_handle == INVALID_HANDLE_VALUE) { + return true; + } + +#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8) + FILE_ID_INFO open_id, current_id; + if (GetFileInformationByHandleEx(open_handle, FileIdInfo, &open_id, sizeof(open_id)) && + GetFileInformationByHandleEx(current_handle, FileIdInfo, ¤t_id, sizeof(current_id))) { + /* 128-bit identifier is available, use it */ + CloseHandle(current_handle); + return open_id.VolumeSerialNumber != current_id.VolumeSerialNumber || + memcmp(&open_id.FileId, ¤t_id.FileId, sizeof(open_id.FileId)) != 0; + } +#endif /* _WIN32_WINNT >= _WIN32_WINNT_WIN8 */ + if (GetFileInformationByHandle(open_handle, &open_info) && + GetFileInformationByHandle(current_handle, ¤t_info)) { + /* Fallback to 64-bit identifier */ + CloseHandle(current_handle); + uint64_t open_size = (((uint64_t)open_info.nFileSizeHigh) << 32) | open_info.nFileSizeLow; + uint64_t current_size = (((uint64_t)current_info.nFileSizeHigh) << 32) | current_info.nFileSizeLow; + return open_info.dwVolumeSerialNumber != current_info.dwVolumeSerialNumber || + open_info.nFileIndexHigh != current_info.nFileIndexHigh || + open_info.nFileIndexLow != current_info.nFileIndexLow || + open_size > current_size; + } + CloseHandle(current_handle); + return true; +#else + ws_statb64 open_stat, current_stat; + + /* consider a file deleted when stat fails for either file, + * or when the residing device / inode has changed. */ + if (0 != ws_fstat64(fd, &open_stat)) + return true; + if (0 != ws_stat64(filename, ¤t_stat)) + return true; + + return open_stat.st_dev != current_stat.st_dev || + open_stat.st_ino != current_stat.st_ino || + open_stat.st_size > current_stat.st_size; +#endif +} + +bool +write_file_binary_mode(const char *filename, const void *content, size_t content_len) +{ + int fd; + size_t bytes_left; + unsigned int bytes_to_write; + ssize_t bytes_written; + const uint8_t *ptr; + int err; + + fd = ws_open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644); + if (fd == -1) { + report_open_failure(filename, errno, true); + return false; + } + + /* + * The third argument to _write() on Windows is an unsigned int, + * so, on Windows, that's the size of the third argument to + * ws_write(). + * + * The third argument to write() on UN*X is a size_t, although + * the return value is an ssize_t, so one probably shouldn't + * write more than the max value of an ssize_t. + * + * In either case, there's no guarantee that a size_t such as + * content_len can be passed to ws_write(), so we write in + * chunks of at most 2^31 bytes. + */ + + ptr = (const uint8_t *)content; + bytes_left = content_len; + while (bytes_left != 0) { + if (bytes_left > 0x40000000) { + bytes_to_write = 0x40000000; + } else { + bytes_to_write = (unsigned int)bytes_left; + } + bytes_written = ws_write(fd, ptr, bytes_to_write); + if (bytes_written <= 0) { + if (bytes_written < 0) { + err = errno; + } else { + err = WTAP_ERR_SHORT_WRITE; + } + report_write_failure(filename, err); + ws_close(fd); + return false; + } + bytes_left -= bytes_written; + ptr += bytes_written; + } + + ws_close(fd); + return true; +} + +/* + * Copy a file in binary mode, for those operating systems that care about + * such things. This should be OK for all files, even text files, as + * we'll copy the raw bytes, and we don't look at the bytes as we copy + * them. + * + * Returns true on success, false on failure. If a failure, it also + * displays a simple dialog window with the error message. + */ +bool +copy_file_binary_mode(const char *from_filename, const char *to_filename) +{ + int from_fd, to_fd, err; + ws_file_ssize_t nread, nwritten; + uint8_t *pd = NULL; + + /* Copy the raw bytes of the file. */ + from_fd = ws_open(from_filename, O_RDONLY | O_BINARY, 0000 /* no creation so don't matter */); + if (from_fd < 0) { + report_open_failure(from_filename, errno, false); + goto done; + } + + /* Use open() instead of creat() so that we can pass the O_BINARY + flag, which is relevant on Win32; it appears that "creat()" + may open the file in text mode, not binary mode, but we want + to copy the raw bytes of the file, so we need the output file + to be open in binary mode. */ + to_fd = ws_open(to_filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644); + if (to_fd < 0) { + report_open_failure(to_filename, errno, true); + ws_close(from_fd); + goto done; + } + +#define FS_READ_SIZE 65536 + pd = (uint8_t *)g_malloc(FS_READ_SIZE); + while ((nread = ws_read(from_fd, pd, FS_READ_SIZE)) > 0) { + nwritten = ws_write(to_fd, pd, nread); + if (nwritten < nread) { + if (nwritten < 0) + err = errno; + else + err = WTAP_ERR_SHORT_WRITE; + report_write_failure(to_filename, err); + ws_close(from_fd); + ws_close(to_fd); + goto done; + } + } + if (nread < 0) { + err = errno; + report_read_failure(from_filename, err); + ws_close(from_fd); + ws_close(to_fd); + goto done; + } + ws_close(from_fd); + if (ws_close(to_fd) < 0) { + report_write_failure(to_filename, errno); + goto done; + } + + g_free(pd); + pd = NULL; + return true; + +done: + g_free(pd); + return false; +} + +char * +data_file_url(const char *filename) +{ + char *file_path; + char *uri; + + /* Absolute path? */ + if(g_path_is_absolute(filename)) { + file_path = g_strdup(filename); + } else { + file_path = ws_strdup_printf("%s/%s", get_datafile_dir(), filename); + } + + /* XXX - check, if the file is really existing, otherwise display a simple_dialog about the problem */ + + /* convert filename to uri */ + uri = g_filename_to_uri(file_path, NULL, NULL); + g_free(file_path); + return uri; +} + +char * +doc_file_url(const char *filename) +{ + char *file_path; + char *uri; + + /* Absolute path? */ + if(g_path_is_absolute(filename)) { + file_path = g_strdup(filename); + } else { + file_path = ws_strdup_printf("%s/%s", get_doc_dir(), filename); + } + + /* XXX - check, if the file is really existing, otherwise display a simple_dialog about the problem */ + + /* convert filename to uri */ + uri = g_filename_to_uri(file_path, NULL, NULL); + g_free(file_path); + return uri; +} + +void +free_progdirs(void) +{ + g_free(persconffile_dir); + persconffile_dir = NULL; + g_free(datafile_dir); + datafile_dir = NULL; + g_free(persdatafile_dir); + persdatafile_dir = NULL; + g_free(persconfprofile); + persconfprofile = NULL; + g_free(progfile_dir); + progfile_dir = NULL; + g_free(doc_dir); + doc_dir = NULL; + g_free(install_prefix); + install_prefix = NULL; +#if defined(HAVE_PLUGINS) || defined(HAVE_LUA) + g_free(plugin_dir); + plugin_dir = NULL; + g_free(plugin_dir_with_version); + plugin_dir_with_version = NULL; + g_free(plugin_pers_dir); + plugin_pers_dir = NULL; + g_free(plugin_pers_dir_with_version); + plugin_pers_dir_with_version = NULL; +#endif + g_free(extcap_dir); + extcap_dir = NULL; + g_free(extcap_pers_dir); + extcap_pers_dir = NULL; +} + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/filesystem.h b/wsutil/filesystem.h new file mode 100644 index 00000000..fd3e3ebc --- /dev/null +++ b/wsutil/filesystem.h @@ -0,0 +1,426 @@ +/** @file + * Filesystem utility definitions + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef FILESYSTEM_H +#define FILESYSTEM_H + +#include <wireshark.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * Default profile name. + */ +#define DEFAULT_PROFILE "Default" + +/** + * Initialize our configuration environment. + * + * Get the pathname of the directory from which the executable came, + * and save it for future use. + * + * Set our configuration namespace, which determines the top-level + * configuration directory name and environment variable prefixes. + * Default is "Wireshark". + * + * @param arg0 Executable name hint. Should be argv[0]. + * @param namespace_name The namespace to use. "Wireshark" or NULL uses + * the Wireshark namespace. "Logray" uses the Logray namespace. + * @return NULL on success, and a g_mallocated string containing an error on failure. + */ +WS_DLL_PUBLIC char *configuration_init(const char *arg0, const char *namespace_name); + +/** + * Get the configuration namespace name. + * @return The namespace name. One of "Wireshark" or "Logray". + */ +WS_DLL_PUBLIC const char *get_configuration_namespace(void); + +/** + * Check to see if the configuration namespace is for packet analysis + * (Wireshark) or log analysis (Logray). + * @return true if the configuration namespace is for packets. + */ +WS_DLL_PUBLIC bool is_packet_configuration_namespace(void); + +/* + * Get the directory in which the main (Wireshark, TShark, Logray, etc) + * program resides. + * Extcaps should use get_extcap_dir() to get their path. + * + * @return The main program file directory. + */ +WS_DLL_PUBLIC const char *get_progfile_dir(void); + +/* + * Construct the path name of a non-extcap Wireshark executable file, + * given the program name. The executable name doesn't include ".exe"; + * append it on Windows, so that callers don't have to worry about that. + * + * This presumes that all non-extcap executables are in the same directory. + * + * The returned file name was g_malloc()'d so it must be g_free()d when the + * caller is done with it. + */ +WS_DLL_PUBLIC char *get_executable_path(const char *filename); + +/* + * Get the directory in which plugins are stored; this must not be called + * before configuration_init() is called, as they might be stored in a + * subdirectory of the program file directory. + */ +WS_DLL_PUBLIC const char *get_plugins_dir(void); + +/* + * Append VERSION_MAJOR.VERSION_MINOR to the plugin dir. + */ +WS_DLL_PUBLIC const char *get_plugins_dir_with_version(void); + +/* + * Get the personal plugin dir. + */ +WS_DLL_PUBLIC const char *get_plugins_pers_dir(void); + +/* + * Append VERSION_MAJOR.VERSION_MINOR to the plugin personal dir. + */ +WS_DLL_PUBLIC const char *get_plugins_pers_dir_with_version(void); + +/* + * Get the directory in which extcap hooks are stored; this must not be called + * before configuration_init() is called, as they might be stored in a + * subdirectory of the program file directory. + */ +WS_DLL_PUBLIC const char *get_extcap_dir(void); + +/* + * Get the personal extcap dir. + */ +WS_DLL_PUBLIC const char *get_extcap_pers_dir(void); + +/* + * Get the flag indicating whether we're running from a build + * directory. + */ +WS_DLL_PUBLIC bool running_in_build_directory(void); + +/* + * Get the directory in which global configuration files are + * stored. + */ +WS_DLL_PUBLIC const char *get_datafile_dir(void); + +/* + * Construct the path name of a global configuration file, given the + * file name. + * + * The returned file name was g_malloc()'d so it must be g_free()d when the + * caller is done with it. + */ +WS_DLL_PUBLIC char *get_datafile_path(const char *filename); + +/* + * Get the directory in which global documentation files are + * stored. + */ +WS_DLL_PUBLIC const char *get_doc_dir(void); + +/* + * Construct the path name of a global documentation file, given the + * file name. + * + * The returned file name was g_malloc()'d so it must be g_free()d when the + * caller is done with it. + */ +WS_DLL_PUBLIC char *get_docfile_path(const char *filename); + +/* + * Construct the path URL of a global documentation file, given the + * file name. + * + * The returned file name was g_malloc()'d so it must be g_free()d when the + * caller is done with it. + */ +WS_DLL_PUBLIC char *doc_file_url(const char *filename); + +/* + * Get the directory in which files that, at least on UNIX, are + * system files (such as "/etc/ethers") are stored; on Windows, + * there's no "/etc" directory, so we get them from the Wireshark + * global configuration and data file directory. + */ +WS_DLL_PUBLIC const char *get_systemfile_dir(void); + +/* + * Set the configuration profile name to be used for storing + * personal configuration files. + */ +WS_DLL_PUBLIC void set_profile_name(const char *profilename); + +/* + * Get the current configuration profile name used for storing + * personal configuration files. + */ +WS_DLL_PUBLIC const char *get_profile_name(void); + +/* + * Check if current profile is default profile. + */ +WS_DLL_PUBLIC bool is_default_profile(void); + +/* + * Check if we have global profiles. + */ +WS_DLL_PUBLIC bool has_global_profiles(void); + +/* + * Get the directory used to store configuration profile directories. + * Caller must free the returned string + */ +WS_DLL_PUBLIC char *get_profiles_dir(void); + +/* + * Get the directory used to store configuration files for a given profile. + * Caller must free the returned string. + */ +WS_DLL_PUBLIC char *get_profile_dir(const char *profilename, bool is_global); + +/* + * Create the directory used to store configuration profile directories. + */ +WS_DLL_PUBLIC int create_profiles_dir(char **pf_dir_path_return); + +/* + * Get the directory used to store global configuration profile directories. + * Caller must free the returned string + */ +WS_DLL_PUBLIC char *get_global_profiles_dir(void); + + +/* + * Store filenames used for personal config files so we know which + * files to copy when duplicate a configuration profile. + */ +WS_DLL_PUBLIC void profile_store_persconffiles(bool store); + +/* + * Register a filename to the personal config files storage. + * This is for files which are not read using get_persconffile_path() during startup. + */ +WS_DLL_PUBLIC void profile_register_persconffile(const char *filename); + +/* + * Check if given configuration profile exists. + */ +WS_DLL_PUBLIC bool profile_exists(const char *profilename, bool global); + +/* + * Create a directory for the given configuration profile. + * If we attempted to create it, and failed, return -1 and + * set "*pf_dir_path_return" to the pathname of the directory we failed + * to create (it's g_mallocated, so our caller should free it); otherwise, + * return 0. + */ +WS_DLL_PUBLIC int create_persconffile_profile(const char *profilename, + char **pf_dir_path_return); + +/* + * Returns the list of known profile config filenames + */ +WS_DLL_PUBLIC const GHashTable * allowed_profile_filenames(void); + +/* + * Delete the directory for the given configuration profile. + * If we attempted to delete it, and failed, return -1 and + * set "*pf_dir_path_return" to the pathname of the directory we failed + * to delete (it's g_mallocated, so our caller should free it); otherwise, + * return 0. + */ +WS_DLL_PUBLIC int delete_persconffile_profile(const char *profilename, + char **pf_dir_path_return); + +/* + * Rename the directory for the given confinguration profile. + */ +WS_DLL_PUBLIC int rename_persconffile_profile(const char *fromname, const char *toname, + char **pf_from_dir_path_return, + char **pf_to_dir_path_return); + +/* + * Copy files in one profile to the other. + */ +WS_DLL_PUBLIC int copy_persconffile_profile(const char *toname, const char *fromname, + bool from_global, + char **pf_filename_return, + char **pf_to_dir_path_return, + char **pf_from_dir_path_return); + +/* + * Create the directory that holds personal configuration files, if + * necessary. If we attempted to create it, and failed, return -1 and + * set "*pf_dir_path_return" to the pathname of the directory we failed + * to create (it's g_mallocated, so our caller should free it); otherwise, + * return 0. + */ +WS_DLL_PUBLIC int create_persconffile_dir(char **pf_dir_path_return); + +/* + * Construct the path name of a personal configuration file, given the + * file name. If using configuration profiles this directory will be + * used if "from_profile" is true. + * + * The returned file name was g_malloc()'d so it must be g_free()d when the + * caller is done with it. + */ +WS_DLL_PUBLIC char *get_persconffile_path(const char *filename, bool from_profile); + +/* + * Set the path of the personal configuration file directory. + */ +WS_DLL_PUBLIC void set_persconffile_dir(const char *p); + +/* + * Get the (default) directory in which personal data is stored. + * + * On Win32, this is the "My Documents" folder in the personal profile. + * On UNIX this is simply the current directory. + */ +WS_DLL_PUBLIC const char *get_persdatafile_dir(void); + +/* + * Set the path of the directory in which personal data is stored. + */ +WS_DLL_PUBLIC void set_persdatafile_dir(const char *p); + +/* + * Return an error message for UNIX-style errno indications on open or + * create operations. + */ +WS_DLL_PUBLIC const char *file_open_error_message(int err, bool for_writing); + +/* + * Return an error message for UNIX-style errno indications on write + * operations. + */ +WS_DLL_PUBLIC const char *file_write_error_message(int err); + +/* + * Given a pathname, return the last component. + */ +WS_DLL_PUBLIC const char *get_basename(const char *); + + /* + * Given a pathname, return a pointer to the last pathname separator + * character in the pathname, or NULL if the pathname contains no + * separators. + */ +WS_DLL_PUBLIC char *find_last_pathname_separator(const char *path); + +/* + * Given a pathname, return a string containing everything but the + * last component. NOTE: this overwrites the pathname handed into + * it.... + */ +WS_DLL_PUBLIC char *get_dirname(char *); + +/* + * Given a pathname, return: + * + * the errno, if an attempt to "stat()" the file fails; + * + * EISDIR, if the attempt succeeded and the file turned out + * to be a directory; + * + * 0, if the attempt succeeded and the file turned out not + * to be a directory. + */ +WS_DLL_PUBLIC int test_for_directory(const char *); + +/* + * Given a pathname, return: + * + * the errno, if an attempt to "stat()" the file fails; + * + * ESPIPE, if the attempt succeeded and the file turned out + * to be a FIFO; + * + * 0, if the attempt succeeded and the file turned out not + * to be a FIFO. + */ +WS_DLL_PUBLIC int test_for_fifo(const char *); + +/* + * Check, if file is existing. + */ +WS_DLL_PUBLIC bool file_exists(const char *fname); + +/* + * Check if file is existing and has text entries which does not start + * with the comment character. + */ +WS_DLL_PUBLIC bool config_file_exists_with_entries(const char *fname, char comment_char); + +/* + * Check if two filenames are identical (with absolute and relative paths). + */ +WS_DLL_PUBLIC bool files_identical(const char *fname1, const char *fname2); + +/* + * Check if file has been recreated since it was opened. + */ +WS_DLL_PUBLIC bool file_needs_reopen(int fd, const char* filename); + +/* + * Write content to a file in binary mode, for those operating systems that + * care about such things. This should be OK for all files, even text files, as + * we'll write the raw bytes, and we don't look at the bytes as we copy them. + * + * Returns true on success, false on failure. If a failure, it also + * displays a simple dialog window with the error message. + */ +WS_DLL_PUBLIC bool write_file_binary_mode(const char *filename, + const void *content, size_t content_len); + +/* + * Copy a file in binary mode, for those operating systems that care about + * such things. This should be OK for all files, even text files, as + * we'll copy the raw bytes, and we don't look at the bytes as we copy + * them. + * + * Returns true on success, false on failure. If a failure, it also + * displays a simple dialog window with the error message. + */ +WS_DLL_PUBLIC bool copy_file_binary_mode(const char *from_filename, + const char *to_filename); + + +/* + * Given a filename return a filesystem URL. Relative paths are prefixed with + * the datafile directory path. + * + * @param filename A file name or path. Relative paths will be prefixed with + * the data file directory path. + * @return A filesystem URL for the file or NULL on failure. A non-NULL return + * value must be freed with g_free(). + */ +WS_DLL_PUBLIC char* data_file_url(const char *filename); + +/* + * Free the internal structtures + */ +WS_DLL_PUBLIC void free_progdirs(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* FILESYSTEM_H */ diff --git a/wsutil/filter_files.c b/wsutil/filter_files.c new file mode 100644 index 00000000..48464892 --- /dev/null +++ b/wsutil/filter_files.c @@ -0,0 +1,564 @@ +/* filter_files.c + * Code for reading and writing the filters file. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <config.h> +#define WS_LOG_DOMAIN LOG_DOMAIN_WSUTIL +#include "filter_files.h" + +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#include <wsutil/file_util.h> +#include <wsutil/filesystem.h> +#include <wsutil/report_message.h> +#include <wsutil/wslog.h> +#include <wsutil/ws_assert.h> + +/* + * List of capture filters - saved. + */ +static GList *capture_filters = NULL; + +/* + * List of display filters - saved. + */ +static GList *display_filters = NULL; + +/* + * Read in a list of filters. + * + * On error, report the error via the UI. + */ + +#define INIT_BUF_SIZE 128 + +static GList * +add_filter_entry(GList *fl, const char *filt_name, const char *filt_expr) +{ + filter_def *filt; + + filt = g_new(filter_def, 1); + filt->name = g_strdup(filt_name); + filt->strval = g_strdup(filt_expr); + return g_list_prepend(fl, filt); +} + +static void +free_filter_entry(void * data) +{ + filter_def *filt = (filter_def*)data; + g_free(filt->name); + g_free(filt->strval); + g_free(filt); +} + +void free_filter_lists(void) +{ + if (capture_filters) { + g_list_free_full(capture_filters, free_filter_entry); + capture_filters = NULL; + } + if (display_filters) { + g_list_free_full(display_filters, free_filter_entry); + display_filters = NULL; + } +} + +static GList * +remove_filter_entry(GList *fl, GList *fl_entry) +{ + filter_def *filt; + + filt = (filter_def *) fl_entry->data; + g_free(filt->name); + g_free(filt->strval); + g_free(filt); + return g_list_remove_link(fl, fl_entry); +} + +static int +skip_whitespace(FILE *ff) +{ + int c; + + while ((c = getc(ff)) != EOF && c != '\n' && g_ascii_isspace(c)) + ; + return c; +} + +static int +getc_crlf(FILE *ff) +{ + int c; + + c = getc(ff); + if (c == '\r') { + /* Treat CR-LF at the end of a line like LF, so that if we're reading + * a Windows-format file on UN*X, we handle it the same way we'd handle + * a UN*X-format file. */ + c = getc(ff); + if (c != EOF && c != '\n') { + /* Put back the character after the CR, and process the CR normally. */ + ungetc(c, ff); + c = '\r'; + } + } + return c; +} + +void +read_filter_list(filter_list_type_t list_type) +{ + const char *ff_name, *ff_description; + char *ff_path; + FILE *ff; + GList **flpp; + int c; + char *filt_name, *filt_expr; + int filt_name_len, filt_expr_len; + int filt_name_index, filt_expr_index; + int line = 1; + + switch (list_type) { + + case CFILTER_LIST: + ff_name = CFILTER_FILE_NAME; + ff_description = "capture"; + flpp = &capture_filters; + break; + + case DFILTER_LIST: + ff_name = DFILTER_FILE_NAME; + ff_description = "display"; + flpp = &display_filters; + break; + + default: + ws_assert_not_reached(); + return; + } + + /* try to open personal "cfilters"/"dfilters" file */ + ff_path = get_persconffile_path(ff_name, true); + if ((ff = ws_fopen(ff_path, "r")) == NULL) { + /* + * Did that fail because the file didn't exist? + */ + if (errno != ENOENT) { + /* + * No. Just give up. + */ + report_warning("Could not open your %s filter file\n\"%s\": %s.", + ff_description, ff_path, g_strerror(errno)); + g_free(ff_path); + return; + } + + /* + * Yes. See if there's an "old style" personal "filters" file; if so, read it. + * This means that a user will start out with their capture and + * display filter lists being identical; each list may contain + * filters that don't belong in that list. The user can edit + * the filter lists, and delete the ones that don't belong in + * a particular list. + */ + g_free(ff_path); + ff_path = get_persconffile_path(FILTER_FILE_NAME, false); + if ((ff = ws_fopen(ff_path, "r")) == NULL) { + /* + * Did that fail because the file didn't exist? + */ + if (errno != ENOENT) { + /* + * No. Just give up. + */ + report_warning("Could not open your %s filter file\n\"%s\": %s.", + ff_description, ff_path, g_strerror(errno)); + g_free(ff_path); + return; + } + + /* + * Try to open the global "cfilters/dfilters" file. + */ + g_free(ff_path); + ff_path = get_datafile_path(ff_name); + if ((ff = ws_fopen(ff_path, "r")) == NULL) { + /* + * Well, that didn't work, either. Just give up. + * Report an error if the file existed but we couldn't open it. + */ + if (errno != ENOENT) { + report_warning("Could not open your %s filter file\n\"%s\": %s.", + ff_description, ff_path, g_strerror(errno)); + } + g_free(ff_path); + return; + } + } + } + + /* If we already have a list of filters, discard it. */ + /* this should never happen - this function is called only once for each list! */ + while(*flpp) { + *flpp = remove_filter_entry(*flpp, g_list_first(*flpp)); + } + + /* Allocate the filter name buffer. */ + filt_name_len = INIT_BUF_SIZE; + filt_name = (char *)g_malloc(filt_name_len + 1); + filt_expr_len = INIT_BUF_SIZE; + filt_expr = (char *)g_malloc(filt_expr_len + 1); + + for (line = 1; ; line++) { + /* Lines in a filter file are of the form + + "name" expression + + where "name" is a name, in quotes - backslashes in the name + escape the next character, so quotes and backslashes can appear + in the name - and "expression" is a filter expression, not in + quotes, running to the end of the line. */ + + /* Skip over leading white space, if any. */ + c = skip_whitespace(ff); + + if (c == EOF) + break; /* Nothing more to read */ + if (c == '\n') + continue; /* Blank line. */ + + /* "c" is the first non-white-space character. + If it's not a quote, it's an error. */ + if (c != '"') { + ws_warning("'%s' line %d doesn't have a quoted filter name.", ff_path, + line); + while (c != '\n') + c = getc(ff); /* skip to the end of the line */ + continue; + } + + /* Get the name of the filter. */ + filt_name_index = 0; + for (;;) { + c = getc_crlf(ff); + if (c == EOF || c == '\n') + break; /* End of line - or end of file */ + if (c == '"') { + /* Closing quote. */ + if (filt_name_index >= filt_name_len) { + /* Filter name buffer isn't long enough; double its length. */ + filt_name_len *= 2; + filt_name = (char *)g_realloc(filt_name, filt_name_len + 1); + } + filt_name[filt_name_index] = '\0'; + break; + } + if (c == '\\') { + /* Next character is escaped */ + c = getc_crlf(ff); + if (c == EOF || c == '\n') + break; /* End of line - or end of file */ + } + /* Add this character to the filter name string. */ + if (filt_name_index >= filt_name_len) { + /* Filter name buffer isn't long enough; double its length. */ + filt_name_len *= 2; + filt_name = (char *)g_realloc(filt_name, filt_name_len + 1); + } + filt_name[filt_name_index] = c; + filt_name_index++; + } + + if (c == EOF) { + if (!ferror(ff)) { + /* EOF, not error; no newline seen before EOF */ + ws_warning("'%s' line %d doesn't have a newline.", ff_path, + line); + } + break; /* nothing more to read */ + } + + if (c != '"') { + /* No newline seen before end-of-line */ + ws_warning("'%s' line %d doesn't have a closing quote.", ff_path, + line); + continue; + } + + /* Skip over separating white space, if any. */ + c = skip_whitespace(ff); + + if (c == EOF) { + if (!ferror(ff)) { + /* EOF, not error; no newline seen before EOF */ + ws_warning("'%s' line %d doesn't have a newline.", ff_path, + line); + } + break; /* nothing more to read */ + } + + if (c == '\n') { + /* No filter expression */ + ws_warning("'%s' line %d doesn't have a filter expression.", ff_path, + line); + continue; + } + + /* "c" is the first non-white-space character; it's the first + character of the filter expression. */ + filt_expr_index = 0; + for (;;) { + /* Add this character to the filter expression string. */ + if (filt_expr_index >= filt_expr_len) { + /* Filter expressioin buffer isn't long enough; double its length. */ + filt_expr_len *= 2; + filt_expr = (char *)g_realloc(filt_expr, filt_expr_len + 1); + } + filt_expr[filt_expr_index] = c; + filt_expr_index++; + + /* Get the next character. */ + c = getc_crlf(ff); + if (c == EOF || c == '\n') + break; + } + + if (c == EOF) { + if (!ferror(ff)) { + /* EOF, not error; no newline seen before EOF */ + ws_warning("'%s' line %d doesn't have a newline.", ff_path, + line); + } + break; /* nothing more to read */ + } + + /* We saw the ending newline; terminate the filter expression string */ + if (filt_expr_index >= filt_expr_len) { + /* Filter expressioin buffer isn't long enough; double its length. */ + filt_expr_len *= 2; + filt_expr = (char *)g_realloc(filt_expr, filt_expr_len + 1); + } + filt_expr[filt_expr_index] = '\0'; + + /* Add the new filter to the list of filters */ + *flpp = add_filter_entry(*flpp, filt_name, filt_expr); + } + if (ferror(ff)) { + report_warning("Error reading your %s filter file\n\"%s\": %s.", + ff_description, ff_path, g_strerror(errno)); + } + g_free(ff_path); + fclose(ff); + g_free(filt_name); + g_free(filt_expr); +} + +/* + * Get a pointer to a list of filters. + */ +static GList ** +get_filter_list(filter_list_type_t list_type) +{ + GList **flpp; + + switch (list_type) { + + case CFILTER_LIST: + flpp = &capture_filters; + break; + + case DFILTER_LIST: + flpp = &display_filters; + break; + + default: + ws_assert_not_reached(); + flpp = NULL; + } + return flpp; +} + +/* + * Get a pointer to the first entry in a filter list. + */ +GList * +get_filter_list_first(filter_list_type_t list_type) +{ + GList **flpp; + + flpp = get_filter_list(list_type); + return g_list_first(*flpp); +} + +/* + * Add a new filter to the end of a list. + * Returns a pointer to the newly-added entry. + */ +GList * +add_to_filter_list(filter_list_type_t list_type, const char *name, + const char *expression) +{ + GList **flpp; + + flpp = get_filter_list(list_type); + *flpp = add_filter_entry(*flpp, name, expression); + + return g_list_last(*flpp); +} + +/* + * Remove a filter from a list. + */ +void +remove_from_filter_list(filter_list_type_t list_type, GList *fl_entry) +{ + GList **flpp; + + flpp = get_filter_list(list_type); + *flpp = remove_filter_entry(*flpp, fl_entry); +} + +/* + * Write out a list of filters. + * + * On error, report the error via the UI. + */ +void +save_filter_list(filter_list_type_t list_type) +{ + char *pf_dir_path; + const char *ff_name, *ff_description; + char *ff_path, *ff_path_new; + GList *fl; + GList *flpp; + filter_def *filt; + FILE *ff; + unsigned char *p, c; + + switch (list_type) { + + case CFILTER_LIST: + ff_name = CFILTER_FILE_NAME; + ff_description = "capture"; + fl = capture_filters; + break; + + case DFILTER_LIST: + ff_name = DFILTER_FILE_NAME; + ff_description = "display"; + fl = display_filters; + break; + + default: + ws_assert_not_reached(); + return; + } + + /* Create the directory that holds personal configuration files, + if necessary. */ + if (create_persconffile_dir(&pf_dir_path) == -1) { + report_failure("Can't create directory\n\"%s\"\nfor filter files: %s.", + pf_dir_path, g_strerror(errno)); + g_free(pf_dir_path); + return; + } + + ff_path = get_persconffile_path(ff_name, true); + + /* Write to "XXX.new", and rename if that succeeds. + That means we don't trash the file if we fail to write it out + completely. */ + ff_path_new = ws_strdup_printf("%s.new", ff_path); + + if ((ff = ws_fopen(ff_path_new, "w")) == NULL) { + /* We had an error saving the filter. */ + report_failure("Error saving your %s filter file\nCouldn't open \"%s\": %s.", + ff_description, ff_path_new, g_strerror(errno)); + g_free(ff_path_new); + g_free(ff_path); + return; + } + flpp = g_list_first(fl); + while (flpp) { + filt = (filter_def *) flpp->data; + + /* Write out the filter name as a quoted string; escape any quotes + or backslashes. */ + putc('"', ff); + for (p = (unsigned char *)filt->name; (c = *p) != '\0'; p++) { + if (c == '"' || c == '\\') + putc('\\', ff); + putc(c, ff); + } + putc('"', ff); + + /* Separate the filter name and value with a space. */ + putc(' ', ff); + + /* Write out the filter expression and a newline. */ + fprintf(ff, "%s\n", filt->strval); + if (ferror(ff)) { + report_failure("Error saving your %s filter file\nWrite to \"%s\" failed: %s.", + ff_description, ff_path_new, g_strerror(errno)); + fclose(ff); + ws_unlink(ff_path_new); + g_free(ff_path_new); + g_free(ff_path); + return; + } + flpp = flpp->next; + } + if (fclose(ff) == EOF) { + report_failure("Error saving your %s filter file\nWrite to \"%s\" failed: %s.", + ff_description, ff_path_new, g_strerror(errno)); + ws_unlink(ff_path_new); + g_free(ff_path_new); + g_free(ff_path); + return; + } + +#ifdef _WIN32 + /* ANSI C doesn't say whether "rename()" removes the target if it + exists; the Win32 call to rename files doesn't do so, which I + infer is the reason why the MSVC++ "rename()" doesn't do so. + We must therefore remove the target file first, on Windows. + + XXX - ws_rename() should be ws_stdio_rename() on Windows, + and ws_stdio_rename() uses MoveFileEx() with MOVEFILE_REPLACE_EXISTING, + so it should remove the target if it exists, so this stuff + shouldn't be necessary. Perhaps it dates back to when we were + calling rename(), with that being a wrapper around Microsoft's + _rename(), which didn't remove the target. */ + if (ws_remove(ff_path) < 0 && errno != ENOENT) { + /* It failed for some reason other than "it's not there"; if + it's not there, we don't need to remove it, so we just + drive on. */ + report_failure("Error saving your %s filter file\nCouldn't remove \"%s\": %s.", + ff_description, ff_path, g_strerror(errno)); + ws_unlink(ff_path_new); + g_free(ff_path_new); + g_free(ff_path); + return; + } +#endif + + if (ws_rename(ff_path_new, ff_path) < 0) { + report_failure("Error saving your %s filter file\nCouldn't rename \"%s\" to \"%s\": %s.", + ff_description, ff_path_new, ff_path, g_strerror(errno)); + ws_unlink(ff_path_new); + g_free(ff_path_new); + g_free(ff_path); + return; + } + g_free(ff_path_new); + g_free(ff_path); +} diff --git a/wsutil/filter_files.h b/wsutil/filter_files.h new file mode 100644 index 00000000..f3e67872 --- /dev/null +++ b/wsutil/filter_files.h @@ -0,0 +1,98 @@ +/** @file + * + * Declarations of routines for reading and writing the filters file. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __FILTER_FILES_H__ +#define __FILTER_FILES_H__ + +#include <wireshark.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * Old filter file name. + */ +#define FILTER_FILE_NAME "filters" + +/* + * Capture filter file name. + */ +#define CFILTER_FILE_NAME "cfilters" + +/* + * Display filter file name. + */ +#define DFILTER_FILE_NAME "dfilters" + +/* + * Filter lists. + */ +typedef enum { + CFILTER_LIST, /* capture filter list - saved */ + DFILTER_LIST /* display filter list - saved */ +} filter_list_type_t; + +/* + * Item in a list of filters. + */ +typedef struct { + char *name; /* filter name */ + char *strval; /* filter expression */ +} filter_def; + +/* + * Read in a list of filters. + * + * On error, report the error via the UI. + */ +WS_DLL_PUBLIC +void read_filter_list(filter_list_type_t list_type); + +/* + * Get a pointer to the first entry in a filter list. + */ +WS_DLL_PUBLIC +GList *get_filter_list_first(filter_list_type_t list); + +/* + * Add a new filter to the end of a list. + * Returns a pointer to the newly-added entry. + */ +WS_DLL_PUBLIC +GList *add_to_filter_list(filter_list_type_t list, const char *name, + const char *expression); + +/* + * Remove a filter from a list. + */ +WS_DLL_PUBLIC +void remove_from_filter_list(filter_list_type_t list, GList *fl_entry); + +/* + * Write out a list of filters. + * + * On error, report the error via the UI. + */ +WS_DLL_PUBLIC +void save_filter_list(filter_list_type_t list_type); + +/* + * Free all filter lists + */ +WS_DLL_PUBLIC +void free_filter_lists(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __FILTER_FILES_H__ */ diff --git a/wsutil/g711.c b/wsutil/g711.c new file mode 100644 index 00000000..e6c3dcae --- /dev/null +++ b/wsutil/g711.c @@ -0,0 +1,300 @@ +/* + * This source code is a product of Sun Microsystems, Inc. and is provided + * for unrestricted use. Users may copy or modify this source code without + * charge. + * + * SUN SOURCE CODE IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING + * THE WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR + * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. + * + * Sun source code is provided with no support and without any obligation on + * the part of Sun Microsystems, Inc. to assist in its use, correction, + * modification or enhancement. + * + * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE + * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY THIS SOFTWARE + * OR ANY PART THEREOF. + * + * In no event will Sun Microsystems, Inc. be liable for any lost revenue + * or profits or other special, indirect and consequential damages, even if + * Sun has been advised of the possibility of such damages. + * + * Sun Microsystems, Inc. + * 2550 Garcia Avenue + * Mountain View, California 94043 + */ + +#include "g711.h" + +/* + * g711.c + * + * u-law, A-law and linear PCM conversions. + */ +#define SIGN_BIT (0x80) /* Sign bit for a A-law byte. */ +#define QUANT_MASK (0xf) /* Quantization field mask. */ +#define NSEGS (8) /* Number of A-law segments. */ +#define SEG_SHIFT (4) /* Left shift for segment number. */ +#define SEG_MASK (0x70) /* Segment field mask. */ + +static short seg_end[8] = {0xFF, 0x1FF, 0x3FF, 0x7FF, + 0xFFF, 0x1FFF, 0x3FFF, 0x7FFF}; + +/* copy from CCITT G.711 specifications */ +unsigned char _u2a[128] = { /* u- to A-law conversions */ + 1, 1, 2, 2, 3, 3, 4, 4, + 5, 5, 6, 6, 7, 7, 8, 8, + 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, + 25, 27, 29, 31, 33, 34, 35, 36, + 37, 38, 39, 40, 41, 42, 43, 44, + 46, 48, 49, 50, 51, 52, 53, 54, + 55, 56, 57, 58, 59, 60, 61, 62, + 64, 65, 66, 67, 68, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79, + 81, 82, 83, 84, 85, 86, 87, 88, + 89, 90, 91, 92, 93, 94, 95, 96, + 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, 112, + 113, 114, 115, 116, 117, 118, 119, 120, + 121, 122, 123, 124, 125, 126, 127, 128}; + +unsigned char _a2u[128] = { /* A- to u-law conversions */ + 1, 3, 5, 7, 9, 11, 13, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + 32, 32, 33, 33, 34, 34, 35, 35, + 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 48, 49, 49, + 50, 51, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 62, 63, 64, 64, + 65, 66, 67, 68, 69, 70, 71, 72, + 73, 74, 75, 76, 77, 78, 79, 79, + 80, 81, 82, 83, 84, 85, 86, 87, + 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, + 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 127}; + +static int +search( + int val, + short *table, + int size) +{ + int i; + + for (i = 0; i < size; i++) { + if (val <= *table++) + return (i); + } + return (size); +} + +/* + * linear2alaw() - Convert a 16-bit linear PCM value to 8-bit A-law + * + * linear2alaw() accepts an 16-bit integer and encodes it as A-law data. + * + * Linear Input Code Compressed Code + * ------------------------ --------------- + * 0000000wxyza 000wxyz + * 0000001wxyza 001wxyz + * 000001wxyzab 010wxyz + * 00001wxyzabc 011wxyz + * 0001wxyzabcd 100wxyz + * 001wxyzabcde 101wxyz + * 01wxyzabcdef 110wxyz + * 1wxyzabcdefg 111wxyz + * + * For further information see John C. Bellamy's Digital Telephony, 1982, + * John Wiley & Sons, pps 98-111 and 472-476. + */ +unsigned char +linear2alaw( + int pcm_val) /* 2's complement (16-bit range) */ +{ + int mask; + int seg; + unsigned char aval; + if (pcm_val >= 0) { + mask = 0xD5; /* sign (7th) bit = 1 */ + } else { + mask = 0x55; /* sign bit = 0 */ + pcm_val = -pcm_val - 8; + } + + /* Convert the scaled magnitude to segment number. */ + seg = search(pcm_val, seg_end, 8); + + /* Combine the sign, segment, and quantization bits. */ + + if (seg >= 8) /* out of range, return maximum value. */ + return (0x7F ^ mask); + else { + aval = seg << SEG_SHIFT; + if (seg < 2) + aval |= (pcm_val >> 4) & QUANT_MASK; + else + aval |= (pcm_val >> (seg + 3)) & QUANT_MASK; + return (aval ^ mask); + } +} + +/* + * alaw2linear() - Convert an A-law value to 16-bit linear PCM + * + */ +int +alaw2linear( + unsigned char a_val) +{ + int t; + int seg; + /*printf(" vrednost a_val %X ", a_val);*/ + a_val ^= 0x55; + + t = (a_val & QUANT_MASK) << 4; + seg = ((unsigned)a_val & SEG_MASK) >> SEG_SHIFT; + switch (seg) { + case 0: + t += 8; + break; + case 1: + t += 0x108; + break; + default: + t += 0x108; + t <<= seg - 1; + } + /*printf("izracunan int %d in njegov hex %X \n", t,t);*/ + return ((a_val & SIGN_BIT) ? t : -t); +} + +#define BIAS (0x84) /* Bias for linear code. */ + +/* + * linear2ulaw() - Convert a linear PCM value to u-law + * + * In order to simplify the encoding process, the original linear magnitude + * is biased by adding 33 which shifts the encoding range from (0 - 8158) to + * (33 - 8191). The result can be seen in the following encoding table: + * + * Biased Linear Input Code Compressed Code + * ------------------------ --------------- + * 00000001wxyza 000wxyz + * 0000001wxyzab 001wxyz + * 000001wxyzabc 010wxyz + * 00001wxyzabcd 011wxyz + * 0001wxyzabcde 100wxyz + * 001wxyzabcdef 101wxyz + * 01wxyzabcdefg 110wxyz + * 1wxyzabcdefgh 111wxyz + * + * Each biased linear code has a leading 1 which identifies the segment + * number. The value of the segment number is equal to 7 minus the number + * of leading 0's. The quantization interval is directly available as the + * four bits wxyz. * The trailing bits (a - h) are ignored. + * + * Ordinarily the complement of the resulting code word is used for + * transmission, and so the code word is complemented before it is returned. + * + * For further information see John C. Bellamy's Digital Telephony, 1982, + * John Wiley & Sons, pps 98-111 and 472-476. + */ +unsigned char +linear2ulaw( + int pcm_val) /* 2's complement (16-bit range) */ +{ + int mask; + int seg; + unsigned char uval; + + /* Get the sign and the magnitude of the value. */ + if (pcm_val < 0) { + pcm_val = BIAS - pcm_val; + mask = 0x7F; + } else { + pcm_val += BIAS; + mask = 0xFF; + } + + /* Convert the scaled magnitude to segment number. */ + seg = search(pcm_val, seg_end, 8); + + /* + * Combine the sign, segment, quantization bits; + * and complement the code word. + */ + if (seg >= 8) /* out of range, return maximum value. */ + return (0x7F ^ mask); + else { + uval = (seg << 4) | ((pcm_val >> (seg + 3)) & 0xF); + return (uval ^ mask); + } + +} + +/* + * ulaw2linear() - Convert a u-law value to 16-bit linear PCM + * + * First, a biased linear code is derived from the code word. An unbiased + * output can then be obtained by subtracting 33 from the biased code. + * + * Note that this function expects to be passed the complement of the + * original code word. This is in keeping with ISDN conventions. + */ +int +ulaw2linear( + unsigned char u_val) +{ + int t; + + /* Complement to obtain normal u-law value. */ + u_val = ~u_val; + + /* + * Extract and bias the quantization bits. Then + * shift up by the segment number and subtract out the bias. + */ + t = ((u_val & QUANT_MASK) << 3) + BIAS; + t <<= ((unsigned)u_val & SEG_MASK) >> SEG_SHIFT; + + return ((u_val & SIGN_BIT) ? (BIAS - t) : (t - BIAS)); +} + +/* A-law to u-law conversion */ +/* unsigned char + * alaw2ulaw( + * unsigned char aval) + * { + * aval &= 0xff; + * return ((aval & 0x80) ? (0xFF ^ _a2u[aval ^ 0xD5]) : + * (0x7F ^ _a2u[aval ^ 0x55])); + * } + */ + +/* u-law to A-law conversion */ +/* unsigned char + * ulaw2alaw( + * unsigned char uval) + * { + * uval &= 0xff; + * return ((uval & 0x80) ? (0xD5 ^ (_u2a[0xFF ^ uval] - 1)) : + * (0x55 ^ (_u2a[0x7F ^ uval] - 1))); + * } + */ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 8 + * tab-width: 8 + * indent-tabs-mode: t + * End: + * + * vi: set shiftwidth=8 tabstop=8 noexpandtab: + * :indentSize=8:tabSize=8:noTabs=false: + */ diff --git a/wsutil/g711.h b/wsutil/g711.h new file mode 100644 index 00000000..d5196da8 --- /dev/null +++ b/wsutil/g711.h @@ -0,0 +1,30 @@ +/** @file + * + * Definitions for routines for u-law, A-law and linear PCM conversions + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __G711_H__ +#define __G711_H__ + +#include "ws_symbol_export.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +WS_DLL_PUBLIC unsigned char linear2alaw( int ); +WS_DLL_PUBLIC int alaw2linear( unsigned char ); +WS_DLL_PUBLIC unsigned char linear2ulaw( int ); +WS_DLL_PUBLIC int ulaw2linear( unsigned char ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __G711_H__ */ diff --git a/wsutil/glib-compat.h b/wsutil/glib-compat.h new file mode 100644 index 00000000..08c87009 --- /dev/null +++ b/wsutil/glib-compat.h @@ -0,0 +1,46 @@ +/** @file +* +* Definitions to provide some functions that are not present in older +* GLIB versions we support (currently down to 2.50) +* +* Wireshark - Network traffic analyzer +* By Gerald Combs <gerald@wireshark.org> +* Copyright 1998 Gerald Combs +* +* SPDX-License-Identifier: GPL-2.0-or-later +*/ +#ifndef GLIB_COMPAT_H +#define GLIB_COMPAT_H + +#include "ws_symbol_export.h" +#include "ws_attributes.h" + +#include <glib.h> +#include <string.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#if !GLIB_CHECK_VERSION(2, 68, 0) +static inline void * +g_memdup2(gconstpointer mem, size_t byte_size) +{ + void * new_mem; + + if (mem && byte_size != 0) { + new_mem = g_malloc(byte_size); + memcpy(new_mem, mem, byte_size); + } + else + new_mem = NULL; + + return new_mem; +} +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* GLIB_COMPAT_H */ diff --git a/wsutil/inet_addr.c b/wsutil/inet_addr.c new file mode 100644 index 00000000..e0f1df62 --- /dev/null +++ b/wsutil/inet_addr.c @@ -0,0 +1,102 @@ +/* inet_addr.c + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" +#define WS_LOG_DOMAIN LOG_DOMAIN_WSUTIL +#include "inet_addr.h" + +#include <errno.h> +#include <string.h> + +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif + +#include <sys/types.h> + +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> /* needed to define AF_ values on UNIX */ +#endif + +#ifdef _WIN32 +#include <ws2tcpip.h> /* indirectly defines AF_ values on Windows */ +#define _NTOP_SRC_CAST_ (PVOID) +#else +#define _NTOP_SRC_CAST_ +#endif + +#include "str_util.h" + +/* + * We assume and require an inet_pton/inet_ntop that supports AF_INET + * and AF_INET6. + */ + +static inline bool +inet_pton_internal(int af, const char *src, void *dst, size_t dst_size, + const char *af_str) +{ + int ret = inet_pton(af, src, dst); + if (ret < 0) { + int err = errno; + ws_log(WS_LOG_DOMAIN, LOG_LEVEL_CRITICAL, "inet_pton: %s (%d): %s", af_str, af, g_strerror(err)); + memset(dst, 0, dst_size); + errno = err; + return false; + } + /* ret == 0 invalid src representation, ret == 1 success. */ + return ret == 1; +} + +static inline const char * +inet_ntop_internal(int af, const void *src, char *dst, size_t dst_size, + const char *af_str) +{ + /* Add a cast to ignore 64-to-32 bit narrowing warnings with some + * compilers (POSIX uses socklen_t instead of size_t). */ + const char *ret = inet_ntop(af, _NTOP_SRC_CAST_ src, dst, (unsigned int)dst_size); + if (ret == NULL) { + int err = errno; + char errbuf[16]; + ws_log(WS_LOG_DOMAIN, LOG_LEVEL_CRITICAL, "inet_ntop: %s (%d): %s", af_str, af, g_strerror(err)); + /* set result to something that can't be confused with a valid conversion */ + (void)g_strlcpy(dst, ws_strerrorname_r(err, errbuf, sizeof(errbuf)), dst_size); + errno = err; + return dst; + } + return dst; +} + +const char * +ws_inet_ntop4(const void *src, char *dst, size_t dst_size) +{ + return inet_ntop_internal(AF_INET, src, dst, dst_size, "AF_INET"); +} + +bool +ws_inet_pton4(const char *src, ws_in4_addr *dst) +{ + return inet_pton_internal(AF_INET, src, dst, sizeof(*dst), "AF_INET"); +} + +const char * +ws_inet_ntop6(const void *src, char *dst, size_t dst_size) +{ + return inet_ntop_internal(AF_INET6, src, dst, dst_size, "AF_INET6"); +} + +bool +ws_inet_pton6(const char *src, ws_in6_addr *dst) +{ + return inet_pton_internal(AF_INET6, src, dst, sizeof(*dst), "AF_INET6"); +} diff --git a/wsutil/inet_addr.h b/wsutil/inet_addr.h new file mode 100644 index 00000000..7147c2ae --- /dev/null +++ b/wsutil/inet_addr.h @@ -0,0 +1,75 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WS_INET_ADDR_H__ +#define __WS_INET_ADDR_H__ + +#include <wireshark.h> + +#include "inet_ipv4.h" +#include "inet_ipv6.h" + +/* + * These are the values specified by RFC 2133 and its successors for + * INET_ADDRSTRLEN and INET6_ADDRSTRLEN. + * + * On UN*X systems, INET_ADDRSTRLEN and INET6_ADDRSTRLEN are defined + * to the values from RFC 2133 and its successors. + * + * However, on Windows: + * + * There are APIs RtlIpv4AddressToStringEx(), which converts an + * IPv4 address *and transport-layer port* to the address in the + * standard text form, followed by a colon and the port number, + * and RtlIpv6AddressToStringEx(), which converts an IPv6 address + * *and scope ID and transport-layer port* to the address in the + * standard text form, followed by a percent sign and the scope + * ID (with the address and scope ID in square brackets), followed + * by a colon and the port number. + * + * Instead of defining INET_ADDRSTRLEN_EX as 22 and INET6_ADDRSTRLEN_EX + * as 65, and saying *those* were the buffer sizes to use for + * RtlIpv4AddressToStringEx() and RtlIpv6AddressToStringEx(), they + * defined INET_ADDRSTRLEN to be 22 and INET6_ADDRSTRLEN to be 65 - and + * recommend using those as the size for the buffers passed to + * RtlIpv4AddressToStringEx() and RtlIpv6AddressToStringEx(). + * + * At least they document inet_ntop() as requiring a 16-byte or larger + * buffer for IPv4 addresses and a 46-byte or larger buffer for + * IPv6 addresses. For this reason, use hard-coded numeric constants rather than + * INET_ADDRSTRLEN and INET6_ADDRSTRLEN. + */ +#define WS_INET_ADDRSTRLEN 16 +#define WS_INET6_ADDRSTRLEN 46 + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * To check for errors set errno to zero before calling ws_inet_ntop{4,6}. + * ENOSPC is set if the result exceeds the given buffer size. + */ +WS_DLL_PUBLIC WS_RETNONNULL const char * +ws_inet_ntop4(const void *src, char *dst, size_t dst_size); + +WS_DLL_PUBLIC WS_RETNONNULL const char * +ws_inet_ntop6(const void *src, char *dst, size_t dst_size); + +WS_DLL_PUBLIC bool +ws_inet_pton4(const char *src, ws_in4_addr *dst); + +WS_DLL_PUBLIC bool +ws_inet_pton6(const char *src, ws_in6_addr *dst); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif diff --git a/wsutil/inet_ipv4.h b/wsutil/inet_ipv4.h new file mode 100644 index 00000000..a5a8fddd --- /dev/null +++ b/wsutil/inet_ipv4.h @@ -0,0 +1,57 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __INET_IPV4_H__ +#define __INET_IPV4_H__ + +#include <inttypes.h> +#include <glib.h> + +typedef uint32_t ws_in4_addr; /* 32 bit IPv4 address, in network byte order */ + +/* + * We define these in *network byte order*, unlike the C library. Therefore + * it uses a different prefix than INADDR_* to make the distinction more obvious. + */ +#define WS_IN4_LOOPBACK ((ws_in4_addr)GUINT32_TO_BE(0x7f000001)) + +/** + * Unicast Local + * Returns true if the address is in the 224.0.0.0/24 local network + * control block + */ +#define in4_addr_is_local_network_control_block(addr) \ + ((addr & 0xffffff00) == 0xe0000000) + +/** + * Multicast + * Returns true if the address is in the 224.0.0.0/4 network block + */ +#define in4_addr_is_multicast(addr) \ + ((addr & 0xf0000000) == 0xe0000000) + +/** + * Private address + * Returns true if the address is in one of the three blocks reserved + * for private IPv4 addresses by section 3 of RFC 1918, namely: + * 10/8, 172.16/12, and 192.168/16 + */ +#define in4_addr_is_private(addr) \ + (((addr & 0xff000000) == 0x0a000000) || \ + ((addr & 0xfff00000) == 0xac100000) || \ + ((addr & 0xffff0000) == 0xc0a80000)) + +/** + * Link-local address + * Returns true if the address is in the 169.254/16 network block + */ +#define in4_addr_is_link_local(addr) \ + ((addr & 0xffff0000) == 0xa9fe0000) + +#endif diff --git a/wsutil/inet_ipv6.h b/wsutil/inet_ipv6.h new file mode 100644 index 00000000..0cd70394 --- /dev/null +++ b/wsutil/inet_ipv6.h @@ -0,0 +1,103 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __INET_IPV6_H__ +#define __INET_IPV6_H__ + +#include <inttypes.h> +#include <stdbool.h> + +#define IPv6_ADDR_SIZE 16 + +#define IPv6_HDR_SIZE 40 +#define IPv6_FRAGMENT_HDR_SIZE 8 + +typedef struct e_in6_addr { + uint8_t bytes[16]; /* 128 bit IPv6 address */ +} ws_in6_addr; + +/* + * Definition for internet protocol version 6. + * RFC 2460 + */ +struct ws_ip6_hdr { + uint32_t ip6h_vc_flow; /* version, class, flow */ + uint16_t ip6h_plen; /* payload length */ + uint8_t ip6h_nxt; /* next header */ + uint8_t ip6h_hlim; /* hop limit */ + ws_in6_addr ip6h_src; /* source address */ + ws_in6_addr ip6h_dst; /* destination address */ +}; + +/* + * Extension Headers + */ + +struct ip6_ext { + unsigned char ip6e_nxt; + unsigned char ip6e_len; +}; + +/* Routing header */ +struct ip6_rthdr { + uint8_t ip6r_nxt; /* next header */ + uint8_t ip6r_len; /* length in units of 8 octets */ + uint8_t ip6r_type; /* routing type */ + uint8_t ip6r_segleft; /* segments left */ + /* followed by routing type specific data */ +}; + +/* Type 0 Routing header */ +struct ip6_rthdr0 { + uint8_t ip6r0_nxt; /* next header */ + uint8_t ip6r0_len; /* length in units of 8 octets */ + uint8_t ip6r0_type; /* always zero */ + uint8_t ip6r0_segleft; /* segments left */ + uint8_t ip6r0_reserved; /* reserved field */ + uint8_t ip6r0_slmap[3]; /* strict/loose bit map */ + /* followed by up to 127 addresses */ + ws_in6_addr ip6r0_addr[1]; +}; + +/* Fragment header */ +struct ip6_frag { + uint8_t ip6f_nxt; /* next header */ + uint8_t ip6f_reserved; /* reserved field */ + uint16_t ip6f_offlg; /* offset, reserved, and flag */ + uint32_t ip6f_ident; /* identification */ +}; + +#define IP6F_OFF_MASK 0xfff8 /* mask out offset from _offlg */ +#define IP6F_RESERVED_MASK 0x0006 /* reserved bits in ip6f_offlg */ +#define IP6F_MORE_FRAG 0x0001 /* more-fragments flag */ + + +/** + * Unicast Scope + * Note that we must check topmost 10 bits only, not 16 bits (see RFC2373). + */ +static inline bool in6_addr_is_linklocal(const ws_in6_addr *a) +{ + return (a->bytes[0] == 0xfe) && ((a->bytes[1] & 0xc0) == 0x80); +} + +static inline bool in6_addr_is_sitelocal(const ws_in6_addr *a) +{ + return (a->bytes[0] == 0xfe) && ((a->bytes[1] & 0xc0) == 0xc0); +} + +/** + * Multicast + */ +static inline bool in6_addr_is_multicast(const ws_in6_addr *a) +{ + return a->bytes[0] == 0xff; +} + +#endif diff --git a/wsutil/interface.c b/wsutil/interface.c new file mode 100644 index 00000000..0fc990d4 --- /dev/null +++ b/wsutil/interface.c @@ -0,0 +1,171 @@ +/* interface.c + * Utility functions to get infos from interfaces + * + * Copyright 2016, Dario Lombardo + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" + +#include "interface.h" + +#include <string.h> +#include <wsutil/inet_addr.h> + +#include <sys/types.h> + +#ifdef HAVE_SYS_SOCKET_H + #include <sys/socket.h> +#endif + +#ifdef HAVE_NETINET_IN_H + #include <netinet/in.h> +#endif + +#ifdef HAVE_ARPA_INET_H + #include <arpa/inet.h> +#endif + +#ifdef HAVE_IFADDRS_H + #include <ifaddrs.h> +#endif + +#ifdef _WIN32 + #include <winsock2.h> + #include <iphlpapi.h> + #include <ws2tcpip.h> +#endif + +#define WORKING_BUFFER_SIZE 15000 + +#ifdef HAVE_GETIFADDRS +static GSList* local_interfaces_to_list_nix(void) +{ + GSList *interfaces = NULL; + struct ifaddrs *ifap; + struct ifaddrs *ifa; + int family; + char ip[WS_INET6_ADDRSTRLEN]; + + if (getifaddrs(&ifap)) { + goto end; + } + + for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL) + continue; + + family = ifa->ifa_addr->sa_family; + + memset(ip, 0x0, WS_INET6_ADDRSTRLEN); + + switch (family) { + case AF_INET: + { + struct sockaddr_in *addr4 = (struct sockaddr_in *)ifa->ifa_addr; + ws_inet_ntop4(&addr4->sin_addr, ip, sizeof(ip)); + break; + } + + case AF_INET6: + { + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)ifa->ifa_addr; + ws_inet_ntop6(&addr6->sin6_addr, ip, sizeof(ip)); + break; + } + + default: + break; + } + + /* skip loopback addresses */ + if (!g_strcmp0(ip, "127.0.0.1") || !g_strcmp0(ip, "::1")) + continue; + + if (*ip) { + interfaces = g_slist_prepend(interfaces, g_strdup(ip)); + } + } + freeifaddrs(ifap); +end: + return interfaces; +} +#endif /* HAVE_GETIFADDRS */ + +#ifdef _WIN32 +static GSList* local_interfaces_to_list_win(void) +{ + GSList *interfaces = NULL; + PIP_ADAPTER_ADDRESSES pAddresses = NULL; + ULONG outBufLen = WORKING_BUFFER_SIZE; + ULONG family = AF_UNSPEC; + PIP_ADAPTER_ADDRESSES pCurrAddresses = NULL; + PIP_ADAPTER_UNICAST_ADDRESS pUnicast = NULL; + char ip[100]; + unsigned iplen = 100; + + pAddresses = (IP_ADAPTER_ADDRESSES *)g_malloc0(outBufLen); + if (pAddresses == NULL) + return NULL; + + if (GetAdaptersAddresses(family, 0, NULL, pAddresses, &outBufLen) != NO_ERROR) + goto end; + + pCurrAddresses = pAddresses; + while (pCurrAddresses) { + + for (pUnicast = pCurrAddresses->FirstUnicastAddress; pUnicast != NULL; pUnicast = pUnicast->Next) { + if (pUnicast->Address.lpSockaddr->sa_family == AF_INET) { + SOCKADDR_IN* sa_in = (SOCKADDR_IN *)pUnicast->Address.lpSockaddr; + ws_inet_ntop4(&(sa_in->sin_addr), ip, iplen); + if (!g_strcmp0(ip, "127.0.0.1")) + continue; + if (*ip) + interfaces = g_slist_prepend(interfaces, g_strdup(ip)); + } + if (pUnicast->Address.lpSockaddr->sa_family == AF_INET6) { + SOCKADDR_IN6* sa_in6 = (SOCKADDR_IN6 *)pUnicast->Address.lpSockaddr; + ws_inet_ntop6(&(sa_in6->sin6_addr), ip, iplen); + if (!g_strcmp0(ip, "::1")) + continue; + if (*ip) + interfaces = g_slist_prepend(interfaces, g_strdup(ip)); + } + } + pCurrAddresses = pCurrAddresses->Next; + } +end: + g_free(pAddresses); + + return interfaces; +} +#endif /* _WIN32 */ + +GSList* local_interfaces_to_list(void) +{ +#if defined(_WIN32) + return local_interfaces_to_list_win(); +#elif defined(HAVE_GETIFADDRS) + return local_interfaces_to_list_nix(); +#else + return NULL; +#endif +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: t + * End: + * + * vi: set shiftwidth=4 tabstop=8 noexpandtab: + * :indentSize=4:tabSize=8:noTabs=false: + */ diff --git a/wsutil/interface.h b/wsutil/interface.h new file mode 100644 index 00000000..5c431bfa --- /dev/null +++ b/wsutil/interface.h @@ -0,0 +1,36 @@ +/** @file + * Utility functions to get infos from interfaces + * + * Copyright 2016, Dario Lombardo + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef _INTERFACE_H +#define _INTERFACE_H + +#include <glib.h> +#include "ws_symbol_export.h" + +/* Return a list of IPv4/IPv6 addresses for local interfaces */ +WS_DLL_PUBLIC +GSList* local_interfaces_to_list(void); + +#endif + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: t + * End: + * + * vi: set shiftwidth=4 tabstop=8 noexpandtab: + * :indentSize=4:tabSize=8:noTabs=false: + */ diff --git a/wsutil/introspection.c b/wsutil/introspection.c new file mode 100644 index 00000000..c571c8d5 --- /dev/null +++ b/wsutil/introspection.c @@ -0,0 +1,27 @@ +/* + * Copyright 2021, João Valverde <j@v6e.pt> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#include "config.h" +#include "introspection.h" +#include <string.h> +#include <stdlib.h> + + +static int +compare_enum(const void *needle, const void *memb) +{ + return strcmp(needle, ((const ws_enum_t *)memb)->symbol); +} + +const ws_enum_t * +ws_enums_bsearch(const ws_enum_t *enums, size_t count, + const char *needle) +{ + return bsearch(needle, enums, count, sizeof(ws_enum_t), compare_enum); +} diff --git a/wsutil/introspection.h b/wsutil/introspection.h new file mode 100644 index 00000000..f3272df8 --- /dev/null +++ b/wsutil/introspection.h @@ -0,0 +1,29 @@ +/** @file + * Copyright 2021, João Valverde <j@v6e.pt> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef _INTROSPECTION_H_ +#define _INTROSPECTION_H_ + +#include <stddef.h> +#include <ws_symbol_export.h> + +typedef struct { + const char *symbol; + int value; +} ws_enum_t; + + +/** Performs a binary search for the magic constant "needle". */ +WS_DLL_PUBLIC +const ws_enum_t * +ws_enums_bsearch(const ws_enum_t *enums, size_t count, + const char *needle); + +#endif diff --git a/wsutil/jsmn.c b/wsutil/jsmn.c new file mode 100644 index 00000000..d5e3bb5f --- /dev/null +++ b/wsutil/jsmn.c @@ -0,0 +1,332 @@ +/* +Copyright (c) 2010 Serge A. Zaitsev + +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. +*/ + +#include "jsmn.h" + +/** + * Allocates a fresh unused token from the token pull. + */ +static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, + jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *tok; + if (parser->toknext >= num_tokens) { + return NULL; + } + tok = &tokens[parser->toknext++]; + tok->start = tok->end = -1; + tok->size = 0; +#ifdef JSMN_PARENT_LINKS + tok->parent = -1; +#endif + return tok; +} + +/** + * Fills token type and boundaries. + */ +static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, + int start, int end) { + token->type = type; + token->start = start; + token->end = end; + token->size = 0; +} + +/** + * Fills next available token with JSON primitive. + */ +static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, + size_t len, jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *token; + int start; + + start = parser->pos; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + switch (js[parser->pos]) { +#ifndef JSMN_STRICT + /* In strict mode primitive must be followed by "," or "}" or "]" */ + case ':': +#endif + case '\t' : case '\r' : case '\n' : case ' ' : + case ',' : case ']' : case '}' : + goto found; + } + if (js[parser->pos] < 32 || js[parser->pos] >= 127) { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } +#ifdef JSMN_STRICT + /* In strict mode primitive must be followed by a comma/object/array */ + parser->pos = start; + return JSMN_ERROR_PART; +#endif + +found: + if (tokens == NULL) { + parser->pos--; + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + parser->pos--; + return 0; +} + +/** + * Fills next token with JSON string. + */ +static int jsmn_parse_string(jsmn_parser *parser, const char *js, + size_t len, jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *token; + + int start = parser->pos; + + parser->pos++; + + /* Skip starting quote */ + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + char c = js[parser->pos]; + + /* Quote: end of string */ + if (c == '\"') { + if (tokens == NULL) { + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + return 0; + } + + /* Backslash: Quoted symbol expected */ + if (c == '\\' && parser->pos + 1 < len) { + int i; + parser->pos++; + switch (js[parser->pos]) { + /* Allowed escaped symbols */ + case '\"': case '/' : case '\\' : case 'b' : + case 'f' : case 'r' : case 'n' : case 't' : + break; + /* Allows escaped symbol \uXXXX */ + case 'u': + parser->pos++; + for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) { + /* If it isn't a hex character we have an error */ + if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ + (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ + (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ + parser->pos = start; + return JSMN_ERROR_INVAL; + } + parser->pos++; + } + parser->pos--; + break; + /* Unexpected symbol */ + default: + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } + } + parser->pos = start; + return JSMN_ERROR_PART; +} + +/** + * Parse JSON string and fill tokens. + */ +int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, + jsmntok_t *tokens, unsigned int num_tokens) { + int r; + int i; + jsmntok_t *token; + int count = parser->toknext; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + char c; + jsmntype_t type; + + c = js[parser->pos]; + switch (c) { + case '{': case '[': + count++; + if (tokens == NULL) { + break; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) + return JSMN_ERROR_NOMEM; + if (parser->toksuper != -1) { + tokens[parser->toksuper].size++; +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + } + token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); + token->start = parser->pos; + parser->toksuper = parser->toknext - 1; + break; + case '}': case ']': + if (tokens == NULL) + break; + type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); +#ifdef JSMN_PARENT_LINKS + if (parser->toknext < 1) { + return JSMN_ERROR_INVAL; + } + token = &tokens[parser->toknext - 1]; + for (;;) { + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + token->end = parser->pos + 1; + parser->toksuper = token->parent; + break; + } + if (token->parent == -1) { + break; + } + token = &tokens[token->parent]; + } +#else + for (i = parser->toknext - 1; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + parser->toksuper = -1; + token->end = parser->pos + 1; + break; + } + } + /* Error if unmatched closing bracket */ + if (i == -1) return JSMN_ERROR_INVAL; + for (; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + parser->toksuper = i; + break; + } + } +#endif + break; + case '\"': + r = jsmn_parse_string(parser, js, len, tokens, num_tokens); + if (r < 0) return r; + count++; + if (parser->toksuper != -1 && tokens != NULL) + tokens[parser->toksuper].size++; + break; + case '\t' : case '\r' : case '\n' : case ' ': + break; + case ':': + parser->toksuper = parser->toknext - 1; + break; + case ',': + if (tokens != NULL && parser->toksuper != -1 && + tokens[parser->toksuper].type != JSMN_ARRAY && + tokens[parser->toksuper].type != JSMN_OBJECT) { +#ifdef JSMN_PARENT_LINKS + parser->toksuper = tokens[parser->toksuper].parent; +#else + for (i = parser->toknext - 1; i >= 0; i--) { + if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) { + if (tokens[i].start != -1 && tokens[i].end == -1) { + parser->toksuper = i; + break; + } + } + } +#endif + } + break; +#ifdef JSMN_STRICT + /* In strict mode primitives are: numbers and booleans */ + case '-': case '0': case '1' : case '2': case '3' : case '4': + case '5': case '6': case '7' : case '8': case '9': + case 't': case 'f': case 'n' : + /* And they must not be keys of the object */ + if (tokens != NULL && parser->toksuper != -1) { + jsmntok_t *t = &tokens[parser->toksuper]; + if (t->type == JSMN_OBJECT || + (t->type == JSMN_STRING && t->size != 0)) { + return JSMN_ERROR_INVAL; + } + } +#else + /* In non-strict mode every unquoted value is a primitive */ + default: +#endif + r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); + if (r < 0) return r; + count++; + if (parser->toksuper != -1 && tokens != NULL) + tokens[parser->toksuper].size++; + break; + +#ifdef JSMN_STRICT + /* Unexpected char in strict mode */ + default: + return JSMN_ERROR_INVAL; +#endif + } + } + + if (tokens != NULL) { + for (i = parser->toknext - 1; i >= 0; i--) { + /* Unmatched opened object or array */ + if (tokens[i].start != -1 && tokens[i].end == -1) { + return JSMN_ERROR_PART; + } + } + } + + return count; +} + +/** + * Creates a new parser based over a given buffer with an array of tokens + * available. + */ +void jsmn_init(jsmn_parser *parser) { + parser->pos = 0; + parser->toknext = 0; + parser->toksuper = -1; +} diff --git a/wsutil/jsmn.h b/wsutil/jsmn.h new file mode 100644 index 00000000..228c4160 --- /dev/null +++ b/wsutil/jsmn.h @@ -0,0 +1,99 @@ +/** @file + +Copyright (c) 2010 Serge A. Zaitsev + +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. +*/ + +#ifndef __JSMN_H_ +#define __JSMN_H_ + +#include <stddef.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * JSON type identifier. Basic types are: + * o Object + * o Array + * o String + * o Other primitive: number, boolean (true/false) or null + */ +typedef enum { + JSMN_UNDEFINED = 0, + JSMN_OBJECT = 1, + JSMN_ARRAY = 2, + JSMN_STRING = 3, + JSMN_PRIMITIVE = 4 +} jsmntype_t; + +enum jsmnerr { + /* Not enough tokens were provided */ + JSMN_ERROR_NOMEM = -1, + /* Invalid character inside JSON string */ + JSMN_ERROR_INVAL = -2, + /* The string is not a full JSON packet, more bytes expected */ + JSMN_ERROR_PART = -3 +}; + +/** + * JSON token description. + * type type (object, array, string etc.) + * start start position in JSON data string + * end end position in JSON data string + */ +typedef struct { + jsmntype_t type; + int start; + int end; + int size; +#ifdef JSMN_PARENT_LINKS + int parent; +#endif +} jsmntok_t; + +/** + * JSON parser. Contains an array of token blocks available. Also stores + * the string being parsed now and current position in that string + */ +typedef struct { + unsigned int pos; /* offset in the JSON string */ + unsigned int toknext; /* next token to allocate */ + int toksuper; /* superior token node, e.g parent object or array */ +} jsmn_parser; + +/** + * Create JSON parser over an array of tokens + */ +void jsmn_init(jsmn_parser *parser); + +/** + * Run JSON parser. It parses a JSON data string into and array of tokens, each describing + * a single JSON object. + */ +int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, + jsmntok_t *tokens, unsigned int num_tokens); + +#ifdef __cplusplus +} +#endif + +#endif /* __JSMN_H_ */ diff --git a/wsutil/json_dumper.c b/wsutil/json_dumper.c new file mode 100644 index 00000000..5cbc9494 --- /dev/null +++ b/wsutil/json_dumper.c @@ -0,0 +1,689 @@ +/* json_dumper.c + * Routines for serializing data as JSON. + * + * Copyright 2018, Peter Wu <peter@lekensteyn.nl> + * Copyright (C) 2016 Jakub Zawadzki + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <glib.h> + +#include "json_dumper.h" +#define WS_LOG_DOMAIN LOG_DOMAIN_WSUTIL + +#include <math.h> + +#include <wsutil/wslog.h> + +/* + * json_dumper.state[current_depth] describes a nested element: + * - type: none/object/array/non-base64 value/base64 value + * - has_name: Whether the object member name was set. + * + * (A base64 value isn't really a nested element, but that's a + * convenient way of handling them, with a begin call that opens + * the string with a double-quote, one or more calls to convert + * raw bytes to base64 and add them to the value, and an end call + * that finishes the base64 encoding, adds any remaining raw bytes + * in base64 encoding, and closes the string with a double-quote.) + */ +enum json_dumper_element_type { + JSON_DUMPER_TYPE_NONE = 0, + JSON_DUMPER_TYPE_VALUE = 1, + JSON_DUMPER_TYPE_OBJECT = 2, + JSON_DUMPER_TYPE_ARRAY = 3, + JSON_DUMPER_TYPE_BASE64 = 4, +}; +#define JSON_DUMPER_TYPE(state) ((enum json_dumper_element_type)((state) & 7)) +#define JSON_DUMPER_HAS_NAME (1 << 3) + +static const char *json_dumper_element_type_names[] = { + [JSON_DUMPER_TYPE_NONE] = "none", + [JSON_DUMPER_TYPE_VALUE] = "value", + [JSON_DUMPER_TYPE_OBJECT] = "object", + [JSON_DUMPER_TYPE_ARRAY] = "array", + [JSON_DUMPER_TYPE_BASE64] = "base64" +}; +#define NUM_JSON_DUMPER_ELEMENT_TYPE_NAMES (sizeof json_dumper_element_type_names / sizeof json_dumper_element_type_names[0]) + +#define JSON_DUMPER_FLAGS_ERROR (1 << 16) /* Output flag: an error occurred. */ + +enum json_dumper_change { + JSON_DUMPER_BEGIN, + JSON_DUMPER_END, + JSON_DUMPER_SET_NAME, + JSON_DUMPER_SET_VALUE, + JSON_DUMPER_WRITE_BASE64, + JSON_DUMPER_FINISH, +}; + +/* JSON Dumper putc */ +static void +jd_putc(const json_dumper *dumper, char c) +{ + if (dumper->output_file) { + fputc(c, dumper->output_file); + } + + if (dumper->output_string) { + g_string_append_c(dumper->output_string, c); + } +} + +/* JSON Dumper puts */ +static void +jd_puts(const json_dumper *dumper, const char *s) +{ + if (dumper->output_file) { + fputs(s, dumper->output_file); + } + + if (dumper->output_string) { + g_string_append(dumper->output_string, s); + } +} + +static void +jd_puts_len(const json_dumper *dumper, const char *s, size_t len) +{ + if (dumper->output_file) { + fwrite(s, 1, len, dumper->output_file); + } + + if (dumper->output_string) { + g_string_append_len(dumper->output_string, s, len); + } +} + +static void +jd_vprintf(const json_dumper *dumper, const char *format, va_list args) +{ + if (dumper->output_file) { + vfprintf(dumper->output_file, format, args); + } + + if (dumper->output_string) { + g_string_append_vprintf(dumper->output_string, format, args); + } +} + +static void +json_puts_string(const json_dumper *dumper, const char *str, bool dot_to_underscore) +{ + if (!str) { + jd_puts(dumper, "null"); + return; + } + + static const char json_cntrl[0x20][6] = { + "u0000", "u0001", "u0002", "u0003", "u0004", "u0005", "u0006", "u0007", "b", "t", "n", "u000b", "f", "r", "u000e", "u000f", + "u0010", "u0011", "u0012", "u0013", "u0014", "u0015", "u0016", "u0017", "u0018", "u0019", "u001a", "u001b", "u001c", "u001d", "u001e", "u001f" + }; + + jd_putc(dumper, '"'); + for (int i = 0; str[i]; i++) { + if ((unsigned)str[i] < 0x20) { + jd_putc(dumper, '\\'); + jd_puts(dumper, json_cntrl[(unsigned)str[i]]); + } else if (i > 0 && str[i - 1] == '<' && str[i] == '/') { + // Convert </script> to <\/script> to avoid breaking web pages. + jd_puts(dumper, "\\/"); + } else { + if (str[i] == '\\' || str[i] == '"') { + jd_putc(dumper, '\\'); + } + if (dot_to_underscore && str[i] == '.') + jd_putc(dumper, '_'); + else + jd_putc(dumper, str[i]); + } + } + jd_putc(dumper, '"'); +} + +static inline uint8_t +json_dumper_get_prev_state(json_dumper *dumper) +{ + unsigned depth = dumper->current_depth; + return depth != 0 ? dumper->state[depth - 1] : 0; +} + +static inline uint8_t +json_dumper_get_curr_state(json_dumper *dumper) +{ + unsigned depth = dumper->current_depth; + return dumper->state[depth]; +} + +/** + * Called when a programming error is encountered where the JSON manipulation + * state got corrupted. This could happen when pairing the wrong begin/end + * calls, when writing multiple values for the same object, etc. + */ +static void +json_dumper_bad(json_dumper *dumper, const char *what) +{ + dumper->flags |= JSON_DUMPER_FLAGS_ERROR; + if ((dumper->flags & JSON_DUMPER_FLAGS_NO_DEBUG)) { + /* Console output can be slow, disable log calls to speed up fuzzing. */ + /* + * XXX - should this call abort()? If that flag isn't set, + * ws_error() wou;d call it; is there any point in continuing + * to do anything if we get here when fuzzing? + */ + return; + } + + if (dumper->output_file) { + fflush(dumper->output_file); + } + char unknown_curr_type_name[10+1]; + char unknown_prev_type_name[10+1]; + const char *curr_type_name, *prev_type_name; + uint8_t curr_state = json_dumper_get_curr_state(dumper); + uint8_t curr_type = JSON_DUMPER_TYPE(curr_state); + if (curr_type < NUM_JSON_DUMPER_ELEMENT_TYPE_NAMES) { + curr_type_name = json_dumper_element_type_names[curr_type]; + } else { + snprintf(unknown_curr_type_name, sizeof unknown_curr_type_name, "%u", curr_type); + curr_type_name = unknown_curr_type_name; + } + if (dumper->current_depth != 0) { + uint8_t prev_state = json_dumper_get_prev_state(dumper); + uint8_t prev_type = JSON_DUMPER_TYPE(prev_state); + if (prev_type < NUM_JSON_DUMPER_ELEMENT_TYPE_NAMES) { + prev_type_name = json_dumper_element_type_names[prev_type]; + } else { + snprintf(unknown_prev_type_name, sizeof unknown_prev_type_name, "%u", prev_type); + prev_type_name = unknown_prev_type_name; + } + } else { + prev_type_name = "(none)"; + } + ws_error("json_dumper error: %s: current stack depth %u, current type %s, previous_type %s", + what, dumper->current_depth, curr_type_name, prev_type_name); + /* NOTREACHED */ +} + +static inline bool +json_dumper_stack_would_overflow(json_dumper *dumper) +{ + if (dumper->current_depth + 1 >= JSON_DUMPER_MAX_DEPTH) { + json_dumper_bad(dumper, "JSON dumper stack overflow"); + return true; + } + return false; +} + +static inline bool +json_dumper_stack_would_underflow(json_dumper *dumper) +{ + if (dumper->current_depth == 0) { + json_dumper_bad(dumper, "JSON dumper stack underflow"); + return true; + } + return false; +} + +/** + * Checks that the dumper has not already had an error. Fail, and + * return false, to tell our caller not to do any more work, if it + * has. + */ +static bool +json_dumper_check_previous_error(json_dumper *dumper) +{ + if ((dumper->flags & JSON_DUMPER_FLAGS_ERROR)) { + json_dumper_bad(dumper, "previous corruption detected"); + return false; + } + return true; +} + +static void +print_newline_indent(const json_dumper *dumper, unsigned depth) +{ + if ((dumper->flags & JSON_DUMPER_FLAGS_PRETTY_PRINT)) { + jd_putc(dumper, '\n'); + for (unsigned i = 0; i < depth; i++) { + jd_puts(dumper, " "); + } + } +} + +/** + * Prints commas, newlines and indentation (if necessary). Used for array + * values, object names and normal values (strings, etc.). + */ +static void +prepare_token(json_dumper *dumper) +{ + if (dumper->current_depth == 0) { + // not part of an array or object. + return; + } + uint8_t prev_state = dumper->state[dumper->current_depth - 1]; + + // While processing the object value, reset the key state as it is consumed. + dumper->state[dumper->current_depth - 1] &= ~JSON_DUMPER_HAS_NAME; + + switch (JSON_DUMPER_TYPE(prev_state)) { + case JSON_DUMPER_TYPE_OBJECT: + if ((prev_state & JSON_DUMPER_HAS_NAME)) { + // Object key already set, value follows. No indentation needed. + return; + } + break; + case JSON_DUMPER_TYPE_ARRAY: + break; + default: + // Initial values do not need indentation. + return; + } + + uint8_t curr_state = json_dumper_get_curr_state(dumper); + if (curr_state != JSON_DUMPER_TYPE_NONE) { + jd_putc(dumper, ','); + } + print_newline_indent(dumper, dumper->current_depth); +} + +/** + * Common code to open an object/array/base64 value, printing + * an opening character. + * + * It also makes various correctness checks. + */ +static bool +json_dumper_begin_nested_element(json_dumper *dumper, enum json_dumper_element_type type) +{ + if (!json_dumper_check_previous_error(dumper)) { + return false; + } + + /* Make sure we won't overflow the dumper stack */ + if (json_dumper_stack_would_overflow(dumper)) { + return false; + } + + prepare_token(dumper); + switch (type) { + case JSON_DUMPER_TYPE_OBJECT: + jd_putc(dumper, '{'); + break; + case JSON_DUMPER_TYPE_ARRAY: + jd_putc(dumper, '['); + break; + case JSON_DUMPER_TYPE_BASE64: + dumper->base64_state = 0; + dumper->base64_save = 0; + + jd_putc(dumper, '"'); + break; + default: + json_dumper_bad(dumper, "beginning unknown nested element type"); + return false; + } + + dumper->state[dumper->current_depth] = type; + /* + * Guaranteed not to overflow, as json_dumper_stack_would_overflow() + * returned false. + */ + ++dumper->current_depth; + dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_NONE; + return true; +} + +/** + * Common code to close an object/array/base64 value, printing a + * closing character (and if necessary, it is preceded by newline + * and indentation). + * + * It also makes various correctness checks. + */ +static bool +json_dumper_end_nested_element(json_dumper *dumper, enum json_dumper_element_type type) +{ + if (!json_dumper_check_previous_error(dumper)) { + return false; + } + + uint8_t prev_state = json_dumper_get_prev_state(dumper); + + switch (type) { + case JSON_DUMPER_TYPE_OBJECT: + if (JSON_DUMPER_TYPE(prev_state) != JSON_DUMPER_TYPE_OBJECT) { + json_dumper_bad(dumper, "ending non-object nested item type as object"); + return false; + } + break; + case JSON_DUMPER_TYPE_ARRAY: + if (JSON_DUMPER_TYPE(prev_state) != JSON_DUMPER_TYPE_ARRAY) { + json_dumper_bad(dumper, "ending non-array nested item type as array"); + return false; + } + break; + case JSON_DUMPER_TYPE_BASE64: + if (JSON_DUMPER_TYPE(prev_state) != JSON_DUMPER_TYPE_BASE64) { + json_dumper_bad(dumper, "ending non-base64 nested item type as base64"); + return false; + } + break; + default: + json_dumper_bad(dumper, "ending unknown nested element type"); + return false; + } + + if (prev_state & JSON_DUMPER_HAS_NAME) { + json_dumper_bad(dumper, "finishing object with last item having name but no value"); + return false; + } + + /* Make sure we won't underflow the dumper stack */ + if (json_dumper_stack_would_underflow(dumper)) { + return false; + } + + // if the object/array was non-empty, add a newline and indentation. + if (dumper->state[dumper->current_depth]) { + print_newline_indent(dumper, dumper->current_depth - 1); + } + + switch (type) { + case JSON_DUMPER_TYPE_OBJECT: + jd_putc(dumper, '}'); + break; + case JSON_DUMPER_TYPE_ARRAY: + jd_putc(dumper, ']'); + break; + case JSON_DUMPER_TYPE_BASE64: + { + char buf[4]; + size_t wrote; + + wrote = g_base64_encode_close(false, buf, &dumper->base64_state, &dumper->base64_save); + jd_puts_len(dumper, buf, wrote); + + jd_putc(dumper, '"'); + break; + } + default: + json_dumper_bad(dumper, "endning unknown nested element type"); + return false; + } + + /* + * Guaranteed not to underflow, as json_dumper_stack_would_underflow() + * returned false. + */ + --dumper->current_depth; + return true; +} + +void +json_dumper_begin_object(json_dumper *dumper) +{ + json_dumper_begin_nested_element(dumper, JSON_DUMPER_TYPE_OBJECT); +} + +void +json_dumper_set_member_name(json_dumper *dumper, const char *name) +{ + if (!json_dumper_check_previous_error(dumper)) { + return; + } + + uint8_t prev_state = json_dumper_get_prev_state(dumper); + + /* Only object members, not array members, have names. */ + if (JSON_DUMPER_TYPE(prev_state) != JSON_DUMPER_TYPE_OBJECT) { + json_dumper_bad(dumper, "setting name on non-object nested item type"); + return; + } + /* An object member name can only be set once before its value is set. */ + if (prev_state & JSON_DUMPER_HAS_NAME) { + json_dumper_bad(dumper, "setting name twice on an object member"); + return; + } + + prepare_token(dumper); + json_puts_string(dumper, name, dumper->flags & JSON_DUMPER_DOT_TO_UNDERSCORE); + jd_putc(dumper, ':'); + if ((dumper->flags & JSON_DUMPER_FLAGS_PRETTY_PRINT)) { + jd_putc(dumper, ' '); + } + + dumper->state[dumper->current_depth - 1] |= JSON_DUMPER_HAS_NAME; +} + +void +json_dumper_end_object(json_dumper *dumper) +{ + json_dumper_end_nested_element(dumper, JSON_DUMPER_TYPE_OBJECT); +} + +void +json_dumper_begin_array(json_dumper *dumper) +{ + json_dumper_begin_nested_element(dumper, JSON_DUMPER_TYPE_ARRAY); +} + +void +json_dumper_end_array(json_dumper *dumper) +{ + json_dumper_end_nested_element(dumper, JSON_DUMPER_TYPE_ARRAY); +} + +static bool +json_dumper_setting_value_ok(json_dumper *dumper) +{ + uint8_t prev_state = json_dumper_get_prev_state(dumper); + + switch (JSON_DUMPER_TYPE(prev_state)) { + case JSON_DUMPER_TYPE_OBJECT: + /* + * This value is part of an object. As such, it must + * have a name. + */ + if (!(prev_state & JSON_DUMPER_HAS_NAME)) { + json_dumper_bad(dumper, "setting value of object member without a name"); + return false; + } + break; + case JSON_DUMPER_TYPE_ARRAY: + /* + * This value is part of an array. As such, it's not + * required to have a name (and shouldn't have a name; + * that's already been checked in json_dumper_set_member_name()). + */ + break; + case JSON_DUMPER_TYPE_BASE64: + /* + * We're in the middle of constructing a base64-encoded + * value. Only json_dumper_write_base64() can be used + * for that; we can't add individual values to it. + */ + json_dumper_bad(dumper, "attempt to set value of base64 item to something not base64-encoded"); + return false; + case JSON_DUMPER_TYPE_NONE: + case JSON_DUMPER_TYPE_VALUE: + { + uint8_t curr_state = json_dumper_get_curr_state(dumper); + switch (JSON_DUMPER_TYPE(curr_state)) { + case JSON_DUMPER_TYPE_NONE: + /* + * We haven't put a value yet, so we can put one now. + */ + break; + case JSON_DUMPER_TYPE_VALUE: + /* + * This value isn't part of an object or array, + * and we've already put one value. + */ + json_dumper_bad(dumper, "value not in object or array immediately follows another value"); + return false; + case JSON_DUMPER_TYPE_OBJECT: + case JSON_DUMPER_TYPE_ARRAY: + case JSON_DUMPER_TYPE_BASE64: + /* + * This should never be the case, no matter what + * our callers do: + * + * JSON_DUMPER_TYPE_OBJECT can be the previous + * type, meaning we're in the process of adding + * elements to an object, but it should never be + * the current type; + * + * JSON_DUMPER_TYPE_ARRAY can be the previous + * type, meaning we're in the process of adding + * elements to an array, but it should never be + * the current type; + * + * JSON_DUMPER_TYPE_BASE64 should only be the + * current type if we're in the middle of + * building a base64 value, in which case the + * previous type should also be JSON_DUMPER_TYPE_BASE64, + * but that's not the previous type. + */ + json_dumper_bad(dumper, "internal error setting value - should not happen"); + return false; + default: + json_dumper_bad(dumper, "internal error setting value, bad current state - should not happen"); + return false; + } + break; + } + default: + json_dumper_bad(dumper, "internal error setting value, bad previous state - should not happen"); + return false; + } + return true; +} + +void +json_dumper_value_string(json_dumper *dumper, const char *value) +{ + if (!json_dumper_check_previous_error(dumper)) { + return; + } + if (!json_dumper_setting_value_ok(dumper)) { + return; + } + + prepare_token(dumper); + json_puts_string(dumper, value, false); + + dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_VALUE; +} + +void +json_dumper_value_double(json_dumper *dumper, double value) +{ + if (!json_dumper_check_previous_error(dumper)) { + return; + } + + if (!json_dumper_setting_value_ok(dumper)) { + return; + } + + prepare_token(dumper); + char buffer[G_ASCII_DTOSTR_BUF_SIZE] = { 0 }; + if (isfinite(value) && g_ascii_dtostr(buffer, G_ASCII_DTOSTR_BUF_SIZE, value) && buffer[0]) { + jd_puts(dumper, buffer); + } else { + jd_puts(dumper, "null"); + } + + dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_VALUE; +} + +void +json_dumper_value_va_list(json_dumper *dumper, const char *format, va_list ap) +{ + if (!json_dumper_check_previous_error(dumper)) { + return; + } + + if (!json_dumper_setting_value_ok(dumper)) { + return; + } + + prepare_token(dumper); + jd_vprintf(dumper, format, ap); + + dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_VALUE; +} + +void +json_dumper_value_anyf(json_dumper *dumper, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + json_dumper_value_va_list(dumper, format, ap); + va_end(ap); +} + +bool +json_dumper_finish(json_dumper *dumper) +{ + if (!json_dumper_check_previous_error(dumper)) { + return false; + } + + if (dumper->current_depth != 0) { + json_dumper_bad(dumper, "JSON dumper stack not empty at finish"); + return false; + } + + jd_putc(dumper, '\n'); + dumper->state[0] = JSON_DUMPER_TYPE_NONE; + return true; +} + +void +json_dumper_begin_base64(json_dumper *dumper) +{ + json_dumper_begin_nested_element(dumper, JSON_DUMPER_TYPE_BASE64); +} + +void +json_dumper_write_base64(json_dumper* dumper, const unsigned char *data, size_t len) +{ + if (!json_dumper_check_previous_error(dumper)) { + return; + } + + uint8_t prev_state = json_dumper_get_prev_state(dumper); + + if (JSON_DUMPER_TYPE(prev_state) != JSON_DUMPER_TYPE_BASE64) { + json_dumper_bad(dumper, "writing base64 data to a non-base64 value"); + return; + } + + #define CHUNK_SIZE 1024 + char buf[(CHUNK_SIZE / 3 + 1) * 4 + 4]; + + while (len > 0) { + size_t chunk_size = len < CHUNK_SIZE ? len : CHUNK_SIZE; + size_t output_size = g_base64_encode_step(data, chunk_size, false, buf, &dumper->base64_state, &dumper->base64_save); + jd_puts_len(dumper, buf, output_size); + data += chunk_size; + len -= chunk_size; + } + + dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_BASE64; +} + +void +json_dumper_end_base64(json_dumper *dumper) +{ + json_dumper_end_nested_element(dumper, JSON_DUMPER_TYPE_BASE64); +} diff --git a/wsutil/json_dumper.h b/wsutil/json_dumper.h new file mode 100644 index 00000000..184960ae --- /dev/null +++ b/wsutil/json_dumper.h @@ -0,0 +1,140 @@ +/** @file + * Routines for serializing data as JSON. + * + * Copyright 2018, Peter Wu <peter@lekensteyn.nl> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __JSON_DUMPER_H__ +#define __JSON_DUMPER_H__ + +#include "ws_symbol_export.h" + +#include <inttypes.h> +#include <stdbool.h> +#include <stdio.h> + +#include <glib.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Example: + * + * json_dumper dumper = { + * .output_file = stdout, // or .output_string = g_string_new(NULL) + * .flags = JSON_DUMPER_FLAGS_PRETTY_PRINT, + * }; + * json_dumper_begin_object(&dumper); + * json_dumper_set_member_name(&dumper, "key"); + * json_dumper_value_string(&dumper, "value"); + * json_dumper_set_member_name(&dumper, "array"); + * json_dumper_begin_array(&dumper); + * json_dumper_value_anyf(&dumper, "true"); + * json_dumper_value_double(&dumper, 1.0); + * json_dumper_begin_base64(&dumper); + * json_dumper_write_base64(&dumper, (const unsigned char *)"abcd", 4); + * json_dumper_write_base64(&dumper, (const unsigned char *)"1234", 4); + * json_dumper_end_base64(&dumper); + * json_dumper_begin_object(&dumper); + * json_dumper_end_object(&dumper); + * json_dumper_begin_array(&dumper); + * json_dumper_end_array(&dumper); + * json_dumper_end_array(&dumper); + * json_dumper_end_object(&dumper); + * json_dumper_finish(&dumper); + */ + +/** Maximum object/array nesting depth. */ +#define JSON_DUMPER_MAX_DEPTH 1100 +typedef struct json_dumper { + FILE *output_file; /**< Output file. If it is not NULL, JSON will be dumped in the file. */ + GString *output_string; /**< Output GLib strings. If it is not NULL, JSON will be dumped in the string. */ +#define JSON_DUMPER_FLAGS_PRETTY_PRINT (1 << 0) /* Enable pretty printing. */ +#define JSON_DUMPER_DOT_TO_UNDERSCORE (1 << 1) /* Convert dots to underscores in keys */ +#define JSON_DUMPER_FLAGS_NO_DEBUG (1 << 17) /* Disable fatal ws_error messsges on error(intended for speeding up fuzzing). */ + int flags; + /* for internal use, initialize with zeroes. */ + unsigned current_depth; + int base64_state; + int base64_save; + uint8_t state[JSON_DUMPER_MAX_DEPTH]; +} json_dumper; + +WS_DLL_PUBLIC void +json_dumper_begin_object(json_dumper *dumper); + +WS_DLL_PUBLIC void +json_dumper_set_member_name(json_dumper *dumper, const char *name); + +WS_DLL_PUBLIC void +json_dumper_end_object(json_dumper *dumper); + +WS_DLL_PUBLIC void +json_dumper_begin_array(json_dumper *dumper); + +WS_DLL_PUBLIC void +json_dumper_end_array(json_dumper *dumper); + +WS_DLL_PUBLIC void +json_dumper_value_string(json_dumper *dumper, const char *value); + +WS_DLL_PUBLIC void +json_dumper_value_double(json_dumper *dumper, double value); + +/** + * Dump number, "true", "false" or "null" values. + */ +WS_DLL_PUBLIC void +json_dumper_value_anyf(json_dumper *dumper, const char *format, ...) +G_GNUC_PRINTF(2, 3); + +/** + * Dump literal values (like json_dumper_value_anyf), but taking a va_list + * as parameter. String values MUST be properly quoted by the caller, no + * escaping occurs. Do not use with untrusted data. + */ +WS_DLL_PUBLIC void +json_dumper_value_va_list(json_dumper *dumper, const char *format, va_list ap); + +WS_DLL_PUBLIC void +json_dumper_begin_base64(json_dumper *dumper); + +WS_DLL_PUBLIC void +json_dumper_end_base64(json_dumper *dumper); + +WS_DLL_PUBLIC void +json_dumper_write_base64(json_dumper *dumper, const unsigned char *data, size_t len); + +/** + * Finishes dumping data. Returns true if everything is okay and false if + * something went wrong (open/close mismatch, missing values, etc.). + */ +WS_DLL_PUBLIC bool +json_dumper_finish(json_dumper *dumper); + +#ifdef __cplusplus +} +#endif + +#endif /* __JSON_DUMPER_H__ */ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/mpeg-audio.c b/wsutil/mpeg-audio.c new file mode 100644 index 00000000..8e80a13f --- /dev/null +++ b/wsutil/mpeg-audio.c @@ -0,0 +1,122 @@ +/* mpeg-audio.c + * + * MPEG Audio header dissection + * Written by Shaun Jackman <sjackman@gmail.com> + * Copyright 2007 Shaun Jackman + * + * Wiretap Library + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "mpeg-audio.h" + +static const int mpa_versions[4] = { 2, -1, 1, 0 }; +static const int mpa_layers[4] = { -1, 2, 1, 0 }; + +static const unsigned int mpa_samples_data[3][3] = { + { 384, 1152, 1152 }, + { 384, 1152, 576 }, + { 384, 1152, 576 }, +}; + +static const unsigned int mpa_bitrates[3][3][16] = { /* kb/s */ + { + { 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448 }, + { 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384 }, + { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320 }, + }, + { + { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256 }, + { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 }, + { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 }, + }, + { + { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256 }, + { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 }, + { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 }, + }, +}; + +static const unsigned int mpa_frequencies[3][4] = { + { 44100, 48000, 32000 }, + { 22050, 24000, 16000 }, + { 11025, 12000, 8000 }, +}; + +static const unsigned int mpa_padding_data[3] = { 4, 1, 1 }; + +int +mpa_version(const struct mpa *mpa) +{ + return mpa_versions[mpa->version]; +} + +int +mpa_layer(const struct mpa *mpa) +{ + return mpa_layers[mpa->layer]; +} + +unsigned +mpa_samples(const struct mpa *mpa) +{ + return mpa_samples_data[mpa_versions[mpa->version]][mpa_layer(mpa)]; +} + +unsigned +mpa_bitrate(const struct mpa *mpa) +{ + return (1000 * (mpa_bitrates[mpa_versions[mpa->version]][mpa_layers[mpa->layer]][mpa->bitrate])); +} + +unsigned +mpa_frequency(const struct mpa *mpa) +{ + return(mpa_frequencies[mpa_versions[mpa->version]][mpa->frequency]); +} + +unsigned +mpa_padding(const struct mpa *mpa) +{ + return(mpa->padding ? mpa_padding_data[mpa_layers[mpa->layer]] : 0); +} + +/* Decode an ID3v2 synchsafe integer. + * See https://id3.org/id3v2.4.0-structure section 6.2. + */ +uint32_t +decode_synchsafe_int(uint32_t input) +{ + uint32_t value; + + /* High-order byte */ + value = (input >> 24) & 0x7f; + /* Shift the result left to make room for the next 7 bits */ + value <<= 7; + + /* Now OR in the 2nd byte */ + value |= (input >> 16) & 0x7f; + value <<= 7; + + /* ... and the 3rd */ + value |= (input >> 8) & 0x7f; + value <<= 7; + + /* For the 4th byte don't do the shift */ + value |= input & 0x7f; + + return value; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 8 + * tab-width: 8 + * indent-tabs-mode: t + * End: + * + * vi: set shiftwidth=8 tabstop=8 noexpandtab: + * :indentSize=8:tabSize=8:noTabs=false: + */ diff --git a/wsutil/mpeg-audio.h b/wsutil/mpeg-audio.h new file mode 100644 index 00000000..10ef36a4 --- /dev/null +++ b/wsutil/mpeg-audio.h @@ -0,0 +1,95 @@ +/** @file + * + * MPEG Audio header dissection + * Written by Shaun Jackman <sjackman@gmail.com> + * Copyright 2007 Shaun Jackman + * + * Wiretap Library + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef MPA_H +#define MPA_H 1 + +#include <stdint.h> +#include "ws_symbol_export.h" + +struct mpa { + unsigned int emphasis :2; + unsigned int original :1; + unsigned int copyright :1; + unsigned int modeext :2; + unsigned int mode :2; + unsigned int priv :1; + unsigned int padding :1; + unsigned int frequency :2; + unsigned int bitrate :4; + unsigned int protection :1; + unsigned int layer :2; + unsigned int version :2; + unsigned int sync :11; +}; + +#define MPA_UNMARSHAL_SYNC(n) ((n) >> 21 & 0x7ff) +#define MPA_UNMARSHAL_VERSION(n) ((n) >> 19 & 0x3) +#define MPA_UNMARSHAL_LAYER(n) ((n) >> 17 & 0x3) +#define MPA_UNMARSHAL_PROTECTION(n) ((n) >> 16 & 0x1) +#define MPA_UNMARSHAL_BITRATE(n) ((n) >> 12 & 0xf) +#define MPA_UNMARSHAL_FREQUENCY(n) ((n) >> 10 & 0x3) +#define MPA_UNMARSHAL_PADDING(n) ((n) >> 9 & 0x1) +#define MPA_UNMARSHAL_PRIVATE(n) ((n) >> 8 & 0x1) +#define MPA_UNMARSHAL_MODE(n) ((n) >> 6 & 0x3) +#define MPA_UNMARSHAL_MODEEXT(n) ((n) >> 4 & 0x3) +#define MPA_UNMARSHAL_COPYRIGHT(n) ((n) >> 3 & 0x1) +#define MPA_UNMARSHAL_ORIGINAL(n) ((n) >> 2 & 0x1) +#define MPA_UNMARSHAL_EMPHASIS(n) ((n) >> 0 & 0x3) + +#define MPA_UNMARSHAL(mpa, n) do { \ + (mpa)->sync = MPA_UNMARSHAL_SYNC(n); \ + (mpa)->version = MPA_UNMARSHAL_VERSION(n); \ + (mpa)->layer = MPA_UNMARSHAL_LAYER(n); \ + (mpa)->protection = MPA_UNMARSHAL_PROTECTION(n); \ + (mpa)->bitrate = MPA_UNMARSHAL_BITRATE(n); \ + (mpa)->frequency = MPA_UNMARSHAL_FREQUENCY(n); \ + (mpa)->padding = MPA_UNMARSHAL_PADDING(n); \ + (mpa)->priv = MPA_UNMARSHAL_PRIVATE(n); \ + (mpa)->mode = MPA_UNMARSHAL_MODE(n); \ + (mpa)->modeext = MPA_UNMARSHAL_MODEEXT(n); \ + (mpa)->copyright = MPA_UNMARSHAL_COPYRIGHT(n); \ + (mpa)->original = MPA_UNMARSHAL_ORIGINAL(n); \ + (mpa)->emphasis = MPA_UNMARSHAL_EMPHASIS(n); \ + } while (0) + +WS_DLL_PUBLIC +int mpa_version(const struct mpa *); +WS_DLL_PUBLIC +int mpa_layer(const struct mpa *); +WS_DLL_PUBLIC +unsigned int mpa_samples(const struct mpa *); +WS_DLL_PUBLIC +unsigned int mpa_bitrate(const struct mpa *); +WS_DLL_PUBLIC +unsigned int mpa_frequency(const struct mpa *); +WS_DLL_PUBLIC +unsigned int mpa_padding(const struct mpa *); +WS_DLL_PUBLIC +uint32_t decode_synchsafe_int(uint32_t); + +#define MPA_DATA_BYTES(mpa) (mpa_bitrate(mpa) * mpa_samples(mpa) \ + / mpa_frequency(mpa) / 8) +#define MPA_BYTES(mpa) (MPA_DATA_BYTES(mpa) + mpa_padding(mpa)) +#define MPA_DURATION_NS(mpa) \ + (1000000000 / mpa_frequency(mpa) * mpa_samples(mpa)) + +enum { MPA_SYNC = 0x7ff }; + +#define MPA_SYNC_VALID(mpa) ((mpa)->sync == MPA_SYNC) +#define MPA_VERSION_VALID(mpa) (mpa_version(mpa) >= 0) +#define MPA_LAYER_VALID(mpa) (mpa_layer(mpa) >= 0) +#define MPA_BITRATE_VALID(mpa) (mpa_bitrate(mpa) > 0) +#define MPA_FREQUENCY_VALID(mpa) (mpa_frequency(mpa) > 0) +#define MPA_VALID(mpa) (MPA_SYNC_VALID(mpa) \ + && MPA_VERSION_VALID(mpa) && MPA_LAYER_VALID(mpa) \ + && MPA_BITRATE_VALID(mpa) && MPA_FREQUENCY_VALID(mpa)) + +#endif diff --git a/wsutil/nstime.c b/wsutil/nstime.c new file mode 100644 index 00000000..1f58dbb4 --- /dev/null +++ b/wsutil/nstime.c @@ -0,0 +1,653 @@ +/* nstime.c + * Routines for manipulating nstime_t structures + * + * Copyright (c) 2005 MX Telecom Ltd. <richardv@mxtelecom.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "nstime.h" + +#include <stdio.h> +#include <string.h> +#include "epochs.h" +#include "time_util.h" +#include "to_str.h" +#include "strtoi.h" + +/* this is #defined so that we can clearly see that we have the right number of + zeros, rather than as a guard against the number of nanoseconds in a second + changing ;) */ +#define NS_PER_S 1000000000 + +/* set the given nstime_t to zero */ +void nstime_set_zero(nstime_t *nstime) +{ + nstime->secs = 0; + nstime->nsecs = 0; +} + +/* is the given nstime_t currently zero? */ +bool nstime_is_zero(const nstime_t *nstime) +{ + return nstime->secs == 0 && nstime->nsecs == 0; +} + +/* set the given nstime_t to (0,maxint) to mark it as "unset" + * That way we can find the first frame even when a timestamp + * is zero (fix for bug 1056) + */ +void nstime_set_unset(nstime_t *nstime) +{ + nstime->secs = 0; + nstime->nsecs = INT_MAX; +} + +/* is the given nstime_t currently (0,maxint)? */ +bool nstime_is_unset(const nstime_t *nstime) +{ + if(nstime->secs == 0 && nstime->nsecs == INT_MAX) { + return true; + } else { + return false; + } +} + + +/** function: nstime_copy + * + * a = b + */ +void nstime_copy(nstime_t *a, const nstime_t *b) +{ + a->secs = b->secs; + a->nsecs = b->nsecs; +} + +/* + * function: nstime_delta + * delta = b - a + */ + +void nstime_delta(nstime_t *delta, const nstime_t *b, const nstime_t *a ) +{ + if (b->secs == a->secs) { + /* The seconds part of b is the same as the seconds part of a, so if + the nanoseconds part of the first time is less than the nanoseconds + part of a, b is before a. The nanoseconds part of the delta should + just be the difference between the nanoseconds part of b and the + nanoseconds part of a; don't adjust the seconds part of the delta, + as it's OK if the nanoseconds part is negative, and an overflow + can never result. */ + delta->secs = 0; + delta->nsecs = b->nsecs - a->nsecs; + } else if (b->secs < a->secs) { + /* The seconds part of b is less than the seconds part of a, so b is + before a. + + Both the "seconds" and "nanoseconds" value of the delta + should have the same sign, so if the difference between the + nanoseconds values would be *positive*, subtract 1,000,000,000 + from it, and add one to the seconds value. */ + delta->secs = b->secs - a->secs; + delta->nsecs = b->nsecs - a->nsecs; + if(delta->nsecs > 0) { + delta->nsecs -= NS_PER_S; + delta->secs ++; + } + } else { + delta->secs = b->secs - a->secs; + delta->nsecs = b->nsecs - a->nsecs; + if(delta->nsecs < 0) { + delta->nsecs += NS_PER_S; + delta->secs --; + } + } +} + +/* + * function: nstime_sum + * sum = a + b + */ + +void nstime_sum(nstime_t *sum, const nstime_t *a, const nstime_t *b) +{ + sum->secs = a->secs + b->secs; + sum->nsecs = a->nsecs + b->nsecs; + if(sum->nsecs>=NS_PER_S || (sum->nsecs>0 && sum->secs<0)){ + sum->nsecs-=NS_PER_S; + sum->secs++; + } else if(sum->nsecs<=-NS_PER_S || (sum->nsecs<0 && sum->secs>0)) { + sum->nsecs+=NS_PER_S; + sum->secs--; + } +} + +/* + * function: nstime_cmp + * + * a > b : > 0 + * a = b : 0 + * a < b : < 0 + */ + +int nstime_cmp (const nstime_t *a, const nstime_t *b ) +{ + if (G_UNLIKELY(nstime_is_unset(a))) { + if (G_UNLIKELY(nstime_is_unset(b))) { + return 0; /* "no time stamp" is "equal" to "no time stamp" */ + } else { + return -1; /* and is less than all time stamps */ + } + } else { + if (G_UNLIKELY(nstime_is_unset(b))) { + return 1; + } + } + if (a->secs == b->secs) { + return a->nsecs - b->nsecs; + } else { + return (int) (a->secs - b->secs); + } +} + +unsigned nstime_hash(const nstime_t *nstime) +{ + int64_t val1 = (int64_t)nstime->secs; + + return g_int64_hash(&val1) ^ g_int_hash(&nstime->nsecs); +} + +/* + * function: nstime_to_msec + * converts nstime to double, time base is milli seconds + */ + +double nstime_to_msec(const nstime_t *nstime) +{ + return ((double)nstime->secs*1000 + (double)nstime->nsecs/1000000); +} + +/* + * function: nstime_to_sec + * converts nstime to double, time base is seconds + */ + +double nstime_to_sec(const nstime_t *nstime) +{ + return ((double)nstime->secs + (double)nstime->nsecs/NS_PER_S); +} + +/* + * This code is based on the Samba code: + * + * Unix SMB/Netbios implementation. + * Version 1.9. + * time handling functions + * Copyright (C) Andrew Tridgell 1992-1998 + */ + +#ifndef TIME_T_MIN +#define TIME_T_MIN ((time_t) ((time_t)0 < (time_t) -1 ? (time_t) 0 \ + : (time_t) (~0ULL << (sizeof (time_t) * CHAR_BIT - 1)))) +#endif +#ifndef TIME_T_MAX +#define TIME_T_MAX ((time_t) (~ (time_t) 0 - TIME_T_MIN)) +#endif + +static bool +common_filetime_to_nstime(nstime_t *nstime, uint64_t ftsecs, int nsecs) +{ + int64_t secs; + + /* + * Shift the seconds from the Windows epoch to the UN*X epoch. + * ftsecs's value should fit in a 64-bit signed variable, as + * ftsecs is derived from a 64-bit fractions-of-a-second value, + * and is far from the maximum 64-bit signed value, and + * EPOCH_DELTA_1601_01_01_00_00_00_UTC is also far from the + * maximum 64-bit signed value, so the difference between them + * should also fit in a 64-bit signed value. + */ + secs = (int64_t)ftsecs - EPOCH_DELTA_1601_01_01_00_00_00_UTC; + + if (!(TIME_T_MIN <= secs && secs <= TIME_T_MAX)) { + /* The result won't fit in a time_t */ + return false; + } + + /* + * Get the time as seconds and nanoseconds. + */ + nstime->secs = (time_t) secs; + nstime->nsecs = nsecs; + return true; +} + +/* + * function: filetime_to_nstime + * converts a Windows FILETIME value to an nstime_t + * returns true if the conversion succeeds, false if it doesn't + * (for example, with a 32-bit time_t, the time overflows or + * underflows time_t) + */ +bool +filetime_to_nstime(nstime_t *nstime, uint64_t filetime) +{ + uint64_t ftsecs; + int nsecs; + + /* + * Split into seconds and tenths of microseconds, and + * then convert tenths of microseconds to nanoseconds. + */ + ftsecs = filetime / 10000000; + nsecs = (int)((filetime % 10000000)*100); + + return common_filetime_to_nstime(nstime, ftsecs, nsecs); +} + +/* + * function: nsfiletime_to_nstime + * converts a Windows FILETIME-like value, but given in nanoseconds + * rather than 10ths of microseconds, to an nstime_t + * returns true if the conversion succeeds, false if it doesn't + * (for example, with a 32-bit time_t, the time overflows or + * underflows time_t) + */ +bool +nsfiletime_to_nstime(nstime_t *nstime, uint64_t nsfiletime) +{ + uint64_t ftsecs; + int nsecs; + + /* Split into seconds and nanoseconds. */ + ftsecs = nsfiletime / NS_PER_S; + nsecs = (int)(nsfiletime % NS_PER_S); + + return common_filetime_to_nstime(nstime, ftsecs, nsecs); +} + +/* + * function: iso8601_to_nstime + * parses a character string for a date and time given in + * ISO 8601 date-time format (eg: 2014-04-07T05:41:56.782+00:00) + * and converts to an nstime_t + * returns pointer to the first character after the last character + * parsed on success, or NULL on failure + * + * NB. ISO 8601 is actually a lot more flexible than the above format, + * much to a developer's chagrin. The "basic format" is distinguished from + * the "extended format" by lacking the - and : separators. This function + * supports both the basic and extended format (as well as both simultaneously) + * with several common options and extensions. Time resolution is supported + * up to nanoseconds (9 fractional digits) or down to whole minutes (omitting + * the seconds component in the latter case). The T separator can be replaced + * by a space in either format (a common extension not in ISO 8601 but found + * in, e.g., RFC 3339) or omitted entirely in the basic format. + * + * Many standards that use ISO 8601 implement profiles with additional + * constraints, such as requiring that the seconds field be present, only + * allowing "." as the decimal separator, or limiting the number of fractional + * digits. Callers that wish to check constraints not yet enforced by a + * profile supported by the function must do so themselves. + * + * Future improvements could parse other ISO 8601 formats, such as + * YYYY-Www-D, YYYY-DDD, etc. For a relatively easy introduction to + * these formats, see wikipedia: https://en.wikipedia.org/wiki/ISO_8601 + */ +const char * +iso8601_to_nstime(nstime_t *nstime, const char *ptr, iso8601_fmt_e format) +{ + struct tm tm; + int n_scanned = 0; + int n_chars = 0; + unsigned frac = 0; + int off_hr = 0; + int off_min = 0; + char sign = '\0'; + bool has_separator = false; + bool have_offset = false; + + memset(&tm, 0, sizeof(tm)); + tm.tm_isdst = -1; + nstime_set_unset(nstime); + + /* Verify that we start with a four digit year and then look for the + * separator. */ + for (n_scanned = 0; n_scanned < 4; n_scanned++) { + if (!g_ascii_isdigit(*ptr)) { + return NULL; + } + tm.tm_year *= 10; + tm.tm_year += *ptr++ - '0'; + } + if (*ptr == '-') { + switch (format) { + case ISO8601_DATETIME_BASIC: + return NULL; + + case ISO8601_DATETIME: + case ISO8601_DATETIME_AUTO: + default: + has_separator = true; + ptr++; + }; + } else if (g_ascii_isdigit(*ptr)) { + switch (format) { + case ISO8601_DATETIME: + return NULL; + + case ISO8601_DATETIME_BASIC: + case ISO8601_DATETIME_AUTO: + default: + has_separator = false; + }; + } else { + return NULL; + } + + tm.tm_year -= 1900; /* struct tm expects number of years since 1900 */ + + /* Note: sscanf is known to be inconsistent across platforms with respect + to whether a %n is counted as a return value or not (XXX: Is this + still true, despite the express comments of C99 §7.19.6.2 12?), so we + use '<'/'>=' + */ + /* XXX: sscanf allows an optional sign indicator before each integer + * converted (whether with %d or %u), so this will convert some bogus + * strings. Either checking afterwards or doing the whole thing by hand + * as with the year above is the only correct way. (strptime certainly + * can't handle the basic format.) + */ + n_scanned = sscanf(ptr, has_separator ? "%2u-%2u%n" : "%2u%2u%n", + &tm.tm_mon, + &tm.tm_mday, + &n_chars); + if (n_scanned >= 2) { + /* Got year, month, and day */ + tm.tm_mon--; /* struct tm expects 0-based month */ + ptr += n_chars; + } + else { + return NULL; + } + + if (*ptr == 'T' || *ptr == ' ') { + /* The 'T' between date and time is optional if the meaning is + unambiguous. We also allow for ' ' here per RFC 3339 to support + formats such as editcap's -A/-B options. */ + ptr++; + } + else if (has_separator) { + /* Allow no separator between date and time iff we have no + separator between units. (Some extended formats may negotiate + no separator here, so this could be changed.) */ + return NULL; + } + + /* Now we're on to the time part. We'll require a minimum of hours and + minutes. */ + + n_scanned = sscanf(ptr, has_separator ? "%2u:%2u%n" : "%2u%2u%n", + &tm.tm_hour, + &tm.tm_min, + &n_chars); + if (n_scanned >= 2) { + ptr += n_chars; + } + else { + /* didn't get hours and minutes */ + return NULL; + } + + /* Test for (whole) seconds */ + if ((has_separator && *ptr == ':') || + (!has_separator && g_ascii_isdigit(*ptr))) { + /* Looks like we should have them */ + if (1 > sscanf(ptr, has_separator ? ":%2u%n" : "%2u%n", + &tm.tm_sec, &n_chars)) { + /* Couldn't get them */ + return NULL; + } + ptr += n_chars; + + /* Now let's test for fractional seconds */ + if (*ptr == '.' || *ptr == ',') { + /* Get fractional seconds */ + ptr++; + if (1 <= sscanf(ptr, "%u%n", &frac, &n_chars)) { + /* normalize frac to nanoseconds */ + if ((frac >= 1000000000) || (frac == 0)) { + frac = 0; + } else { + switch (n_chars) { /* including leading zeros */ + case 1: frac *= 100000000; break; + case 2: frac *= 10000000; break; + case 3: frac *= 1000000; break; + case 4: frac *= 100000; break; + case 5: frac *= 10000; break; + case 6: frac *= 1000; break; + case 7: frac *= 100; break; + case 8: frac *= 10; break; + default: break; + } + } + ptr += n_chars; + } + /* If we didn't get frac, it's still its default of 0 */ + } + } + else { + /* No seconds. ISO 8601 allows decimal fractions of a minute here, + * but that's pretty rare in practice. Could be added later if needed. + */ + tm.tm_sec = 0; + } + + /* Validate what we got so far. mktime() doesn't care about strange + values but we should at least start with something valid */ + if (!tm_is_valid(&tm)) { + return NULL; + } + + /* Check for a time zone offset */ + if (*ptr == '-' || *ptr == '+' || *ptr == 'Z') { + /* Just in case somewhere decides to observe a timezone of -00:30 or + * some such. */ + sign = *ptr; + /* We have a UTC-relative offset */ + if (*ptr == 'Z') { + off_hr = off_min = 0; + have_offset = true; + ptr++; + } + else { + off_hr = off_min = 0; + n_scanned = sscanf(ptr, "%3d%n", &off_hr, &n_chars); + if (n_scanned >= 1) { + /* Definitely got hours */ + have_offset = true; + ptr += n_chars; + n_scanned = sscanf(ptr, *ptr == ':' ? ":%2d%n" : "%2d%n", &off_min, &n_chars); + if (n_scanned >= 1) { + /* Got minutes too */ + ptr += n_chars; + } + } + else { + /* Didn't get a valid offset, treat as if there's none at all */ + have_offset = false; + } + } + } + if (have_offset) { + nstime->secs = mktime_utc(&tm); + if (sign == '+') { + nstime->secs -= (off_hr * 3600) + (off_min * 60); + } else if (sign == '-') { + /* -00:00 is illegal according to ISO 8601, but RFC 3339 allows + * it under a convention where -00:00 means "time in UTC is known, + * local timezone is unknown." This has the same value as an + * offset of Z or +00:00, but semantically implies that UTC is + * not the preferred time zone, which is immaterial to us. + */ + /* Add the time, but reverse the sign of off_hr, which includes + * the negative sign. + */ + nstime->secs += ((-off_hr) * 3600) + (off_min * 60); + } + } + else { + /* No UTC offset given; ISO 8601 says this means local time */ + nstime->secs = mktime(&tm); + } + nstime->nsecs = frac; + return ptr; +} + +/* + * function: unix_epoch_to_nstime + * parses a character string for a date and time given in + * a floating point number containing a Unix epoch date-time + * format (e.g. 1600000000.000 for Sun Sep 13 05:26:40 AM PDT 2020) + * and converts to an nstime_t + * returns pointer to the first character after the last character + * parsed on success, or NULL on failure + * + * Reference: https://en.wikipedia.org/wiki/Unix_time + */ +const char * +unix_epoch_to_nstime(nstime_t *nstime, const char *ptr) +{ + int64_t secs; + const char *ptr_new; + + int n_chars = 0; + unsigned frac = 0; + + nstime_set_unset(nstime); + + /* + * Extract the seconds as a 64-bit signed number, as time_t + * might be 64-bit. + */ + if (!ws_strtoi64(ptr, &ptr_new, &secs)) { + return NULL; + } + + /* For now, reject times before the Epoch. */ + if (secs < 0) { + return NULL; + } + + /* Make sure it fits. */ + nstime->secs = (time_t) secs; + if (nstime->secs != secs) { + return NULL; + } + + /* Now let's test for fractional seconds */ + if (*ptr_new == '.' || *ptr_new == ',') { + /* Get fractional seconds */ + ptr_new++; + if (1 <= sscanf(ptr_new, "%u%n", &frac, &n_chars)) { + /* normalize frac to nanoseconds */ + if ((frac >= 1000000000) || (frac == 0)) { + frac = 0; + } else { + switch (n_chars) { /* including leading zeros */ + case 1: frac *= 100000000; break; + case 2: frac *= 10000000; break; + case 3: frac *= 1000000; break; + case 4: frac *= 100000; break; + case 5: frac *= 10000; break; + case 6: frac *= 1000; break; + case 7: frac *= 100; break; + case 8: frac *= 10; break; + default: break; + } + } + ptr_new += n_chars; + } + /* If we didn't get frac, it's still its default of 0 */ + } + else { + frac = 0; + } + nstime->nsecs = frac; + return ptr_new; +} + +size_t nstime_to_iso8601(char *buf, size_t buf_size, const nstime_t *nstime) +{ + struct tm *tm; +#ifndef _WIN32 + struct tm tm_time; +#endif + size_t len; + +#ifdef _WIN32 + /* + * Do not use gmtime_s(), as it will call and + * exception handler if the time we're providing + * is < 0, and that will, by default, exit. + * ("Programmers not bothering to check return + * values? Try new Microsoft Visual Studio, + * with Parameter Validation(R)! Kill insufficiently + * careful programs - *and* the processes running them - + * fast!") + * + * We just want to report this as an unrepresentable + * time. It fills in a per-thread structure, which + * is sufficiently thread-safe for our purposes. + */ + tm = gmtime(&nstime->secs); +#else + /* + * Use gmtime_r(), because the Single UNIX Specification + * does *not* guarantee that gmtime() is thread-safe. + * Perhaps it is on all platforms on which we run, but + * this way we don't have to check. + */ + tm = gmtime_r(&nstime->secs, &tm_time); +#endif + if (tm == NULL) { + return 0; + } + + /* Some platforms (MinGW-w64) do not support %F or %T. */ + /* Returns number of bytes, excluding terminaning null, placed in + * buf, or zero if there is not enough space for the whole string. */ + len = strftime(buf, buf_size, "%Y-%m-%dT%H:%M:%S", tm); + if (len == 0) { + return 0; + } + ws_assert(len < buf_size); + buf += len; + buf_size -= len; + len += snprintf(buf, buf_size, ".%09dZ", nstime->nsecs); + return len; +} + +void nstime_to_unix(char *buf, size_t buf_size, const nstime_t *nstime) +{ + display_signed_time(buf, buf_size, nstime, WS_TSPREC_NSEC); +} + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/nstime.h b/wsutil/nstime.h new file mode 100644 index 00000000..acc78b53 --- /dev/null +++ b/wsutil/nstime.h @@ -0,0 +1,186 @@ +/* nstime.h + * Definition of data structure to hold time values with nanosecond resolution + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __NSTIME_H__ +#define __NSTIME_H__ + +#include <wireshark.h> +#include <time.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** @file + * Definition of data structure to hold time values with nanosecond resolution + */ + +/** data structure to hold time values with nanosecond resolution*/ +typedef struct { + time_t secs; + int nsecs; +} nstime_t; + +/* Macros that expand to nstime_t initializers */ + +/* Initialize to zero */ +#define NSTIME_INIT_ZERO {0, 0} + +/* Initialize to unset */ +#define NSTIME_INIT_UNSET {0, INT_MAX} + +/* Initialize to a specified number of seconds and nanoseconds */ +#define NSTIME_INIT_SECS_NSECS(secs, nsecs) {secs, nsecs} + +/* Initialize to a specified number of seconds and microseconds */ +#define NSTIME_INIT_SECS_USECS(secs, usecs) {secs, usecs*1000} + +/* Initialize to a specified number of seconds and milliseconds */ +#define NSTIME_INIT_SECS_MSECS(secs, msecs) {secs, msecs*1000000} + +/* Initialize to a specified number of seconds */ +#define NSTIME_INIT_SECS(secs) {secs, 0} + +/* Initialize to the maximum possible value */ +#define NSTIME_INIT_MAX {sizeof(time_t) > sizeof(int) ? LONG_MAX : INT_MAX, INT_MAX} + +/* functions */ + +/** set the given nstime_t to zero */ +WS_DLL_PUBLIC void nstime_set_zero(nstime_t *nstime); + +/** is the given nstime_t currently zero? */ +WS_DLL_PUBLIC bool nstime_is_zero(const nstime_t *nstime); + +/** set the given nstime_t to (0,maxint) to mark it as "unset" + * That way we can find the first frame even when a timestamp + * is zero (fix for bug 1056) + */ +WS_DLL_PUBLIC void nstime_set_unset(nstime_t *nstime); + +/* is the given nstime_t currently (0,maxint)? */ +WS_DLL_PUBLIC bool nstime_is_unset(const nstime_t *nstime); + +/** duplicate the current time + * + * a = b + */ +WS_DLL_PUBLIC void nstime_copy(nstime_t *a, const nstime_t *b); + +/** calculate the delta between two times (can be negative!) + * + * delta = b-a + * + * Note that it is acceptable for two or more of the arguments to point at the + * same structure. + */ +WS_DLL_PUBLIC void nstime_delta(nstime_t *delta, const nstime_t *b, const nstime_t *a ); + +/** calculate the sum of two times + * + * sum = a+b + * + * Note that it is acceptable for two or more of the arguments to point at the + * same structure. + */ +WS_DLL_PUBLIC void nstime_sum(nstime_t *sum, const nstime_t *a, const nstime_t *b ); + +/** sum += a */ +#define nstime_add(sum, a) nstime_sum(sum, sum, a) + +/** sum -= a */ +#define nstime_subtract(sum, a) nstime_delta(sum, sum, a) + +/** compare two times are return a value similar to memcmp() or strcmp(). + * + * a > b : > 0 + * a = b : 0 + * a < b : < 0 + */ +WS_DLL_PUBLIC int nstime_cmp (const nstime_t *a, const nstime_t *b ); + +WS_DLL_PUBLIC unsigned nstime_hash(const nstime_t *nstime); + +/** converts nstime to double, time base is milli seconds */ +WS_DLL_PUBLIC double nstime_to_msec(const nstime_t *nstime); + +/** converts nstime to double, time base is seconds */ +WS_DLL_PUBLIC double nstime_to_sec(const nstime_t *nstime); + +/** converts Windows FILETIME to nstime, returns true on success, + false on failure */ +WS_DLL_PUBLIC bool filetime_to_nstime(nstime_t *nstime, uint64_t filetime); + +/** converts time like Windows FILETIME, but expressed in nanoseconds + rather than tenths of microseconds, to nstime, returns true on success, + false on failure */ +WS_DLL_PUBLIC bool nsfiletime_to_nstime(nstime_t *nstime, uint64_t nsfiletime); + +typedef enum { + ISO8601_DATETIME, /** e.g. 2014-07-04T12:34:56.789+00:00 */ + ISO8601_DATETIME_BASIC, /** ISO8601 Basic format, i.e. no - : separators */ + ISO8601_DATETIME_AUTO, /** Autodetect the presence of separators */ +} iso8601_fmt_e; + +/** parse an ISO 8601 format datetime string to nstime, returns pointer + to the first character after the last character, NULL on failure + Note that nstime is set to unset in the case of failure */ +WS_DLL_PUBLIC const char * iso8601_to_nstime(nstime_t *nstime, const char *ptr, iso8601_fmt_e format); + +/** parse an Unix epoch timestamp format datetime string to nstime, returns + pointer to the first character after the last character, NULL on failure + Note that nstime is set to unset in the case of failure */ +WS_DLL_PUBLIC const char * unix_epoch_to_nstime(nstime_t *nstime, const char *ptr); + +#define NSTIME_ISO8601_BUFSIZE sizeof("YYYY-MM-DDTHH:MM:SS.123456789Z") + +WS_DLL_PUBLIC size_t nstime_to_iso8601(char *buf, size_t buf_size, const nstime_t *nstime); + +/* 64 bit signed number plus nanosecond fractional part */ +#define NSTIME_UNIX_BUFSIZE (20+10+1) + +WS_DLL_PUBLIC void nstime_to_unix(char *buf, size_t buf_size, const nstime_t *nstime); + +/* + * Timestamp precision values. + * + * The value is the number of digits of precision after the integral part. + */ +typedef enum { + WS_TSPREC_SEC = 0, + WS_TSPREC_100_MSEC = 1, + WS_TSPREC_10_MSEC = 2, + WS_TSPREC_MSEC = 3, + WS_TSPREC_100_USEC = 4, + WS_TSPREC_10_USEC = 5, + WS_TSPREC_USEC = 6, + WS_TSPREC_100_NSEC = 7, + WS_TSPREC_10_NSEC = 8, + WS_TSPREC_NSEC = 9 +} ws_tsprec_e; + +/* + * Maximum time stamp precision supported. + * Note that going beyond nanosecond precision would require expanding + * the fractional part of an nstime_t to 64 bits, and changing code + * that currently only handles second to nanosecond precision. + */ +#define WS_TSPREC_MAX 9 + +/* + * Total number of valid precision values. + */ +#define NUM_WS_TSPREC_VALS (WS_TSPREC_MAX + 1) + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __NSTIME_H__ */ diff --git a/wsutil/os_version_info.c b/wsutil/os_version_info.c new file mode 100644 index 00000000..69a80c8f --- /dev/null +++ b/wsutil/os_version_info.c @@ -0,0 +1,806 @@ +/* os_version_info.c + * Routines to report operating system version information + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" + +#include <wsutil/os_version_info.h> + +#include <string.h> +#include <errno.h> + +#ifdef HAVE_SYS_UTSNAME_H +#include <sys/utsname.h> +#endif + +#ifdef HAVE_MACOS_FRAMEWORKS +#include <CoreFoundation/CoreFoundation.h> +#include <wsutil/cfutils.h> +#endif + +#include <wsutil/unicode-utils.h> + +/* + * Handles the rather elaborate process of getting OS version information + * from macOS (we want the macOS version, not the Darwin version, the latter + * being easy to get with uname()). + */ +#ifdef HAVE_MACOS_FRAMEWORKS + +/* + * Fetch a string, as a UTF-8 C string, from a dictionary, given a key. + */ +static char * +get_string_from_dictionary(CFPropertyListRef dict, CFStringRef key) +{ + CFStringRef cfstring; + + cfstring = (CFStringRef)CFDictionaryGetValue((CFDictionaryRef)dict, + (const void *)key); + if (cfstring == NULL) + return NULL; + if (CFGetTypeID(cfstring) != CFStringGetTypeID()) { + /* It isn't a string. Punt. */ + return NULL; + } + return CFString_to_C_string(cfstring); +} + +/* + * Get the macOS version information, and append it to the GString. + * Return true if we succeed, false if we fail. + * + * XXX - this gives the OS name as "Mac OS X" even if Apple called/calls + * it "OS X" or "macOS". + */ +static bool +get_macos_version_info(GString *str) +{ + static const UInt8 server_version_plist_path[] = + "/System/Library/CoreServices/ServerVersion.plist"; + static const UInt8 system_version_plist_path[] = + "/System/Library/CoreServices/SystemVersion.plist"; + CFURLRef version_plist_file_url; + CFReadStreamRef version_plist_stream; + CFDictionaryRef version_dict; + char *string; + + /* + * On macOS, report the macOS version number as the OS, and put + * the Darwin information in parentheses. + * + * Alas, Gestalt() is deprecated in Mountain Lion, so the build + * fails if you treat deprecation warnings as fatal. I don't + * know of any replacement API, so we fall back on reading + * /System/Library/CoreServices/ServerVersion.plist if it + * exists, otherwise /System/Library/CoreServices/SystemVersion.plist, + * and using ProductUserVisibleVersion. We also get the build + * version from ProductBuildVersion and the product name from + * ProductName. + */ + version_plist_file_url = CFURLCreateFromFileSystemRepresentation(NULL, + server_version_plist_path, sizeof server_version_plist_path - 1, + false); + if (version_plist_file_url == NULL) + return false; + version_plist_stream = CFReadStreamCreateWithFile(NULL, + version_plist_file_url); + CFRelease(version_plist_file_url); + if (version_plist_stream == NULL) + return false; + if (!CFReadStreamOpen(version_plist_stream)) { + CFRelease(version_plist_stream); + + /* + * Try SystemVersion.plist. + */ + version_plist_file_url = CFURLCreateFromFileSystemRepresentation(NULL, + system_version_plist_path, sizeof system_version_plist_path - 1, + false); + if (version_plist_file_url == NULL) + return false; + version_plist_stream = CFReadStreamCreateWithFile(NULL, + version_plist_file_url); + CFRelease(version_plist_file_url); + if (version_plist_stream == NULL) + return false; + if (!CFReadStreamOpen(version_plist_stream)) { + CFRelease(version_plist_stream); + return false; + } + } +#ifdef HAVE_CFPROPERTYLISTCREATEWITHSTREAM + version_dict = (CFDictionaryRef)CFPropertyListCreateWithStream(NULL, + version_plist_stream, 0, kCFPropertyListImmutable, + NULL, NULL); +#else + version_dict = (CFDictionaryRef)CFPropertyListCreateFromStream(NULL, + version_plist_stream, 0, kCFPropertyListImmutable, + NULL, NULL); +#endif + if (version_dict == NULL) { + CFRelease(version_plist_stream); + return false; + } + if (CFGetTypeID(version_dict) != CFDictionaryGetTypeID()) { + /* This is *supposed* to be a dictionary. Punt. */ + CFRelease(version_dict); + CFReadStreamClose(version_plist_stream); + CFRelease(version_plist_stream); + return false; + } + /* Get the product name string. */ + string = get_string_from_dictionary(version_dict, + CFSTR("ProductName")); + if (string == NULL) { + CFRelease(version_dict); + CFReadStreamClose(version_plist_stream); + CFRelease(version_plist_stream); + return false; + } + g_string_append_printf(str, "%s", string); + g_free(string); + + /* Get the OS version string. */ + string = get_string_from_dictionary(version_dict, + CFSTR("ProductUserVisibleVersion")); + if (string == NULL) { + CFRelease(version_dict); + CFReadStreamClose(version_plist_stream); + CFRelease(version_plist_stream); + return false; + } + g_string_append_printf(str, " %s", string); + g_free(string); + + /* Get the build string */ + string = get_string_from_dictionary(version_dict, + CFSTR("ProductBuildVersion")); + if (string == NULL) { + CFRelease(version_dict); + CFReadStreamClose(version_plist_stream); + CFRelease(version_plist_stream); + return false; + } + g_string_append_printf(str, ", build %s", string); + g_free(string); + CFRelease(version_dict); + CFReadStreamClose(version_plist_stream); + CFRelease(version_plist_stream); + return true; +} +#endif + +#ifdef _WIN32 +typedef LONG (WINAPI * RtlGetVersionProc) (OSVERSIONINFOEX *); +#ifndef STATUS_SUCCESS +#define STATUS_SUCCESS 0 +#endif +#include <stdlib.h> + +/* + * Determine whether it's 32-bit or 64-bit Windows based on the + * instruction set; this only tests for the instruction sets + * that we currently support for Windows, it doesn't bother with MIPS, + * PowerPC, Alpha, or IA-64, nor does it bother wieth 32-bit ARM. + */ +static void +add_os_bitsize(GString *str, SYSTEM_INFO *system_info) +{ + switch (system_info->wProcessorArchitecture) { + case PROCESSOR_ARCHITECTURE_AMD64: +#ifdef PROCESSOR_ARCHITECTURE_ARM64 + case PROCESSOR_ARCHITECTURE_ARM64: +#endif + g_string_append(str, "64-bit "); + break; + case PROCESSOR_ARCHITECTURE_INTEL: + g_string_append(str, "32-bit "); + break; + default: + break; + } +} + +/* + * Test whether the OS an "NT Workstation" version, meaning "not server". + */ +static bool +is_nt_workstation(OSVERSIONINFOEX *win_version_info) +{ + return win_version_info->wProductType == VER_NT_WORKSTATION; +} +#endif // _WIN32 + +/* + * Get the OS version, and append it to the GString + */ +void +get_os_version_info(GString *str) +{ +#if defined(_WIN32) + + OSVERSIONINFOEX win_version_info = {0}; + RtlGetVersionProc RtlGetVersionP = 0; + LONG version_status = STATUS_ENTRYPOINT_NOT_FOUND; // Any nonzero value should work. + + /* + * We want the major and minor Windows version along with other + * information. GetVersionEx provides this, but is deprecated. + * We use RtlGetVersion instead, which requires a bit of extra + * effort. + */ + + HMODULE ntdll_module = LoadLibrary(_T("ntdll.dll")); + if (ntdll_module) { +DIAG_OFF(cast-function-type) + RtlGetVersionP = (RtlGetVersionProc) GetProcAddress(ntdll_module, "RtlGetVersion"); +DIAG_ON(cast-function-type) + } + + if (RtlGetVersionP) { + win_version_info.dwOSVersionInfoSize = sizeof(win_version_info); + version_status = RtlGetVersionP(&win_version_info); + } + + if (ntdll_module) { + FreeLibrary(ntdll_module); + } + + if (version_status != STATUS_SUCCESS) { + /* + * XXX - get the failure reason. + */ + g_string_append(str, "unknown Windows version"); + return; + } + + SYSTEM_INFO system_info; + memset(&system_info, '\0', sizeof system_info); + /* + * Look for and use the GetNativeSystemInfo() function to get the + * correct processor architecture even when running 32-bit Wireshark + * in WOW64 (x86 emulation on 64-bit Windows). + * + * However, the documentation for GetNativeSystemInfo() says + * + * If the function is called from an x86 or x64 application + * running on a 64-bit system that does not have an Intel64 + * or x64 processor (such as ARM64), it will return information + * as if the system is x86 only if x86 emulation is supported + * (or x64 if x64 emulation is also supported). + * + * so it appears that it will *not* return the correct processor + * architecture if running x86-64 Wireshark on ARM64 with + * x86-64 emulation - it will presumably say "x86-64", not "ARM64". + * + * So we use it to say "32-bit" or "64-bit", but we don't use + * it to say "N-bit x86" or "N-bit ARM". + * + * It Would Be Nice if there were some way to report that + * Wireshark is running in emulation on an ARM64 system; + * that might be important if, for example, a user is + * reporting a capture problem, as there currently isn't + * a version of Npcap that can support x86-64 programs on + * an ARM64 system. + */ + GetNativeSystemInfo(&system_info); + + switch (win_version_info.dwPlatformId) { + + case VER_PLATFORM_WIN32s: + /* Shyeah, right. */ + g_string_append_printf(str, "Windows 3.1 with Win32s"); + break; + + case VER_PLATFORM_WIN32_WINDOWS: + /* + * Windows OT. + * + * https://nsis-dev.github.io/NSIS-Forums/html/t-128527.html + * + * claims that + * + * HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion + * + * has a key ProductName, at least in Windows M3, the + * value of that key appears to be an OS product name. + */ + switch (win_version_info.dwMajorVersion) { + + case 4: + /* 3 cheers for Microsoft marketing! */ + switch (win_version_info.dwMinorVersion) { + + case 0: + g_string_append_printf(str, "Windows 95"); + break; + + case 10: + g_string_append_printf(str, "Windows 98"); + break; + + case 90: + g_string_append_printf(str, "Windows Me"); + break; + + default: + g_string_append_printf(str, "Windows OT, unknown version %lu.%lu", + win_version_info.dwMajorVersion, win_version_info.dwMinorVersion); + break; + } + break; + + default: + g_string_append_printf(str, "Windows OT, unknown version %lu.%lu", + win_version_info.dwMajorVersion, win_version_info.dwMinorVersion); + break; + } + break; + + case VER_PLATFORM_WIN32_NT: + /* + * Windows NT. + * + * https://stackoverflow.com/a/19778234/16139739 + * + * claims that + * + * HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion + * + * has a key ProductName that is "present for Windows XP + * and aboeve[sic]". The value of that key gives a + * "product name"... + * + * ...at least until Windows 11, which it insists is + * Windows 10. So we don't bother with it. (It may + * indicate whether it's Home or Pro or..., but that's + * not worth the effort of fixing the "Windows 11 is + * Windows 10" nonsense.) + * + * https://patents.google.com/patent/EP1517235A2/en + * + * is a Microsoft patent that mentions the + * BrandingFormatString() routine, and seems to suggest + * that it dates back to at least Windows XP. + * + * https://dennisbabkin.com/blog/?t=how-to-tell-the-real-version-of-windows-your-app-is-running-on + * + * says that routine is in an undocumented winbrand.dll DLL, + * but is used by Microsoft's own code to put the OS + * product name into messages. It, unlike ProductName, + * appears to make a distinction between Windows 10 and + * Windows 11, and, when handed the string "%WINDOWS_LONG%", + * gives the same edition decoration that I suspect + * ProductName does. + */ + switch (win_version_info.dwMajorVersion) { + + case 3: + case 4: + /* NT 3.x and 4.x. */ + g_string_append_printf(str, "Windows NT %lu.%lu", + win_version_info.dwMajorVersion, win_version_info.dwMinorVersion); + break; + + case 5: + /* + * W2K, WXP, and their server versions. + * 3 cheers for Microsoft marketing! + */ + switch (win_version_info.dwMinorVersion) { + + case 0: + g_string_append_printf(str, "Windows 2000"); + break; + + case 1: + g_string_append_printf(str, "Windows XP"); + break; + + case 2: + if (is_nt_workstation(&win_version_info) && + (system_info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64)) { + g_string_append_printf(str, "Windows XP Professional x64 Edition"); + } else { + g_string_append_printf(str, "Windows Server 2003"); + if (system_info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) + g_string_append_printf(str, " x64 Edition"); + } + break; + + default: + g_string_append_printf(str, "Windows NT, unknown version %lu.%lu", + win_version_info.dwMajorVersion, win_version_info.dwMinorVersion); + break; + } + break; + + case 6: { + /* + * Vista, W7, W8, W8.1, and their server versions. + */ + add_os_bitsize(str, &system_info); + switch (win_version_info.dwMinorVersion) { + case 0: + g_string_append_printf(str, is_nt_workstation(&win_version_info) ? "Windows Vista" : "Windows Server 2008"); + break; + case 1: + g_string_append_printf(str, is_nt_workstation(&win_version_info) ? "Windows 7" : "Windows Server 2008 R2"); + break; + case 2: + g_string_append_printf(str, is_nt_workstation(&win_version_info) ? "Windows 8" : "Windows Server 2012"); + break; + case 3: + g_string_append_printf(str, is_nt_workstation(&win_version_info) ? "Windows 8.1" : "Windows Server 2012 R2"); + break; + default: + g_string_append_printf(str, "Windows NT, unknown version %lu.%lu", + win_version_info.dwMajorVersion, win_version_info.dwMinorVersion); + break; + } + break; + } /* case 6 */ + + case 10: { + /* + * W10, W11, and their server versions. + */ + TCHAR ReleaseId[10]; + DWORD ridSize = _countof(ReleaseId); + + add_os_bitsize(str, &system_info); + switch (win_version_info.dwMinorVersion) { + case 0: + /* List of BuildNumber from https://en.wikipedia.org/wiki/List_of_Microsoft_Windows_versions + * and https://docs.microsoft.com/en-us/windows/release-health/windows11-release-information */ + if (is_nt_workstation(&win_version_info)) { + if (win_version_info.dwBuildNumber < 10240) { + /* XXX - W10 builds before 10240? */ + g_string_append_printf(str, "Windows"); + } else if (win_version_info.dwBuildNumber < 22000){ + /* W10 builds sstart at 10240 and end before 22000 */ + g_string_append_printf(str, "Windows 10"); + } else { + /* Builds 22000 and later are W11 (until there's W12...). */ + g_string_append_printf(str, "Windows 11"); + } + } else { + switch (win_version_info.dwBuildNumber) { + case 14393: + g_string_append_printf(str, "Windows Server 2016"); + break; + case 17763: + g_string_append_printf(str, "Windows Server 2019"); + break; + case 20348: + g_string_append_printf(str, "Windows Server 2022"); + break; + default: + g_string_append_printf(str, "Windows Server"); + break; + } + } + + /* + * Windows 10 and 11 have had multiple + * releases, with different build numbers. + * + * The build number *could* be used to + * determine the release string, but + * that would require a table of releases + * and strings, and that would have to + * get updated whenever a new release + * comes out, and that seems to happen + * twice a year these days. + * + * The good news is that, under + * + * HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion + * + * there are two keys, DisplayVersion and + * ReleaseId. If DisplayVersion is present, + * it's a string that gives the release + * string; if not, ReleaseId gives the + * release string. + * + * The DisplayVersion value is currently + * of the form YYHN, where YY is the + * last two digits of the year, H stands + * for "half", and N is the half of the + * year in which it came out. + * + * The ReleaseId is just a numeric string + * and for all the YYHN releases, it's + * stuck at the same value. + * + * Note further that + * + * https://github.com/nvaccess/nvda/blob/master/source/winVersion.py + * + * has a comment claiming that + * + * From Version 1511 (build 10586), release + * Id/display version comes from Windows + * Registry. + * However there are builds with no release + * name (Version 1507/10240) or releases + * with different builds. + * Look these up first before asking + * Windows Registry. + * + * "Look these up first" means "look them + * up in a table that goes from + * + * 10240: Windows 10 1507 + * + * to + * + * 22621: Windows 11 22H2 + * + * and also includes + * + * 20348: Windows Server 2022 + * + * I'm not sure why any Windows 10 builds + * after 10240 are in the table; what does + * "releases with different builds" mean? + * does it mean that those particular + * builds have bogus ReleaseId or + * DisplayVersion values? Those builds + * appear to be official release builds + * for W10/W11, according to the table + * in + * + * https://en.wikipedia.org/wiki/Windows_NT + * + * so, if those are all necessary, why + * should ReleaseId or DisplayVersion be + * trusted at all? + * + * As for the Windows Server 2022 entry, + * is that just becuase that script doesn't + * bother checking for "workstation" vs. + * "server"? + */ + if (RegGetValue(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", + L"DisplayVersion", RRF_RT_REG_SZ, NULL, &ReleaseId, &ridSize) == ERROR_SUCCESS) { + g_string_append_printf(str, " (%s)", utf_16to8(ReleaseId)); + } + else if (RegGetValue(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", + L"ReleaseId", RRF_RT_REG_SZ, NULL, &ReleaseId, &ridSize) == ERROR_SUCCESS) { + g_string_append_printf(str, " (%s)", utf_16to8(ReleaseId)); + } + break; + default: + g_string_append_printf(str, "Windows NT, unknown version %lu.%lu", + win_version_info.dwMajorVersion, win_version_info.dwMinorVersion); + break; + } + break; + } /* case 10 */ + + default: + g_string_append_printf(str, "Windows NT, unknown version %lu.%lu", + win_version_info.dwMajorVersion, win_version_info.dwMinorVersion); + break; + } /* info.dwMajorVersion */ + break; + + default: + g_string_append_printf(str, "Unknown Windows platform %lu version %lu.%lu", + win_version_info.dwPlatformId, win_version_info.dwMajorVersion, win_version_info.dwMinorVersion); + break; + } + if (win_version_info.szCSDVersion[0] != '\0') + g_string_append_printf(str, " %s", utf_16to8(win_version_info.szCSDVersion)); + g_string_append_printf(str, ", build %lu", win_version_info.dwBuildNumber); +#elif defined(HAVE_SYS_UTSNAME_H) + struct utsname name; + /* + * We have <sys/utsname.h>, so we assume we have "uname()". + */ + if (uname(&name) < 0) { + g_string_append_printf(str, "unknown OS version (uname failed - %s)", + g_strerror(errno)); + return; + } + + if (strcmp(name.sysname, "AIX") == 0) { + /* + * Yay, IBM! Thanks for doing something different + * from most of the other UNIXes out there, and + * making "name.version" apparently be the major + * version number and "name.release" be the minor + * version number. + */ + g_string_append_printf(str, "%s %s.%s", name.sysname, name.version, + name.release); + } else { + /* + * XXX - get "version" on any other platforms? + * + * On Digital/Tru64 UNIX, it's something unknown. + * On Solaris, it's some kind of build information. + * On HP-UX, it appears to be some sort of subrevision + * thing. + * On *BSD and Darwin/macOS, it's a long string giving + * a build date, config file name, etc., etc., etc.. + */ +#ifdef HAVE_MACOS_FRAMEWORKS + /* + * On macOS, report the macOS version number as the OS + * version if we can, and put the Darwin information + * in parentheses. + */ + if (get_macos_version_info(str)) { + /* Success - append the Darwin information. */ + g_string_append_printf(str, " (%s %s)", name.sysname, name.release); + } else { + /* Failure - just use the Darwin information. */ + g_string_append_printf(str, "%s %s", name.sysname, name.release); + } +#else /* HAVE_MACOS_FRAMEWORKS */ + /* + * XXX - on Linux, are there any APIs to get the distribution + * name and version number? I think some distributions have + * that. + * + * At least on Linux Standard Base-compliant distributions, + * there's an "lsb_release" command. However: + * + * http://forums.fedoraforum.org/showthread.php?t=220885 + * + * seems to suggest that if you don't have the redhat-lsb + * package installed, you don't have lsb_release, and that + * /etc/fedora-release has the release information on + * Fedora. + * + * http://linux.die.net/man/1/lsb_release + * + * suggests that there's an /etc/distrib-release file, but + * it doesn't indicate whether "distrib" is literally + * "distrib" or is the name for the distribution, and + * also speaks of an /etc/debian_version file. + * + * "lsb_release" apparently parses /etc/lsb-release, which + * has shell-style assignments, assigning to, among other + * values, DISTRIB_ID (distributor/distribution name), + * DISTRIB_RELEASE (release number of the distribution), + * DISTRIB_DESCRIPTION (*might* be name followed by version, + * but the manpage for lsb_release seems to indicate that's + * not guaranteed), and DISTRIB_CODENAME (code name, e.g. + * "licentious" for the Ubuntu Licentious Lemur release). + * the lsb_release man page also speaks of the distrib-release + * file, but Debian doesn't have one, and Ubuntu 7's + * lsb_release command doesn't look for one. + * + * I've seen references to /etc/redhat-release as well. + * + * See also + * + * http://bugs.python.org/issue1322 + * + * http://www.novell.com/coolsolutions/feature/11251.html + * + * http://linuxmafia.com/faq/Admin/release-files.html + * + * and the Lib/Platform.py file in recent Python 2.x + * releases. + * + * And then there's /etc/os-release: + * + * https://0pointer.de/blog/projects/os-release + * + * which, apparently, is something that all distributions + * with systemd have, which seems to mean "most distributions" + * these days. It also has a list of several of the assorted + * *other* such files that various distributions have. + * + * Maybe look at what pre-version-43 systemd does? 43 + * removed support for the old files, but I guess that + * means older versions *did* support them: + * + * https://lists.freedesktop.org/archives/systemd-devel/2012-February/004475.html + * + * At least on my Ubuntu 7 system, /etc/debian_version + * doesn't contain anything interesting (just some Debian + * codenames). It does have /etc/lsb-release. My Ubuntu + * 22.04 system has /etc/lsb-release and /etc/os-release. + * + * My Fedora 9 system has /etc/fedora-release, with + * /etc/redhat-release and /etc/system-release as symlinks + * to it. They all just contain a one-line relase + * description. My Fedora 38 system has that, plus + * /etc/os-release. + * + * A quick Debian 3.1a installation I did has only + * /etc/debian_version. My Debian 11.3 system has + * /etc/os-release. + * + * See + * + * https://gist.github.com/natefoo/814c5bf936922dad97ff + * + * for descriptions of what some versions of some + * distributions offer. + * + * So maybe have a table of files to try, with each + * entry having a pathname, a pointer to a file parser + * routine, and a pointer to a string giving a + * parameter name passed to that routine, with entries + * for: + * + * /etc/os-release, regular parser, "PRETTY_NAME" + * /etc/lsb-release, regular parser, "DISTRIB_DESCRIPTION" + * /etc/system-release, first line parser, NULL + * /etc/redhat-release, first line parser, NULL + * /etc/fedora-release, first line parser, NULL + * /etc/centos-release, first line parser, NULL + * /etc/debian_version, first line parser, "Debian" + * /etc/SuSE-release, first line parser, NULL + * /etc/slackware-version:, first line parser, NULL + * /etc/gentoo-release, first line parser, NULL + * /etc/antix-version, first line parser, NULL + * + * Each line is tried in order. If the open fails, go to + * the next one. If the open succeeds but the parser + * fails, close the file and go on to the next one. + * + * The regular parser parses files of the form + * <param>="value". It's passed the value of <param> + * for which to look; if not found, it fails. + * + * The first line parser reads the first line of the file. + * If a string is passed to it, it constructs a distribution + * name string by concatenating the parameter, a space, + * and the contents of that line (iwth the newline removed), + * otherwise it constructs it from the contents of the line. + * + * Fall back on just "Linux" if nothing works. + * + * Then use the uname() information to indicate what + * kernel version the machine is running. + * + * XXX - for Gentoo, PRETTY_NAME might not give a version, + * so fall back on /etc/gentoo-release? Gentoo is + * a rolling-release distribution, so what *is* the + * significance of the contnets of /etc/gentoo-release? + * + * XXX - MX appears to be a Debian-based distribution + * whose /etc/os-release gives its Debian version and + * whose /etc/mx-version and /etc/antix-version give + * the MX version. Are there any other Debian derivatives + * that do this? (The Big One calls itself "Ubuntu" + * in PRETTY_NAME.) + * + * XXX - use ID_LIKE in /etc/os-release to check for, + * for example, Debian-like distributions, e.g. when + * suggesting how to give dumpcap capture privileges? + */ + g_string_append_printf(str, "%s %s", name.sysname, name.release); +#endif /* HAVE_MACOS_FRAMEWORKS */ + } +#else + g_string_append(str, "an unknown OS"); +#endif +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 8 + * tab-width: 8 + * indent-tabs-mode: t + * End: + * + * vi: set shiftwidth=8 tabstop=8 noexpandtab: + * :indentSize=8:tabSize=8:noTabs=false: + */ diff --git a/wsutil/os_version_info.h b/wsutil/os_version_info.h new file mode 100644 index 00000000..f496b303 --- /dev/null +++ b/wsutil/os_version_info.h @@ -0,0 +1,29 @@ +/** @file + * Declarations of outines to report operating system version information + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WSUTIL_OS_VERSION_INFO_H__ +#define __WSUTIL_OS_VERSION_INFO_H__ + +#include <wireshark.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * Get the OS version, and append it to a GString. + */ +WS_DLL_PUBLIC void get_os_version_info(GString *str); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __WSUTIL_OS_VERSION_INFO_H__ */ diff --git a/wsutil/path_config.h.in b/wsutil/path_config.h.in new file mode 100644 index 00000000..6539a5ab --- /dev/null +++ b/wsutil/path_config.h.in @@ -0,0 +1,10 @@ +#ifndef __PATH_CONFIG_H__ +#define __PATH_CONFIG_H__ + +#define INSTALL_PREFIX "@PATH_INSTALL_PREFIX@" +#define DATA_DIR "@PATH_DATA_DIR@" +#define DOC_DIR "@PATH_DOC_DIR@" +#define PLUGIN_DIR "@PATH_PLUGIN_DIR@" +#define EXTCAP_DIR "@PATH_EXTCAP_DIR@" + +#endif diff --git a/wsutil/pint.h b/wsutil/pint.h new file mode 100644 index 00000000..97b6da23 --- /dev/null +++ b/wsutil/pint.h @@ -0,0 +1,385 @@ +/** @file + * + * Definitions for extracting and translating integers safely and portably + * via pointers. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __PINT_H__ +#define __PINT_H__ + +#include <inttypes.h> + +#include <glib.h> + +/* Routines that take a possibly-unaligned pointer to a 16-bit, 24-bit, + * 32-bit, 40-bit, ... 64-bit integral quantity, in a particular byte + * order, and fetch the value and return it in host byte order. + * + * The pntohN() routines fetch big-endian values; the pletohN() routines + * fetch little-endian values. + */ + +/* On most architectures, accesses of 16, 32, and 64 bit quantities can be + * heavily optimized. gcc and clang recognize portable versions below and, + * at -Os and higher, optimize them appropriately (for gcc, that includes + * for z/Architecture, PPC64, MIPS, etc.). Older versions don't do as good + * of a job with 16 bit accesses, though. + * + * Unfortunately, MSVC and icc (both the "classic" version and the new + * LLVM-based Intel C Compiler) do not, according to Matt Godbolt's Compiler + * Explorer (https://godbolt.org) as of the end of 2022. They *do* recognize + * and optimize a memcpy based approach (which avoids unaligned accesses on, + * say, ARM32), though that requires byteswapping appropriately. + */ + +#if (defined(_MSC_VER) && !defined(__clang__)) || defined(__INTEL_COMPILER) || defined(__INTEL_LLVM_COMPILER) +/* MSVC or Intel C Compiler (Classic or new LLVM version), but not + * clang-cl on Windows. + */ +/* Unfortunately, C23 did not fully accept the N3022 Modern Bit Utilities + * proposal, so a standard bytereverse function has been deferred for some + * future version: + * https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3048.htm + * https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3022.htm + * + * So choose byteswap intrinsics we know we have. + */ +#if defined(_MSC_VER) && !defined(__INTEL_COMPILER) && !defined(__INTEL_LLVM_COMPILER) && !defined(__clang__) +/* Intel and clang-cl both define _MSC_VER when compiling on Windows for + * greater compatiblity (just as they define __GNUC__ on other platforms). + * However, at least on some versions, while including the MSVC <stdlib.h> + * provides access to the _byteswap_ intrinsics, they are not actually + * optimized into a single x86 BSWAP function, unlike the gcc-style intrinsics + * (which both support.) See: https://stackoverflow.com/q/72327906 + */ +#include <stdlib.h> // For MSVC _byteswap intrinsics +#define pint_bswap16(x) _byteswap_ushort(x) +#define pint_bswap32(x) _byteswap_ulong(x) +/* Hopefully MSVC never decides that a long is 64 bit. */ +#define pint_bswap64(x) _byteswap_uint64(x) +#elif defined(__INTEL_COMPILER) +/* The (deprecated) Intel C++ Compiler Classic has these byteswap intrinsics. + * It also has the GCC-style intrinsics, though __builtin_bswap16 wasn't + * added until some point after icc 13.0 but at least by 16.0, reflecting + * that it wasn't added to gcc until 4.8. + */ +#define pint_bswap16(x) _bswap16(x) +#define pint_bswap32(x) _bswap32(x) +#define pint_bswap64(x) _bswap64(x) +#else +/* GCC-style _bswap intrinsics */ +/* The new LLVM-based Intel C++ Compiler doesn't have the above intrinsics, + * but it always has all the GCC intrinsics. + */ +/* __builtin_bswap32 and __builtin_bswap64 intrinsics have been supported + * for a long time on gcc (4.1), and clang (pre 3.0), versions that predate + * C11 and C+11 support, which we require, so we could assume we have them. + * + * __builtin_bswap16 was added a bit later, gcc 4.8, and clang 3.2. While + * those versions or later are required for full C11 and C++11 support, + * some earlier versions claim to support C11 and C++11 in ways that might + * allow them to get past CMake. We don't use this codepath for those + * compilers because they heavily optimize the portable versions, though. + */ +#define pint_bswap16(x) __builtin_bswap16(x) +#define pint_bswap32(x) __builtin_bswap32(x) +#define pint_bswap64(x) __builtin_bswap64(x) +#endif + +static inline uint16_t pntoh16(const void *p) +{ + uint16_t ret; + memcpy(&ret, p, sizeof(ret)); +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + ret = pint_bswap16(ret); +#endif + return ret; +} + +static inline uint32_t pntoh32(const void *p) +{ + uint32_t ret; + memcpy(&ret, p, sizeof(ret)); +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + ret = pint_bswap32(ret); +#endif + return ret; +} + +static inline uint64_t pntoh64(const void *p) +{ + uint64_t ret; + memcpy(&ret, p, sizeof(ret)); +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + ret = pint_bswap64(ret); +#endif + return ret; +} + +static inline uint16_t pletoh16(const void *p) +{ + uint16_t ret; + memcpy(&ret, p, sizeof(ret)); +#if G_BYTE_ORDER == G_BIG_ENDIAN + ret = pint_bswap16(ret); +#endif + return ret; +} + +static inline uint32_t pletoh32(const void *p) +{ + uint32_t ret; + memcpy(&ret, p, sizeof(ret)); +#if G_BYTE_ORDER == G_BIG_ENDIAN + ret = pint_bswap32(ret); +#endif + return ret; +} + +static inline uint64_t pletoh64(const void *p) +{ + uint64_t ret; + memcpy(&ret, p, sizeof(ret)); +#if G_BYTE_ORDER == G_BIG_ENDIAN + ret = pint_bswap64(ret); +#endif + return ret; +} + +static inline void phton16(uint8_t *p, uint16_t v) +{ +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + v = pint_bswap16(v); +#endif + memcpy(p, &v, sizeof(v)); +} + +static inline void phton32(uint8_t *p, uint32_t v) +{ +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + v = pint_bswap32(v); +#endif + memcpy(p, &v, sizeof(v)); +} + +static inline void phton64(uint8_t *p, uint64_t v) { +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + v = pint_bswap64(v); +#endif + memcpy(p, &v, sizeof(v)); +} + +static inline void phtole32(uint8_t *p, uint32_t v) +{ +#if G_BYTE_ORDER == G_BIG_ENDIAN + v = pint_bswap32(v); +#endif + memcpy(p, &v, sizeof(v)); +} + +static inline void phtole64(uint8_t *p, uint64_t v) { +#if G_BYTE_ORDER == G_BIG_ENDIAN + v = pint_bswap64(v); +#endif + memcpy(p, &v, sizeof(v)); +} + +#else +/* Portable functions */ +static inline uint16_t pntoh16(const void *p) +{ + return (uint16_t)*((const uint8_t *)(p)+0)<<8| + (uint16_t)*((const uint8_t *)(p)+1)<<0; +} + +static inline uint32_t pntoh32(const void *p) +{ + return (uint32_t)*((const uint8_t *)(p)+0)<<24| + (uint32_t)*((const uint8_t *)(p)+1)<<16| + (uint32_t)*((const uint8_t *)(p)+2)<<8| + (uint32_t)*((const uint8_t *)(p)+3)<<0; +} + +static inline uint64_t pntoh64(const void *p) +{ + return (uint64_t)*((const uint8_t *)(p)+0)<<56| + (uint64_t)*((const uint8_t *)(p)+1)<<48| + (uint64_t)*((const uint8_t *)(p)+2)<<40| + (uint64_t)*((const uint8_t *)(p)+3)<<32| + (uint64_t)*((const uint8_t *)(p)+4)<<24| + (uint64_t)*((const uint8_t *)(p)+5)<<16| + (uint64_t)*((const uint8_t *)(p)+6)<<8| + (uint64_t)*((const uint8_t *)(p)+7)<<0; +} + +static inline uint16_t pletoh16(const void *p) +{ + return (uint16_t)*((const uint8_t *)(p)+1)<<8| + (uint16_t)*((const uint8_t *)(p)+0)<<0; +} + +static inline uint32_t pletoh32(const void *p) +{ + return (uint32_t)*((const uint8_t *)(p)+3)<<24| + (uint32_t)*((const uint8_t *)(p)+2)<<16| + (uint32_t)*((const uint8_t *)(p)+1)<<8| + (uint32_t)*((const uint8_t *)(p)+0)<<0; +} + +static inline uint64_t pletoh64(const void *p) +{ + return (uint64_t)*((const uint8_t *)(p)+7)<<56| + (uint64_t)*((const uint8_t *)(p)+6)<<48| + (uint64_t)*((const uint8_t *)(p)+5)<<40| + (uint64_t)*((const uint8_t *)(p)+4)<<32| + (uint64_t)*((const uint8_t *)(p)+3)<<24| + (uint64_t)*((const uint8_t *)(p)+2)<<16| + (uint64_t)*((const uint8_t *)(p)+1)<<8| + (uint64_t)*((const uint8_t *)(p)+0)<<0; +} + +/* Pointer routines to put items out in a particular byte order. + * These will work regardless of the byte alignment of the pointer. + */ + +static inline void phton16(uint8_t *p, uint16_t v) +{ + p[0] = (uint8_t)(v >> 8); + p[1] = (uint8_t)(v >> 0); +} + +static inline void phton32(uint8_t *p, uint32_t v) +{ + p[0] = (uint8_t)(v >> 24); + p[1] = (uint8_t)(v >> 16); + p[2] = (uint8_t)(v >> 8); + p[3] = (uint8_t)(v >> 0); +} + +static inline void phton64(uint8_t *p, uint64_t v) { + p[0] = (uint8_t)(v >> 56); + p[1] = (uint8_t)(v >> 48); + p[2] = (uint8_t)(v >> 40); + p[3] = (uint8_t)(v >> 32); + p[4] = (uint8_t)(v >> 24); + p[5] = (uint8_t)(v >> 16); + p[6] = (uint8_t)(v >> 8); + p[7] = (uint8_t)(v >> 0); +} + +static inline void phtole32(uint8_t *p, uint32_t v) { + p[0] = (uint8_t)(v >> 0); + p[1] = (uint8_t)(v >> 8); + p[2] = (uint8_t)(v >> 16); + p[3] = (uint8_t)(v >> 24); +} + +static inline void phtole64(uint8_t *p, uint64_t v) { + p[0] = (uint8_t)(v >> 0); + p[1] = (uint8_t)(v >> 8); + p[2] = (uint8_t)(v >> 16); + p[3] = (uint8_t)(v >> 24); + p[4] = (uint8_t)(v >> 32); + p[5] = (uint8_t)(v >> 40); + p[6] = (uint8_t)(v >> 48); + p[7] = (uint8_t)(v >> 56); +} +#endif + +static inline uint32_t pntoh24(const void *p) +{ + return (uint32_t)*((const uint8_t *)(p)+0)<<16| + (uint32_t)*((const uint8_t *)(p)+1)<<8| + (uint32_t)*((const uint8_t *)(p)+2)<<0; +} + +static inline uint64_t pntoh40(const void *p) +{ + return (uint64_t)*((const uint8_t *)(p)+0)<<32| + (uint64_t)*((const uint8_t *)(p)+1)<<24| + (uint64_t)*((const uint8_t *)(p)+2)<<16| + (uint64_t)*((const uint8_t *)(p)+3)<<8| + (uint64_t)*((const uint8_t *)(p)+4)<<0; +} + +static inline uint64_t pntoh48(const void *p) +{ + return (uint64_t)*((const uint8_t *)(p)+0)<<40| + (uint64_t)*((const uint8_t *)(p)+1)<<32| + (uint64_t)*((const uint8_t *)(p)+2)<<24| + (uint64_t)*((const uint8_t *)(p)+3)<<16| + (uint64_t)*((const uint8_t *)(p)+4)<<8| + (uint64_t)*((const uint8_t *)(p)+5)<<0; +} + +static inline uint64_t pntoh56(const void *p) +{ + return (uint64_t)*((const uint8_t *)(p)+0)<<48| + (uint64_t)*((const uint8_t *)(p)+1)<<40| + (uint64_t)*((const uint8_t *)(p)+2)<<32| + (uint64_t)*((const uint8_t *)(p)+3)<<24| + (uint64_t)*((const uint8_t *)(p)+4)<<16| + (uint64_t)*((const uint8_t *)(p)+5)<<8| + (uint64_t)*((const uint8_t *)(p)+6)<<0; +} + +static inline uint32_t pletoh24(const void *p) +{ + return (uint32_t)*((const uint8_t *)(p)+2)<<16| + (uint32_t)*((const uint8_t *)(p)+1)<<8| + (uint32_t)*((const uint8_t *)(p)+0)<<0; +} + +static inline uint64_t pletoh40(const void *p) +{ + return (uint64_t)*((const uint8_t *)(p)+4)<<32| + (uint64_t)*((const uint8_t *)(p)+3)<<24| + (uint64_t)*((const uint8_t *)(p)+2)<<16| + (uint64_t)*((const uint8_t *)(p)+1)<<8| + (uint64_t)*((const uint8_t *)(p)+0)<<0; +} + +static inline uint64_t pletoh48(const void *p) +{ + return (uint64_t)*((const uint8_t *)(p)+5)<<40| + (uint64_t)*((const uint8_t *)(p)+4)<<32| + (uint64_t)*((const uint8_t *)(p)+3)<<24| + (uint64_t)*((const uint8_t *)(p)+2)<<16| + (uint64_t)*((const uint8_t *)(p)+1)<<8| + (uint64_t)*((const uint8_t *)(p)+0)<<0; +} + +static inline uint64_t pletoh56(const void *p) +{ + return (uint64_t)*((const uint8_t *)(p)+6)<<48| + (uint64_t)*((const uint8_t *)(p)+5)<<40| + (uint64_t)*((const uint8_t *)(p)+4)<<32| + (uint64_t)*((const uint8_t *)(p)+3)<<24| + (uint64_t)*((const uint8_t *)(p)+2)<<16| + (uint64_t)*((const uint8_t *)(p)+1)<<8| + (uint64_t)*((const uint8_t *)(p)+0)<<0; +} + +/* Subtract two guint32s with respect to wraparound */ +#define guint32_wraparound_diff(higher, lower) ((higher>lower)?(higher-lower):(higher+0xffffffff-lower+1)) + +#endif /* PINT_H */ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/please_report_bug.c b/wsutil/please_report_bug.c new file mode 100644 index 00000000..82520fb2 --- /dev/null +++ b/wsutil/please_report_bug.c @@ -0,0 +1,33 @@ +/* please_report_bug.c + * Routines returning strings to use when reporting a bug. + * They ask the user to report a bug to the Wireshark developers. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "please_report_bug.h" + +/* + * Long message, to use in alert boxes and printed messages. + */ +const char * +please_report_bug(void) +{ + return + "Please report this to the Wireshark developers as a bug.\n" + "https://gitlab.com/wireshark/wireshark/issues\n" + "(This is not a crash; please do not say, in your report, that it is a crash.)"; +} + +/* + * Short message, to use in status bar messages. + */ +const char * +please_report_bug_short(void) +{ + return "Please report this to the Wireshark developers."; +} diff --git a/wsutil/please_report_bug.h b/wsutil/please_report_bug.h new file mode 100644 index 00000000..e99b26ca --- /dev/null +++ b/wsutil/please_report_bug.h @@ -0,0 +1,35 @@ +/** @file + * Declarations of routines returning strings to use when reporting a bug. + * They ask the user to report a bug to the Wireshark developers. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __PLEASE_REPORT_BUG_H__ +#define __PLEASE_REPORT_BUG_H__ + +#include "ws_symbol_export.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * Long message, to use in alert boxes and printed messages. + */ +WS_DLL_PUBLIC const char *please_report_bug(void); + +/* + * Short message, to use in status bar messages. + */ +WS_DLL_PUBLIC const char *please_report_bug_short(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __PLEASE_REPORT_BUG_H__ */ diff --git a/wsutil/plugins.c b/wsutil/plugins.c new file mode 100644 index 00000000..2798fdc3 --- /dev/null +++ b/wsutil/plugins.c @@ -0,0 +1,333 @@ +/* plugins.c + * plugin routines + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" +#define WS_LOG_DOMAIN LOG_DOMAIN_PLUGINS +#include "plugins.h" + +#include <time.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include <gmodule.h> + +#include <wsutil/filesystem.h> +#include <wsutil/privileges.h> +#include <wsutil/file_util.h> +#include <wsutil/report_message.h> +#include <wsutil/wslog.h> + +typedef struct _plugin { + GModule *handle; /* handle returned by g_module_open */ + char *name; /* plugin name */ + const char *version; /* plugin version */ + const char *type_name; /* user-facing name (what it does). Should these be capitalized? */ +} plugin; + +#define TYPE_DIR_EPAN "epan" +#define TYPE_DIR_WIRETAP "wiretap" +#define TYPE_DIR_CODECS "codecs" + +#define TYPE_NAME_DISSECTOR "dissector" +#define TYPE_NAME_FILE_TYPE "file type" +#define TYPE_NAME_CODEC "codec" + + +static GSList *plugins_module_list = NULL; + + +static inline const char * +type_to_dir(plugin_type_e type) +{ + switch (type) { + case WS_PLUGIN_EPAN: + return TYPE_DIR_EPAN; + case WS_PLUGIN_WIRETAP: + return TYPE_DIR_WIRETAP; + case WS_PLUGIN_CODEC: + return TYPE_DIR_CODECS; + default: + ws_error("Unknown plugin type: %u. Aborting.", (unsigned) type); + break; + } + ws_assert_not_reached(); +} + +static inline const char * +type_to_name(plugin_type_e type) +{ + switch (type) { + case WS_PLUGIN_EPAN: + return TYPE_NAME_DISSECTOR; + case WS_PLUGIN_WIRETAP: + return TYPE_NAME_FILE_TYPE; + case WS_PLUGIN_CODEC: + return TYPE_NAME_CODEC; + default: + ws_error("Unknown plugin type: %u. Aborting.", (unsigned) type); + break; + } + ws_assert_not_reached(); +} + +static void +free_plugin(void * data) +{ + plugin *p = (plugin *)data; + g_module_close(p->handle); + g_free(p->name); + g_free(p); +} + +static int +compare_plugins(gconstpointer a, gconstpointer b) +{ + return g_strcmp0((*(plugin *const *)a)->name, (*(plugin *const *)b)->name); +} + +static bool +pass_plugin_version_compatibility(GModule *handle, const char *name) +{ + void * symb; + int major, minor; + + if(!g_module_symbol(handle, "plugin_want_major", &symb)) { + report_failure("The plugin '%s' has no \"plugin_want_major\" symbol", name); + return false; + } + major = *(int *)symb; + + if(!g_module_symbol(handle, "plugin_want_minor", &symb)) { + report_failure("The plugin '%s' has no \"plugin_want_minor\" symbol", name); + return false; + } + minor = *(int *)symb; + + if (major != VERSION_MAJOR || minor != VERSION_MINOR) { + report_failure("The plugin '%s' was compiled for Wireshark version %d.%d", + name, major, minor); + return false; + } + + return true; +} + +// GLib and Qt allow ".dylib" and ".so" on macOS. Should we do the same? +#ifdef _WIN32 +#define MODULE_SUFFIX ".dll" +#else +#define MODULE_SUFFIX ".so" +#endif + +static void +scan_plugins_dir(GHashTable *plugins_module, const char *dirpath, plugin_type_e type, bool append_type) +{ + GDir *dir; + const char *name; /* current file name */ + char *plugin_folder; + char *plugin_file; /* current file full path */ + GModule *handle; /* handle returned by g_module_open */ + void * symbol; + const char *plug_version; + plugin *new_plug; + + if (append_type) + plugin_folder = g_build_filename(dirpath, type_to_dir(type), (char *)NULL); + else + plugin_folder = g_strdup(dirpath); + + dir = g_dir_open(plugin_folder, 0, NULL); + if (dir == NULL) { + g_free(plugin_folder); + return; + } + + ws_debug("Scanning plugins folder \"%s\"", plugin_folder); + + while ((name = g_dir_read_name(dir)) != NULL) { + /* Skip anything but files with .dll or .so. */ + if (!g_str_has_suffix(name, MODULE_SUFFIX)) + continue; + + /* + * Check if the same name is already registered. + */ + if (g_hash_table_lookup(plugins_module, name)) { + /* Yes, it is. */ + report_warning("The plugin '%s' was found " + "in multiple directories", name); + continue; + } + + plugin_file = g_build_filename(plugin_folder, name, (char *)NULL); + handle = g_module_open(plugin_file, G_MODULE_BIND_LOCAL); + if (handle == NULL) { + /* g_module_error() provides file path. */ + report_failure("Couldn't load plugin '%s': %s", name, + g_module_error()); + g_free(plugin_file); + continue; + } + + if (!g_module_symbol(handle, "plugin_version", &symbol)) + { + report_failure("The plugin '%s' has no \"plugin_version\" symbol", name); + g_module_close(handle); + g_free(plugin_file); + continue; + } + plug_version = (const char *)symbol; + + if (!pass_plugin_version_compatibility(handle, name)) { + g_module_close(handle); + g_free(plugin_file); + continue; + } + + /* Search for the entry point for the plugin registration function */ + if (!g_module_symbol(handle, "plugin_register", &symbol)) { + report_failure("The plugin '%s' has no \"plugin_register\" symbol", name); + g_module_close(handle); + g_free(plugin_file); + continue; + } + +DIAG_OFF_PEDANTIC + /* Found it, call the plugin registration function. */ + ((plugin_register_func)symbol)(); +DIAG_ON_PEDANTIC + + new_plug = g_new(plugin, 1); + new_plug->handle = handle; + new_plug->name = g_strdup(name); + new_plug->version = plug_version; + new_plug->type_name = type_to_name(type); + + /* Add it to the list of plugins. */ + g_hash_table_replace(plugins_module, new_plug->name, new_plug); + ws_info("Registered plugin: %s (%s)", new_plug->name, plugin_file); + g_free(plugin_file); + } + ws_dir_close(dir); + g_free(plugin_folder); +} + +/* + * Scan for plugins. + */ +plugins_t * +plugins_init(plugin_type_e type) +{ + if (!g_module_supported()) + return NULL; /* nothing to do */ + + GHashTable *plugins_module = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, free_plugin); + + /* + * Scan the global plugin directory. + */ + scan_plugins_dir(plugins_module, get_plugins_dir_with_version(), type, true); + + /* + * If the program wasn't started with special privileges, + * scan the users plugin directory. (Even if we relinquish + * them, plugins aren't safe unless we've *permanently* + * relinquished them, and we can't do that in Wireshark as, + * if we need privileges to start capturing, we'd need to + * reclaim them before each time we start capturing.) + */ + if (!started_with_special_privs()) { + scan_plugins_dir(plugins_module, get_plugins_pers_dir_with_version(), type, true); + } + + plugins_module_list = g_slist_prepend(plugins_module_list, plugins_module); + + return plugins_module; +} + +WS_DLL_PUBLIC void +plugins_get_descriptions(plugin_description_callback callback, void *callback_data) +{ + GPtrArray *plugins_array = g_ptr_array_new(); + GHashTableIter iter; + void * value; + + for (GSList *l = plugins_module_list; l != NULL; l = l->next) { + g_hash_table_iter_init (&iter, (GHashTable *)l->data); + while (g_hash_table_iter_next (&iter, NULL, &value)) { + g_ptr_array_add(plugins_array, value); + } + } + + g_ptr_array_sort(plugins_array, compare_plugins); + + for (unsigned i = 0; i < plugins_array->len; i++) { + plugin *plug = (plugin *)plugins_array->pdata[i]; + callback(plug->name, plug->version, plug->type_name, g_module_name(plug->handle), callback_data); + } + + g_ptr_array_free(plugins_array, true); +} + +static void +print_plugin_description(const char *name, const char *version, + const char *description, const char *filename, + void *user_data _U_) +{ + printf("%-16s\t%s\t%s\t%s\n", name, version, description, filename); +} + +void +plugins_dump_all(void) +{ + plugins_get_descriptions(print_plugin_description, NULL); +} + +int +plugins_get_count(void) +{ + unsigned count = 0; + + for (GSList *l = plugins_module_list; l != NULL; l = l->next) { + count += g_hash_table_size((GHashTable *)l->data); + } + return count; +} + +void +plugins_cleanup(plugins_t *plugins) +{ + if (!plugins) + return; + + plugins_module_list = g_slist_remove(plugins_module_list, plugins); + g_hash_table_destroy((GHashTable *)plugins); +} + +bool +plugins_supported(void) +{ + return g_module_supported(); +} + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/plugins.h b/wsutil/plugins.h new file mode 100644 index 00000000..96f3ac03 --- /dev/null +++ b/wsutil/plugins.h @@ -0,0 +1,63 @@ +/** @file + * definitions for plugins structures + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __PLUGINS_H__ +#define __PLUGINS_H__ + +#include <wireshark.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef void (*plugin_register_func)(void); + +typedef void plugins_t; + +typedef enum { + WS_PLUGIN_EPAN, + WS_PLUGIN_WIRETAP, + WS_PLUGIN_CODEC +} plugin_type_e; + +WS_DLL_PUBLIC plugins_t *plugins_init(plugin_type_e type); + +typedef void (*plugin_description_callback)(const char *name, const char *version, + const char *types, const char *filename, + void *user_data); + +WS_DLL_PUBLIC void plugins_get_descriptions(plugin_description_callback callback, void *user_data); + +WS_DLL_PUBLIC void plugins_dump_all(void); + +WS_DLL_PUBLIC int plugins_get_count(void); + +WS_DLL_PUBLIC void plugins_cleanup(plugins_t *plugins); + +WS_DLL_PUBLIC bool plugins_supported(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __PLUGINS_H__ */ + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/pow2.h b/wsutil/pow2.h new file mode 100644 index 00000000..1555f3c4 --- /dev/null +++ b/wsutil/pow2.h @@ -0,0 +1,29 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WS_POW2_H__ +#define __WS_POW2_H__ + +/* + * Macros to calculate pow2^M, for various power-of-2 values and positive + * integer values of M. That's (2^N)^M, i.e. 2^(N*M). + * + * The first argument is the type of the desired result; the second + * argument is M. + */ +#define pow2(type, m) (((type)1U) << (m)) +#define pow4(type, m) (((type)1U) << (2*(m))) +#define pow8(type, m) (((type)1U) << (3*(m))) +#define pow16(type, m) (((type)1U) << (4*(m))) +#define pow32(type, m) (((type)1U) << (5*(m))) +#define pow64(type, m) (((type)1U) << (6*(m))) +#define pow128(type, m) (((type)1U) << (7*(m))) +#define pow256(type, m) (((type)1U) << (8*(m))) + +#endif /* __WS_POW2_H__ */ diff --git a/wsutil/privileges.c b/wsutil/privileges.c new file mode 100644 index 00000000..2ca30203 --- /dev/null +++ b/wsutil/privileges.c @@ -0,0 +1,288 @@ +/* privileges.c + * Routines for handling privileges, e.g. set-UID and set-GID on UNIX. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 2006 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" +#define WS_LOG_DOMAIN LOG_DOMAIN_WSUTIL + +#if defined(HAVE_SETRESUID) || defined(HAVE_SETREGUID) +#define _GNU_SOURCE /* Otherwise [sg]etres[gu]id won't be defined on Linux */ +#endif +#include "privileges.h" + +#include <wsutil/ws_assert.h> +#include <wsutil/wslog.h> + +#ifdef _WIN32 +#include <windows.h> +#include <wchar.h> +#include <tchar.h> + +/* + * Called when the program starts, to save whatever credential information + * we'll need later, and to do whatever other specialized platform-dependent + * initialization we want. + */ +void +init_process_policies(void) +{ + /* + * If we have SetProcessDEPPolicy(), turn "data execution + * prevention" on - i.e., if the MMU lets you set execute + * permission on a per-page basis, turn execute permission + * off on most data pages. SetProcessDEPPolicy() fails on + * 64-bit Windows (it's *always* on there), but if it fails, + * we don't care (we did our best), so we don't check for + * errors. + * + */ + SetProcessDEPPolicy(PROCESS_DEP_ENABLE); +} + +/* + * For now, we say the program wasn't started with special privileges. + * There are ways of running programs with credentials other than those + * for the session in which it's run, but I don't know whether that'd be + * done with Wireshark/TShark or not. + */ +bool +started_with_special_privs(void) +{ + return false; +} + +/* + * For now, we say the program isn't running with special privileges. + * There are ways of running programs with credentials other than those + * for the session in which it's run, but I don't know whether that'd be + * done with Wireshark/TShark or not. + */ +bool +running_with_special_privs(void) +{ + return false; +} + +/* + * For now, we don't do anything when asked to relinquish special privileges. + */ +void +relinquish_special_privs_perm(void) +{ +} + +/* + * Get the current username. String must be g_free()d after use. + */ +char * +get_cur_username(void) { + char *username; + username = g_strdup("UNKNOWN"); + return username; +} + +/* + * Get the current group. String must be g_free()d after use. + */ +char * +get_cur_groupname(void) { + char *groupname; + groupname = g_strdup("UNKNOWN"); + return groupname; +} + +#else /* _WIN32 */ + +#include <sys/types.h> + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif + +#ifdef HAVE_GRP_H +#include <grp.h> +#endif + +#include <string.h> +#include <errno.h> + +static uid_t ruid, euid; +static gid_t rgid, egid; +static bool init_process_policies_called = false; + +/* + * Called when the program starts, to save whatever credential information + * we'll need later, and to do whatever other specialized platform-dependent + * initialization we want. + * + * The credential information we'll need later on UNIX is the real and + * effective UID and GID. + * + * XXX - do any UN*Xes have opt-in "no execute on data pages by default" + * permission? This would be the place to request it. + */ +void +init_process_policies(void) +{ + ruid = getuid(); + euid = geteuid(); + rgid = getgid(); + egid = getegid(); + + init_process_policies_called = true; +} + +/* + * "Started with special privileges" means "started out set-UID or set-GID", + * or run as the root user or group. + */ +bool +started_with_special_privs(void) +{ + ws_assert(init_process_policies_called); +#ifdef HAVE_ISSETUGID + return issetugid(); +#else + return (ruid != euid || rgid != egid || ruid == 0 || rgid == 0); +#endif +} + +/* + * Return true if the real, effective, or saved (if we can check it) user + * ID or group are 0. + */ +bool +running_with_special_privs(void) +{ +#ifdef HAVE_SETRESUID + uid_t ru, eu, su; +#endif +#ifdef HAVE_SETRESGID + gid_t rg, eg, sg; +#endif + +#ifdef HAVE_SETRESUID + getresuid(&ru, &eu, &su); + if (ru == 0 || eu == 0 || su == 0) + return true; +#else + if (getuid() == 0 || geteuid() == 0) + return true; +#endif +#ifdef HAVE_SETRESGID + getresgid(&rg, &eg, &sg); + if (rg == 0 || eg == 0 || sg == 0) + return true; +#else + if (getgid() == 0 || getegid() == 0) + return true; +#endif + return false; +} + +/* + * Permanently relinquish set-UID and set-GID privileges. + * If error, abort since we probably shouldn't continue + * with elevated privileges. + * Note that if this error occurs when dumpcap is called from + * wireshark or tshark, the message seen will be + * "Child dumpcap process died:". This is obscure but we'll + * consider it acceptable since it should be highly unlikely + * that this error will occur. + */ + +static void +setxid_fail(const char *str) +{ + ws_error("Attempt to relinquish privileges failed [%s()] - aborting: %s\n", + str, g_strerror(errno)); +} + +void +relinquish_special_privs_perm(void) +{ + /* + * If we were started with special privileges, set the + * real and effective group and user IDs to the original + * values of the real and effective group and user IDs. + * If we're not, don't bother - doing so seems to mung + * our group set, at least in Mac OS X 10.5. + * + * (Set the effective UID last - that takes away our + * rights to set anything else.) + */ + if (started_with_special_privs()) { +#ifdef HAVE_SETRESGID + if (setresgid(rgid, rgid, rgid) == -1) {setxid_fail("setresgid");} +#else + if (setgid(rgid) == -1) {setxid_fail("setgid"); } + if (setegid(rgid) == -1) {setxid_fail("setegid");} +#endif + +#ifdef HAVE_SETRESUID + if (setresuid(ruid, ruid, ruid) == -1) {setxid_fail("setresuid");} +#else + if (setuid(ruid) == -1) {setxid_fail("setuid"); } + if (seteuid(ruid) == -1) {setxid_fail("seteuid");} +#endif + } +} + +/* + * Get the current username. String must be g_free()d after use. + */ +char * +get_cur_username(void) { + char *username; + struct passwd *pw = getpwuid(getuid()); + + if (pw) { + username = g_strdup(pw->pw_name); + } else { + username = g_strdup("UNKNOWN"); + } + endpwent(); + return username; +} + +/* + * Get the current group. String must be g_free()d after use. + */ +char * +get_cur_groupname(void) { + char *groupname; + struct group *gr = getgrgid(getgid()); + + if (gr) { + groupname = g_strdup(gr->gr_name); + } else { + groupname = g_strdup("UNKNOWN"); + } + endgrent(); + return groupname; +} + +#endif /* _WIN32 */ + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 8 + * tab-width: 8 + * indent-tabs-mode: t + * End: + * + * ex: set shiftwidth=8 tabstop=8 noexpandtab: + * :indentSize=8:tabSize=8:noTabs=false: + */ diff --git a/wsutil/privileges.h b/wsutil/privileges.h new file mode 100644 index 00000000..c936228c --- /dev/null +++ b/wsutil/privileges.h @@ -0,0 +1,66 @@ +/** @file + * Declarations of routines for handling privileges. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 2006 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __PRIVILEGES_H__ +#define __PRIVILEGES_H__ + +#include <wireshark.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Called when the program starts, to enable security features and save + * whatever credential information we'll need later. + */ +WS_DLL_PUBLIC void init_process_policies(void); + +/** + * Was this program started with special privileges? get_credential_info() + * MUST be called before calling this. + * @return true if the program was started with special privileges, + * false otherwise. + */ +WS_DLL_PUBLIC bool started_with_special_privs(void); + +/** + * Is this program running with special privileges? get_credential_info() + * MUST be called before calling this. + * @return true if the program is running with special privileges, + * false otherwise. + */ +WS_DLL_PUBLIC bool running_with_special_privs(void); + +/** + * Permanently relinquish special privileges. get_credential_info() + * MUST be called before calling this. + */ +WS_DLL_PUBLIC void relinquish_special_privs_perm(void); + +/** + * Get the current username. String must be g_free()d after use. + * @return A freshly g_alloc()ed string containing the username, + * or "UNKNOWN" on failure. + */ +WS_DLL_PUBLIC char *get_cur_username(void); + +/** + * Get the current group. String must be g_free()d after use. + * @return A freshly g_alloc()ed string containing the group, + * or "UNKNOWN" on failure. + */ +WS_DLL_PUBLIC char *get_cur_groupname(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __PRIVILEGES_H__ */ diff --git a/wsutil/processes.h b/wsutil/processes.h new file mode 100644 index 00000000..8e08a171 --- /dev/null +++ b/wsutil/processes.h @@ -0,0 +1,56 @@ +/** @file + * + * Process utility definitions + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef _WSUTIL_PROCESSES_H_ +#define _WSUTIL_PROCESSES_H_ + +#include "ws_symbol_export.h" + +#ifdef _WIN32 +/* + * On Windows, a process ID is a HANDLE. + * Include <windows.h> to make sure HANDLE is defined. + */ +#include <windows.h> +#else +/* + * On UN*X, a process ID is a pid_t. + * Include <sys/types.h> to make sure pid_t is defined. + */ +#include <sys/types.h> +#endif + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#ifdef _WIN32 +/* + * On Windows, a process ID is a HANDLE. + */ +typedef HANDLE ws_process_id; + +#define WS_INVALID_PID INVALID_HANDLE_VALUE + +#else +/* + * On UN*X, a process ID is a pid_t. + */ +typedef pid_t ws_process_id; + +#define WS_INVALID_PID -1 +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _WSUTIL_PROCESSES_H_ */ diff --git a/wsutil/regex.c b/wsutil/regex.c new file mode 100644 index 00000000..bb27189b --- /dev/null +++ b/wsutil/regex.c @@ -0,0 +1,203 @@ +/* + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" + +#include "regex.h" + +#include <wsutil/str_util.h> +#include <pcre2.h> + + +struct _ws_regex { + pcre2_code *code; + char *pattern; +}; + +#define ERROR_MAXLEN_IN_CODE_UNITS 128 + +static char * +get_error_msg(int errorcode) +{ + char *buffer; + + /* + * We have to provide a buffer and we don't know how long the + * error message is or even the maximum size. From pcre2api(3): + * "None of the messages are very long; a + * buffer size of 120 code units is ample." + */ + /* Code unit = one byte */ + buffer = g_malloc(ERROR_MAXLEN_IN_CODE_UNITS); + /* Message is returned with a trailing zero. */ + pcre2_get_error_message(errorcode, buffer, ERROR_MAXLEN_IN_CODE_UNITS); + /* One more at the end for good luck. */ + buffer[ERROR_MAXLEN_IN_CODE_UNITS-1] = '\0'; + return buffer; +} + + +static pcre2_code * +compile_pcre2(const char *patt, ssize_t size, char **errmsg, unsigned flags) +{ + pcre2_code *code; + int errorcode; + PCRE2_SIZE length; + PCRE2_SIZE erroroffset; + uint32_t options = 0; + + if (size < 0) + length = PCRE2_ZERO_TERMINATED; + else + length = (PCRE2_SIZE)size; + + if (flags & WS_REGEX_NEVER_UTF) + options |= PCRE2_NEVER_UTF; + if (flags & WS_REGEX_CASELESS) + options |= PCRE2_CASELESS; + + /* By default UTF-8 is off. */ + code = pcre2_compile_8((PCRE2_SPTR)patt, + length, + options, + &errorcode, + &erroroffset, + NULL); + + if (code == NULL) { + *errmsg = get_error_msg(errorcode); + return NULL; + } + + return code; +} + + +ws_regex_t * +ws_regex_compile_ex(const char *patt, ssize_t size, char **errmsg, unsigned flags) +{ + ws_return_val_if(!patt, NULL); + + pcre2_code *code = compile_pcre2(patt, size, errmsg, flags); + if (code == NULL) + return NULL; + + ws_regex_t *re = g_new(ws_regex_t, 1); + re->code = code; + re->pattern = ws_escape_string_len(NULL, patt, size, false); + return re; +} + + +ws_regex_t * +ws_regex_compile(const char *patt, char **errmsg) +{ + return ws_regex_compile_ex(patt, -1, errmsg, 0); +} + + +static bool +match_pcre2(pcre2_code *code, const char *subject, ssize_t subj_length, + pcre2_match_data *match_data) +{ + PCRE2_SIZE length; + int rc; + + if (subj_length < 0) + length = PCRE2_ZERO_TERMINATED; + else + length = (PCRE2_SIZE)subj_length; + + rc = pcre2_match(code, + subject, + length, + 0, /* start at offset zero of the subject */ + 0, /* default options */ + match_data, + NULL); + + if (rc < 0) { + /* No match */ + if (rc != PCRE2_ERROR_NOMATCH) { + /* Error. Should not happen with UTF-8 disabled. Some huge + * subject strings could hit some internal limit. */ + char *msg = get_error_msg(rc); + ws_debug("Unexpected pcre2_match() error: %s.", msg); + g_free(msg); + } + return false; + } + + /* Matched */ + return true; +} + + +bool +ws_regex_matches(const ws_regex_t *re, const char *subj) +{ + return ws_regex_matches_length(re, subj, -1); +} + + +bool +ws_regex_matches_length(const ws_regex_t *re, + const char *subj, ssize_t subj_length) +{ + bool matched; + pcre2_match_data *match_data; + + ws_return_val_if(!re, false); + ws_return_val_if(!subj, false); + + /* We don't use the matched substring but pcre2_match requires + * at least one pair of offsets. */ + match_data = pcre2_match_data_create(1, NULL); + matched = match_pcre2(re->code, subj, subj_length, match_data); + pcre2_match_data_free(match_data); + return matched; +} + + +bool +ws_regex_matches_pos(const ws_regex_t *re, + const char *subj, ssize_t subj_length, + size_t pos_vect[2]) +{ + bool matched; + pcre2_match_data *match_data; + + ws_return_val_if(!re, false); + ws_return_val_if(!subj, false); + + match_data = pcre2_match_data_create(1, NULL); + matched = match_pcre2(re->code, subj, subj_length, match_data); + if (matched && pos_vect) { + PCRE2_SIZE *ovect = pcre2_get_ovector_pointer(match_data); + pos_vect[0] = ovect[0]; + pos_vect[1] = ovect[1]; + } + pcre2_match_data_free(match_data); + return matched; +} + + +void +ws_regex_free(ws_regex_t *re) +{ + pcre2_code_free(re->code); + g_free(re->pattern); + g_free(re); +} + + +const char * +ws_regex_pattern(const ws_regex_t *re) +{ + return re->pattern; +} diff --git a/wsutil/regex.h b/wsutil/regex.h new file mode 100644 index 00000000..0484eab3 --- /dev/null +++ b/wsutil/regex.h @@ -0,0 +1,63 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WSUTIL_REGEX_H__ +#define __WSUTIL_REGEX_H__ + +#include <wireshark.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct _ws_regex; +typedef struct _ws_regex ws_regex_t; + +WS_DLL_PUBLIC ws_regex_t * +ws_regex_compile(const char *patt, char **errmsg); + +#define WS_REGEX_CASELESS (1U << 0) +/* By default UTF-8 is off. This option also prevents it from being + * turned on using a pattern option. */ +#define WS_REGEX_NEVER_UTF (1U << 1) + +WS_DLL_PUBLIC ws_regex_t * +ws_regex_compile_ex(const char *patt, ssize_t size, char **errmsg, unsigned flags); + +/** Matches a null-terminated subject string. */ +WS_DLL_PUBLIC bool +ws_regex_matches(const ws_regex_t *re, const char *subj); + +/** Matches a subject string length in 8 bit code units. */ +WS_DLL_PUBLIC bool +ws_regex_matches_length(const ws_regex_t *re, + const char *subj, ssize_t subj_length); + +/** Returns start and end position of the matched substring. + * + * pos_vect[0] is first codepoint in the matched substring. + * pos_vect[1] is the next to last codepoint in the matched substring. + * pos_vect[1] - pos_vect[0] is the matched substring length. + */ +WS_DLL_PUBLIC bool +ws_regex_matches_pos(const ws_regex_t *re, + const char *subj, ssize_t subj_length, + size_t pos_vect[2]); + +WS_DLL_PUBLIC void +ws_regex_free(ws_regex_t *re); + +WS_DLL_PUBLIC const char * +ws_regex_pattern(const ws_regex_t *re); + +#ifdef __cplusplus +} +#endif + +#endif /* __WSUTIL_REGEX_H__ */ diff --git a/wsutil/report_message.c b/wsutil/report_message.c new file mode 100644 index 00000000..6f797fab --- /dev/null +++ b/wsutil/report_message.c @@ -0,0 +1,162 @@ +/* report_message.c + * Routines for code that can run in GUI and command-line environments to + * use to report errors and warnings to the user (e.g., I/O errors, or + * problems with preference settings) if the message should be shown as + * a GUI error in a GUI environment. + * + * The application using libwsutil will register error-reporting + * routines, and the routines defined here will call the registered + * routines. That way, these routines can be called by code that + * doesn't itself know whether to pop up a dialog or print something + * to the standard error. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#include "config.h" + +#include "report_message.h" + +static const char *friendly_program_name; +static const struct report_message_routines *routines; + +void +init_report_message(const char *friendly_program_name_arg, + const struct report_message_routines *routines_arg) +{ + friendly_program_name = friendly_program_name_arg; + routines = routines_arg; +} + +/* + * Report a general error. + */ +void +report_failure(const char *msg_format, ...) +{ + va_list ap; + + va_start(ap, msg_format); + (*routines->vreport_failure)(msg_format, ap); + va_end(ap); +} + +/* + * Report a general warning. + */ +void +report_warning(const char *msg_format, ...) +{ + va_list ap; + + va_start(ap, msg_format); + (*routines->vreport_warning)(msg_format, ap); + va_end(ap); +} + +/* + * Report an error when trying to open or create a file. + * "err" is assumed to be an error code from Wiretap; positive values are + * UNIX-style errnos, so this can be used for open failures not from + * Wiretap as long as the failure code is just an errno. + */ +void +report_open_failure(const char *filename, int err, + bool for_writing) +{ + (*routines->report_open_failure)(filename, err, for_writing); +} + +/* + * Report an error when trying to read a file. + * "err" is assumed to be a UNIX-style errno. + */ +void +report_read_failure(const char *filename, int err) +{ + (*routines->report_read_failure)(filename, err); +} + +/* + * Report an error when trying to write a file. + * "err" is assumed to be a UNIX-style errno. + */ +void +report_write_failure(const char *filename, int err) +{ + (*routines->report_write_failure)(filename, err); +} + +/* + * Report an error from opening a capture file for reading. + */ +void +report_cfile_open_failure(const char *filename, int err, char *err_info) +{ + (*routines->report_cfile_open_failure)(filename, err, err_info); +} + +/* + * Report an error from opening a capture file for writing. + */ +void +report_cfile_dump_open_failure(const char *filename, + int err, char *err_info, int file_type_subtype) +{ + (*routines->report_cfile_dump_open_failure)(filename, + err, err_info, file_type_subtype); +} + +/* + * Report an error from attempting to read from a capture file. + */ +void +report_cfile_read_failure(const char *filename, int err, char *err_info) +{ + (*routines->report_cfile_read_failure)(filename, err, err_info); +} + +/* + * Report an error from attempting to write to a capture file. + */ +void +report_cfile_write_failure(const char *in_filename, const char *out_filename, + int err, char *err_info, uint32_t framenum, int file_type_subtype) +{ + (*routines->report_cfile_write_failure)(in_filename, out_filename, + err, err_info, framenum, file_type_subtype); +} + +/* + * Report an error from closing a capture file open for writing. + */ +void +report_cfile_close_failure(const char *filename, int err, char *err_info) +{ + (*routines->report_cfile_close_failure)(filename, err, err_info); +} + +/* + * Return the "friendly" program name. + */ +const char * +get_friendly_program_name(void) +{ + return friendly_program_name; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 8 + * tab-width: 8 + * indent-tabs-mode: t + * End: + * + * vi: set shiftwidth=8 tabstop=8 noexpandtab: + * :indentSize=8:tabSize=8:noTabs=false: + */ diff --git a/wsutil/report_message.h b/wsutil/report_message.h new file mode 100644 index 00000000..7be1808b --- /dev/null +++ b/wsutil/report_message.h @@ -0,0 +1,120 @@ +/** @file + * Declarations of routines for code that can run in GUI and command-line + * environments to use to report errors and warnings to the user (e.g., + * I/O errors, or problems with preference settings) if the message should + * be shown as a GUI error in a GUI environment. + * + * The application using libwsutil will register message-reporting + * routines, and the routines declared here will call the registered + * routines. That way, these routines can be called by code that + * doesn't itself know whether to pop up a dialog or print something + * to the standard error. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __REPORT_MESSAGE_H__ +#define __REPORT_MESSAGE_H__ + +#include <wireshark.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * Initialize the report message routines + */ +struct report_message_routines { + void (*vreport_failure)(const char *, va_list); + void (*vreport_warning)(const char *, va_list); + void (*report_open_failure)(const char *, int, gboolean); + void (*report_read_failure)(const char *, int); + void (*report_write_failure)(const char *, int); + void (*report_cfile_open_failure)(const char *, int, char *); + void (*report_cfile_dump_open_failure)(const char *, int, char *, int); + void (*report_cfile_read_failure)(const char *, int, char *); + void (*report_cfile_write_failure)(const char *, const char *, + int, char *, uint32_t, int); + void (*report_cfile_close_failure)(const char *, int, char *); +}; + +WS_DLL_PUBLIC void init_report_message(const char *friendly_program_name, + const struct report_message_routines *routines); + +/* + * Report a general error. + */ +WS_DLL_PUBLIC void report_failure(const char *msg_format, ...) G_GNUC_PRINTF(1, 2); + +/* + * Report a general warning. + */ +WS_DLL_PUBLIC void report_warning(const char *msg_format, ...) G_GNUC_PRINTF(1, 2); + +/* + * Report an error when trying to open a file. + * "err" is assumed to be an error code from Wiretap; positive values are + * UNIX-style errnos, so this can be used for open failures not from + * Wiretap as long as the failure code is just an errno. + */ +WS_DLL_PUBLIC void report_open_failure(const char *filename, int err, + bool for_writing); + +/* + * Report an error when trying to read a file. + * "err" is assumed to be a UNIX-style errno. + */ +WS_DLL_PUBLIC void report_read_failure(const char *filename, int err); + +/* + * Report an error when trying to write a file. + * "err" is assumed to be a UNIX-style errno. + */ +WS_DLL_PUBLIC void report_write_failure(const char *filename, int err); + +/* + * Report an error from opening a capture file for reading. + */ +WS_DLL_PUBLIC void report_cfile_open_failure(const char *filename, + int err, char *err_info); + +/* + * Report an error from opening a capture file for writing. + */ +WS_DLL_PUBLIC void report_cfile_dump_open_failure(const char *filename, + int err, char *err_info, int file_type_subtype); + +/* + * Report an error from attempting to read from a capture file. + */ +WS_DLL_PUBLIC void report_cfile_read_failure(const char *filename, + int err, char *err_info); + +/* + * Report an error from attempting to write to a capture file. + */ +WS_DLL_PUBLIC void report_cfile_write_failure(const char *in_filename, + const char *out_filename, int err, char *err_info, uint32_t framenum, + int file_type_subtype); + +/* + * Report an error from closing a capture file open for writing. + */ +WS_DLL_PUBLIC void report_cfile_close_failure(const char *filename, + int err, char *err_info); + +/* + * Return the "friendly" program name. + */ +WS_DLL_PUBLIC const char *get_friendly_program_name(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __REPORT_MESSAGE_H__ */ diff --git a/wsutil/rsa.c b/wsutil/rsa.c new file mode 100644 index 00000000..22afe08f --- /dev/null +++ b/wsutil/rsa.c @@ -0,0 +1,376 @@ +/* rsa.c + * + * Functions for RSA private key reading and use + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 2007 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" +#define WS_LOG_DOMAIN LOG_DOMAIN_WSUTIL + +#include "rsa.h" +#include "filesystem.h" +#include "file_util.h" +#include <errno.h> +#include <wsutil/wslog.h> + + +#ifdef HAVE_LIBGNUTLS + +#include <gnutls/abstract.h> +#include <gnutls/pkcs12.h> + +/* RSA private key file processing {{{ */ +#define RSA_PARS 6 +gcry_sexp_t +rsa_privkey_to_sexp(gnutls_x509_privkey_t priv_key, char **err) +{ + gnutls_datum_t rsa_datum[RSA_PARS]; /* m, e, d, p, q, u */ + size_t tmp_size; + gcry_error_t gret; + gcry_sexp_t rsa_priv_key = NULL; + int i; + gcry_mpi_t rsa_params[RSA_PARS]; + *err = NULL; + + /* RSA get parameter */ + if (gnutls_x509_privkey_export_rsa_raw(priv_key, + &rsa_datum[0], + &rsa_datum[1], + &rsa_datum[2], + &rsa_datum[3], + &rsa_datum[4], + &rsa_datum[5]) != 0) { + *err = g_strdup("can't export rsa param (is a rsa private key file ?!?)"); + return NULL; + } + + /* convert each rsa parameter to mpi format*/ + for(i=0; i<RSA_PARS; i++) { + gret = gcry_mpi_scan(&rsa_params[i], GCRYMPI_FMT_USG, rsa_datum[i].data, rsa_datum[i].size,&tmp_size); + /* these buffers were allocated by gnutls_x509_privkey_export_rsa_raw() */ + g_free(rsa_datum[i].data); + if (gret != 0) { + *err = ws_strdup_printf("can't convert m rsa param to int (size %d)", rsa_datum[i].size); + return NULL; + } + } + + /* libgcrypt expects p < q, and gnutls might not return it as such, depending on gnutls version and its crypto backend */ + if (gcry_mpi_cmp(rsa_params[3], rsa_params[4]) > 0) + { + /* p, q = q, p */ + gcry_mpi_swap(rsa_params[3], rsa_params[4]); + /* due to swapping p and q, u = p^-1 mod p which happens to be needed. */ + } + /* libgcrypt expects u = p^-1 mod q (for OpenPGP), but the u parameter + * says u = q^-1 mod p. Recompute u = p^-1 mod q. Do this unconditionally as + * at least GnuTLS 2.12.23 computes an invalid value. */ + gcry_mpi_invm(rsa_params[5], rsa_params[3], rsa_params[4]); + + if (gcry_sexp_build( &rsa_priv_key, NULL, + "(private-key(rsa((n%m)(e%m)(d%m)(p%m)(q%m)(u%m))))", rsa_params[0], + rsa_params[1], rsa_params[2], rsa_params[3], rsa_params[4], + rsa_params[5]) != 0) { + *err = g_strdup("can't build rsa private key s-exp"); + return NULL; + } + + for (i=0; i< 6; i++) + gcry_mpi_release(rsa_params[i]); + return rsa_priv_key; +} + +gnutls_x509_privkey_t +rsa_load_pem_key(FILE *fp, char **err) +{ + /* gnutls makes our work much harder, since we have to work internally with + * s-exp formatted data, but PEM loader exports only in "gnutls_datum_t" + * format, and a datum -> s-exp conversion function does not exist. + */ + gnutls_x509_privkey_t priv_key; + gnutls_datum_t key; + ws_statb64 statbuf; + int ret; + unsigned bytes; + *err = NULL; + + if (ws_fstat64(ws_fileno(fp), &statbuf) == -1) { + *err = ws_strdup_printf("can't ws_fstat64 file: %s", g_strerror(errno)); + return NULL; + } + if (S_ISDIR(statbuf.st_mode)) { + *err = g_strdup("file is a directory"); + errno = EISDIR; + return NULL; + } + if (S_ISFIFO(statbuf.st_mode)) { + *err = g_strdup("file is a named pipe"); + errno = EINVAL; + return NULL; + } + if (!S_ISREG(statbuf.st_mode)) { + *err = g_strdup("file is not a regular file"); + errno = EINVAL; + return NULL; + } + /* XXX - check for a too-big size */ + /* load all file contents into a datum buffer*/ + key.data = (unsigned char *)g_malloc((size_t)statbuf.st_size); + key.size = (int)statbuf.st_size; + bytes = (unsigned) fread(key.data, 1, key.size, fp); + if (bytes < key.size) { + if (bytes == 0 && ferror(fp)) { + *err = ws_strdup_printf("can't read from file %d bytes, got error %s", + key.size, g_strerror(errno)); + } else { + *err = ws_strdup_printf("can't read from file %d bytes, got %d", + key.size, bytes); + } + g_free(key.data); + return NULL; + } + + /* init private key data*/ + gnutls_x509_privkey_init(&priv_key); + + /* import PEM data*/ + if ((ret = gnutls_x509_privkey_import(priv_key, &key, GNUTLS_X509_FMT_PEM)) != GNUTLS_E_SUCCESS) { + *err = ws_strdup_printf("can't import pem data: %s", gnutls_strerror(ret)); + g_free(key.data); + gnutls_x509_privkey_deinit(priv_key); + return NULL; + } + + if (gnutls_x509_privkey_get_pk_algorithm(priv_key) != GNUTLS_PK_RSA) { + *err = g_strdup("private key public key algorithm isn't RSA"); + g_free(key.data); + gnutls_x509_privkey_deinit(priv_key); + return NULL; + } + + g_free(key.data); + + return priv_key; +} + +static const char * +BAGTYPE(gnutls_pkcs12_bag_type_t x) { + switch (x) { + case GNUTLS_BAG_EMPTY: return "Empty"; + case GNUTLS_BAG_PKCS8_ENCRYPTED_KEY: return "PKCS#8 Encrypted key"; + case GNUTLS_BAG_PKCS8_KEY: return "PKCS#8 Key"; + case GNUTLS_BAG_CERTIFICATE: return "Certificate"; + case GNUTLS_BAG_CRL: return "CRL"; + case GNUTLS_BAG_ENCRYPTED: return "Encrypted"; + case GNUTLS_BAG_UNKNOWN: return "Unknown"; + default: return "<undefined>"; + } +} + +gnutls_x509_privkey_t +rsa_load_pkcs12(FILE *fp, const char *cert_passwd, char **err) +{ + int i, j, ret; + int rest; + unsigned char *p; + gnutls_datum_t data; + gnutls_pkcs12_bag_t bag = NULL; + size_t len; + + gnutls_pkcs12_t rsa_p12 = NULL; + + gnutls_x509_privkey_t priv_key = NULL; + *err = NULL; + + rest = 4096; + data.data = (unsigned char *)g_malloc(rest); + data.size = rest; + p = data.data; + while ((len = fread(p, 1, rest, fp)) > 0) { + p += len; + rest -= (int) len; + if (!rest) { + rest = 1024; + data.data = (unsigned char *)g_realloc(data.data, data.size + rest); + p = data.data + data.size; + data.size += rest; + } + } + data.size -= rest; + if (!feof(fp)) { + *err = g_strdup("Error during certificate reading."); + g_free(data.data); + return NULL; + } + + ret = gnutls_pkcs12_init(&rsa_p12); + if (ret < 0) { + *err = ws_strdup_printf("gnutls_pkcs12_init(&st_p12) - %s", gnutls_strerror(ret)); + g_free(data.data); + return NULL; + } + + /* load PKCS#12 in DER or PEM format */ + ret = gnutls_pkcs12_import(rsa_p12, &data, GNUTLS_X509_FMT_DER, 0); + if (ret < 0) { + ret = gnutls_pkcs12_import(rsa_p12, &data, GNUTLS_X509_FMT_PEM, 0); + if (ret < 0) { + *err = ws_strdup_printf("could not load PKCS#12 in DER or PEM format: %s", gnutls_strerror(ret)); + } + } + g_free(data.data); + if (ret < 0) { + gnutls_pkcs12_deinit(rsa_p12); + return NULL; + } + + ws_debug("grsa_privkey_to_sexp: PKCS#12 imported"); + + /* TODO: Use gnutls_pkcs12_simple_parse, since 3.1.0 (August 2012) */ + for (i=0; ; i++) { + gnutls_pkcs12_bag_type_t bag_type; + + ret = gnutls_pkcs12_bag_init(&bag); + if (ret < 0) { + *err = ws_strdup_printf("gnutls_pkcs12_bag_init failed: %s", + gnutls_strerror(ret)); + goto done; + } + + ret = gnutls_pkcs12_get_bag(rsa_p12, i, bag); + if (ret < 0) { + *err = ws_strdup_printf("gnutls_pkcs12_get_bag failed: %s", + gnutls_strerror(ret)); + goto done; + } + + for (j=0; j<gnutls_pkcs12_bag_get_count(bag); j++) { + + ret = gnutls_pkcs12_bag_get_type(bag, j); + if (ret < 0) { + *err = ws_strdup_printf("gnutls_pkcs12_bag_get_type failed: %s", + gnutls_strerror(ret)); + goto done; + } + bag_type = (gnutls_pkcs12_bag_type_t)ret; + if (bag_type >= GNUTLS_BAG_UNKNOWN) { + *err = ws_strdup_printf("gnutls_pkcs12_bag_get_type returned unknown bag type %u", + ret); + goto done; + } + ws_debug("Bag %d/%d: %s", i, j, BAGTYPE(bag_type)); + if (bag_type == GNUTLS_BAG_ENCRYPTED) { + ret = gnutls_pkcs12_bag_decrypt(bag, cert_passwd); + if (ret == 0) { + ret = gnutls_pkcs12_bag_get_type(bag, j); + if (ret < 0) { + *err = ws_strdup_printf("gnutls_pkcs12_bag_get_type failed: %s", + gnutls_strerror(ret)); + goto done; + } + bag_type = (gnutls_pkcs12_bag_type_t)ret; + if (bag_type >= GNUTLS_BAG_UNKNOWN) { + *err = ws_strdup_printf("gnutls_pkcs12_bag_get_type returned unknown bag type %u", + ret); + goto done; + } + ws_debug("Bag %d/%d decrypted: %s", i, j, BAGTYPE(bag_type)); + } + } + + ret = gnutls_pkcs12_bag_get_data(bag, j, &data); + if (ret < 0) { + *err = ws_strdup_printf("gnutls_pkcs12_bag_get_data failed: %s", + gnutls_strerror(ret)); + goto done; + } + + switch (bag_type) { + + case GNUTLS_BAG_PKCS8_KEY: + case GNUTLS_BAG_PKCS8_ENCRYPTED_KEY: + { + gnutls_x509_privkey_t rsa_pkey; + + ret = gnutls_x509_privkey_init(&rsa_pkey); + if (ret < 0) { + *err = ws_strdup_printf("gnutls_x509_privkey_init failed: %s", gnutls_strerror(ret)); + goto done; + } + ret = gnutls_x509_privkey_import_pkcs8(rsa_pkey, &data, GNUTLS_X509_FMT_DER, cert_passwd, + (bag_type==GNUTLS_BAG_PKCS8_KEY) ? GNUTLS_PKCS_PLAIN : 0); + if (ret < 0) { + *err = ws_strdup_printf("Can not decrypt private key - %s", gnutls_strerror(ret)); + gnutls_x509_privkey_deinit(rsa_pkey); + goto done; + } + + if (gnutls_x509_privkey_get_pk_algorithm(rsa_pkey) != GNUTLS_PK_RSA) { + *err = g_strdup("private key public key algorithm isn't RSA"); + gnutls_x509_privkey_deinit(rsa_pkey); + goto done; + } + + /* Private key found, return it. */ + priv_key = rsa_pkey; + goto done; + } + + default: ; + } + } /* j */ + + gnutls_pkcs12_bag_deinit(bag); + bag = NULL; + } /* i */ + +done: + if (bag) { + gnutls_pkcs12_bag_deinit(bag); + } + if (!priv_key) { + /* + * We failed. If we didn't fail with an error, we failed because + * we found no PKCS8 key and fell out of the loop; report that + * error. + */ + if (*err == NULL) + *err = g_strdup("no PKCS8 key found"); + } + gnutls_pkcs12_deinit(rsa_p12); + + return priv_key; +} + +void +rsa_private_key_free(void * key) +{ + gcry_sexp_release((gcry_sexp_t) key); +} + +#else /* ! defined(HAVE_LIBGNUTLS) */ + +void +rsa_private_key_free(void * key _U_) +{ +} + +#endif /* HAVE_LIBGNUTLS */ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/rsa.h b/wsutil/rsa.h new file mode 100644 index 00000000..83817048 --- /dev/null +++ b/wsutil/rsa.h @@ -0,0 +1,44 @@ +/** @file + * + * Functions for RSA private key reading and use + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 2007 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __RSA_H__ +#define __RSA_H__ + +#include <wireshark.h> +#include <gcrypt.h> + +#ifdef HAVE_LIBGNUTLS +#include <stdio.h> +#include <gnutls/abstract.h> +WS_DLL_PUBLIC gcry_sexp_t rsa_privkey_to_sexp(gnutls_x509_privkey_t priv_key, char **err); + +/** + * Load an RSA private key from specified file + * @param fp the file that contain the key data + * @param [out] err error message upon failure; NULL upon success + * @return a pointer to the loaded key on success, or NULL upon failure + */ +WS_DLL_PUBLIC gnutls_x509_privkey_t rsa_load_pem_key(FILE* fp, char **err); + +/** + * Load a RSA private key from a PKCS#12 file (DER or PEM format) + * @param fp the file that contains the key data + * @param cert_passwd password to decrypt the PKCS#12 file + * @param [out] err error message upon failure; NULL upon success + * @return a pointer to the loaded key on success; NULL upon failure + */ +WS_DLL_PUBLIC gnutls_x509_privkey_t rsa_load_pkcs12(FILE* fp, const char *cert_passwd, char** err); +#endif + +WS_DLL_PUBLIC void rsa_private_key_free(void * key); + + +#endif /* __RSA_H__ */ diff --git a/wsutil/safe-math.h b/wsutil/safe-math.h new file mode 100644 index 00000000..f40ee16e --- /dev/null +++ b/wsutil/safe-math.h @@ -0,0 +1,1064 @@ +/* Overflow-safe math functions + * Portable Snippets - https://github.com/nemequ/portable-snippets + * Created by Evan Nemerson <evan@nemerson.com> + * + * To the extent possible under law, the authors have waived all + * copyright and related or neighboring rights to this code. For + * details, see the Creative Commons Zero 1.0 Universal license at + * https://creativecommons.org/publicdomain/zero/1.0/ + */ + +#if !defined(PSNIP_SAFE_H) +#define PSNIP_SAFE_H + +#if !defined(PSNIP_SAFE_FORCE_PORTABLE) +# if defined(__has_builtin) +# if __has_builtin(__builtin_add_overflow) && !defined(__ibmxl__) +# define PSNIP_SAFE_HAVE_BUILTIN_OVERFLOW +# endif +# elif defined(__GNUC__) && (__GNUC__ >= 5) && !defined(__INTEL_COMPILER) +# define PSNIP_SAFE_HAVE_BUILTIN_OVERFLOW +# endif +# if defined(__has_include) +# if __has_include(<intsafe.h>) +# define PSNIP_SAFE_HAVE_INTSAFE_H +# endif +# elif defined(_WIN32) +# define PSNIP_SAFE_HAVE_INTSAFE_H +# endif +#endif /* !defined(PSNIP_SAFE_FORCE_PORTABLE) */ + +#if defined(__GNUC__) +# define PSNIP_SAFE_LIKELY(expr) __builtin_expect(!!(expr), 1) +# define PSNIP_SAFE_UNLIKELY(expr) __builtin_expect(!!(expr), 0) +#else +# define PSNIP_SAFE_LIKELY(expr) !!(expr) +# define PSNIP_SAFE_UNLIKELY(expr) !!(expr) +#endif /* defined(__GNUC__) */ + +#if !defined(PSNIP_SAFE_STATIC_INLINE) +# if defined(__GNUC__) +# define PSNIP_SAFE__COMPILER_ATTRIBUTES __attribute__((__unused__)) +# else +# define PSNIP_SAFE__COMPILER_ATTRIBUTES +# endif + +# if defined(HEDLEY_INLINE) +# define PSNIP_SAFE__INLINE HEDLEY_INLINE +# elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +# define PSNIP_SAFE__INLINE inline +# elif defined(__GNUC_STDC_INLINE__) +# define PSNIP_SAFE__INLINE __inline__ +# elif defined(_MSC_VER) && _MSC_VER >= 1200 +# define PSNIP_SAFE__INLINE __inline +# else +# define PSNIP_SAFE__INLINE +# endif + +# define PSNIP_SAFE__FUNCTION PSNIP_SAFE__COMPILER_ATTRIBUTES static PSNIP_SAFE__INLINE +#endif + +#if !defined(__cplusplus) && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +# define psnip_safe_bool _Bool +#else +# define psnip_safe_bool int +#endif + +#if !defined(PSNIP_SAFE_NO_FIXED) +/* For maximum portability include the exact-int module from + portable snippets. */ +# if \ + !defined(psnip_int64_t) || !defined(psnip_uint64_t) || \ + !defined(psnip_int32_t) || !defined(psnip_uint32_t) || \ + !defined(psnip_int16_t) || !defined(psnip_uint16_t) || \ + !defined(psnip_int8_t) || !defined(psnip_uint8_t) +# include <stdint.h> +# if !defined(psnip_int64_t) +# define psnip_int64_t int64_t +# endif +# if !defined(psnip_uint64_t) +# define psnip_uint64_t uint64_t +# endif +# if !defined(psnip_int32_t) +# define psnip_int32_t int32_t +# endif +# if !defined(psnip_uint32_t) +# define psnip_uint32_t uint32_t +# endif +# if !defined(psnip_int16_t) +# define psnip_int16_t int16_t +# endif +# if !defined(psnip_uint16_t) +# define psnip_uint16_t uint16_t +# endif +# if !defined(psnip_int8_t) +# define psnip_int8_t int8_t +# endif +# if !defined(psnip_uint8_t) +# define psnip_uint8_t uint8_t +# endif +# endif +#endif /* !defined(PSNIP_SAFE_NO_FIXED) */ +#include <limits.h> +#include <stdlib.h> + +#if !defined(PSNIP_SAFE_SIZE_MAX) +# if defined(__SIZE_MAX__) +# define PSNIP_SAFE_SIZE_MAX __SIZE_MAX__ +# elif defined(PSNIP_EXACT_INT_HAVE_STDINT) +# include <stdint.h> +# endif +#endif + +#if defined(PSNIP_SAFE_SIZE_MAX) +# define PSNIP_SAFE__SIZE_MAX_RT PSNIP_SAFE_SIZE_MAX +#else +# define PSNIP_SAFE__SIZE_MAX_RT (~((size_t) 0)) +#endif + +#if defined(PSNIP_SAFE_HAVE_INTSAFE_H) +/* In VS 10, stdint.h and intsafe.h both define (U)INTN_MIN/MAX, which + triggers warning C4005 (level 1). */ +# if defined(_MSC_VER) && (_MSC_VER == 1600) +# pragma warning(push) +# pragma warning(disable:4005) +# endif +# include <intsafe.h> +# if defined(_MSC_VER) && (_MSC_VER == 1600) +# pragma warning(pop) +# endif +#endif /* defined(PSNIP_SAFE_HAVE_INTSAFE_H) */ + +/* If there is a type larger than the one we're concerned with it's + * likely much faster to simply promote the operands, perform the + * requested operation, verify that the result falls within the + * original type, then cast the result back to the original type. */ + +#if !defined(PSNIP_SAFE_NO_PROMOTIONS) + +#define PSNIP_SAFE_DEFINE_LARGER_BINARY_OP(T, name, op_name, op) \ + PSNIP_SAFE__FUNCTION psnip_safe_##name##_larger \ + psnip_safe_larger_##name##_##op_name (T a, T b) { \ + return ((psnip_safe_##name##_larger) a) op ((psnip_safe_##name##_larger) b); \ + } + +#define PSNIP_SAFE_DEFINE_LARGER_UNARY_OP(T, name, op_name, op) \ + PSNIP_SAFE__FUNCTION psnip_safe_##name##_larger \ + psnip_safe_larger_##name##_##op_name (T value) { \ + return (op ((psnip_safe_##name##_larger) value)); \ + } + +#define PSNIP_SAFE_DEFINE_LARGER_SIGNED_OPS(T, name) \ + PSNIP_SAFE_DEFINE_LARGER_BINARY_OP(T, name, add, +) \ + PSNIP_SAFE_DEFINE_LARGER_BINARY_OP(T, name, sub, -) \ + PSNIP_SAFE_DEFINE_LARGER_BINARY_OP(T, name, mul, *) \ + PSNIP_SAFE_DEFINE_LARGER_BINARY_OP(T, name, div, /) \ + PSNIP_SAFE_DEFINE_LARGER_BINARY_OP(T, name, mod, %) \ + PSNIP_SAFE_DEFINE_LARGER_UNARY_OP (T, name, neg, -) + +#define PSNIP_SAFE_DEFINE_LARGER_UNSIGNED_OPS(T, name) \ + PSNIP_SAFE_DEFINE_LARGER_BINARY_OP(T, name, add, +) \ + PSNIP_SAFE_DEFINE_LARGER_BINARY_OP(T, name, sub, -) \ + PSNIP_SAFE_DEFINE_LARGER_BINARY_OP(T, name, mul, *) \ + PSNIP_SAFE_DEFINE_LARGER_BINARY_OP(T, name, div, /) \ + PSNIP_SAFE_DEFINE_LARGER_BINARY_OP(T, name, mod, %) + +#define PSNIP_SAFE_IS_LARGER(ORIG_MAX, DEST_MAX) ((DEST_MAX / ORIG_MAX) >= ORIG_MAX) + +#if defined(__GNUC__) && ((__GNUC__ >= 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) && defined(__SIZEOF_INT128__) && !defined(__ibmxl__) +#define PSNIP_SAFE_HAVE_128 +typedef __int128 psnip_safe_int128_t; +typedef unsigned __int128 psnip_safe_uint128_t; +#endif /* defined(__GNUC__) */ + +#if !defined(PSNIP_SAFE_NO_FIXED) +#define PSNIP_SAFE_HAVE_INT8_LARGER +#define PSNIP_SAFE_HAVE_UINT8_LARGER +typedef psnip_int16_t psnip_safe_int8_larger; +typedef psnip_uint16_t psnip_safe_uint8_larger; + +#define PSNIP_SAFE_HAVE_INT16_LARGER +typedef psnip_int32_t psnip_safe_int16_larger; +typedef psnip_uint32_t psnip_safe_uint16_larger; + +#define PSNIP_SAFE_HAVE_INT32_LARGER +typedef psnip_int64_t psnip_safe_int32_larger; +typedef psnip_uint64_t psnip_safe_uint32_larger; + +#if defined(PSNIP_SAFE_HAVE_128) +#define PSNIP_SAFE_HAVE_INT64_LARGER +typedef psnip_safe_int128_t psnip_safe_int64_larger; +typedef psnip_safe_uint128_t psnip_safe_uint64_larger; +#endif /* defined(PSNIP_SAFE_HAVE_128) */ +#endif /* !defined(PSNIP_SAFE_NO_FIXED) */ + +#define PSNIP_SAFE_HAVE_LARGER_SCHAR +#if PSNIP_SAFE_IS_LARGER(SCHAR_MAX, SHRT_MAX) +typedef short psnip_safe_schar_larger; +#elif PSNIP_SAFE_IS_LARGER(SCHAR_MAX, INT_MAX) +typedef int psnip_safe_schar_larger; +#elif PSNIP_SAFE_IS_LARGER(SCHAR_MAX, LONG_MAX) +typedef long psnip_safe_schar_larger; +#elif PSNIP_SAFE_IS_LARGER(SCHAR_MAX, LLONG_MAX) +typedef long long psnip_safe_schar_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && PSNIP_SAFE_IS_LARGER(SCHAR_MAX, 0x7fff) +typedef psnip_int16_t psnip_safe_schar_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && PSNIP_SAFE_IS_LARGER(SCHAR_MAX, 0x7fffffffLL) +typedef psnip_int32_t psnip_safe_schar_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && PSNIP_SAFE_IS_LARGER(SCHAR_MAX, 0x7fffffffffffffffLL) +typedef psnip_int64_t psnip_safe_schar_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && defined(PSNIP_SAFE_HAVE_128) && (SCHAR_MAX <= 0x7fffffffffffffffLL) +typedef psnip_safe_int128_t psnip_safe_schar_larger; +#else +#undef PSNIP_SAFE_HAVE_LARGER_SCHAR +#endif + +#define PSNIP_SAFE_HAVE_LARGER_UCHAR +#if PSNIP_SAFE_IS_LARGER(UCHAR_MAX, USHRT_MAX) +typedef unsigned short psnip_safe_uchar_larger; +#elif PSNIP_SAFE_IS_LARGER(UCHAR_MAX, UINT_MAX) +typedef unsigned int psnip_safe_uchar_larger; +#elif PSNIP_SAFE_IS_LARGER(UCHAR_MAX, ULONG_MAX) +typedef unsigned long psnip_safe_uchar_larger; +#elif PSNIP_SAFE_IS_LARGER(UCHAR_MAX, ULLONG_MAX) +typedef unsigned long long psnip_safe_uchar_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && PSNIP_SAFE_IS_LARGER(UCHAR_MAX, 0xffffU) +typedef psnip_uint16_t psnip_safe_uchar_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && PSNIP_SAFE_IS_LARGER(UCHAR_MAX, 0xffffffffUL) +typedef psnip_uint32_t psnip_safe_uchar_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && PSNIP_SAFE_IS_LARGER(UCHAR_MAX, 0xffffffffffffffffULL) +typedef psnip_uint64_t psnip_safe_uchar_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && defined(PSNIP_SAFE_HAVE_128) && (UCHAR_MAX <= 0xffffffffffffffffULL) +typedef psnip_safe_uint128_t psnip_safe_uchar_larger; +#else +#undef PSNIP_SAFE_HAVE_LARGER_UCHAR +#endif + +#if CHAR_MIN == 0 && defined(PSNIP_SAFE_HAVE_LARGER_UCHAR) +#define PSNIP_SAFE_HAVE_LARGER_CHAR +typedef psnip_safe_uchar_larger psnip_safe_char_larger; +#elif CHAR_MIN < 0 && defined(PSNIP_SAFE_HAVE_LARGER_SCHAR) +#define PSNIP_SAFE_HAVE_LARGER_CHAR +typedef psnip_safe_schar_larger psnip_safe_char_larger; +#endif + +#define PSNIP_SAFE_HAVE_LARGER_SHRT +#if PSNIP_SAFE_IS_LARGER(SHRT_MAX, INT_MAX) +typedef int psnip_safe_short_larger; +#elif PSNIP_SAFE_IS_LARGER(SHRT_MAX, LONG_MAX) +typedef long psnip_safe_short_larger; +#elif PSNIP_SAFE_IS_LARGER(SHRT_MAX, LLONG_MAX) +typedef long long psnip_safe_short_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && PSNIP_SAFE_IS_LARGER(SHRT_MAX, 0x7fff) +typedef psnip_int16_t psnip_safe_short_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && PSNIP_SAFE_IS_LARGER(SHRT_MAX, 0x7fffffffLL) +typedef psnip_int32_t psnip_safe_short_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && PSNIP_SAFE_IS_LARGER(SHRT_MAX, 0x7fffffffffffffffLL) +typedef psnip_int64_t psnip_safe_short_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && defined(PSNIP_SAFE_HAVE_128) && (SHRT_MAX <= 0x7fffffffffffffffLL) +typedef psnip_safe_int128_t psnip_safe_short_larger; +#else +#undef PSNIP_SAFE_HAVE_LARGER_SHRT +#endif + +#define PSNIP_SAFE_HAVE_LARGER_USHRT +#if PSNIP_SAFE_IS_LARGER(USHRT_MAX, UINT_MAX) +typedef unsigned int psnip_safe_ushort_larger; +#elif PSNIP_SAFE_IS_LARGER(USHRT_MAX, ULONG_MAX) +typedef unsigned long psnip_safe_ushort_larger; +#elif PSNIP_SAFE_IS_LARGER(USHRT_MAX, ULLONG_MAX) +typedef unsigned long long psnip_safe_ushort_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && PSNIP_SAFE_IS_LARGER(USHRT_MAX, 0xffff) +typedef psnip_uint16_t psnip_safe_ushort_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && PSNIP_SAFE_IS_LARGER(USHRT_MAX, 0xffffffffUL) +typedef psnip_uint32_t psnip_safe_ushort_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && PSNIP_SAFE_IS_LARGER(USHRT_MAX, 0xffffffffffffffffULL) +typedef psnip_uint64_t psnip_safe_ushort_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && defined(PSNIP_SAFE_HAVE_128) && (USHRT_MAX <= 0xffffffffffffffffULL) +typedef psnip_safe_uint128_t psnip_safe_ushort_larger; +#else +#undef PSNIP_SAFE_HAVE_LARGER_USHRT +#endif + +#define PSNIP_SAFE_HAVE_LARGER_INT +#if PSNIP_SAFE_IS_LARGER(INT_MAX, LONG_MAX) +typedef long psnip_safe_int_larger; +#elif PSNIP_SAFE_IS_LARGER(INT_MAX, LLONG_MAX) +typedef long long psnip_safe_int_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && PSNIP_SAFE_IS_LARGER(INT_MAX, 0x7fff) +typedef psnip_int16_t psnip_safe_int_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && PSNIP_SAFE_IS_LARGER(INT_MAX, 0x7fffffffLL) +typedef psnip_int32_t psnip_safe_int_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && PSNIP_SAFE_IS_LARGER(INT_MAX, 0x7fffffffffffffffLL) +typedef psnip_int64_t psnip_safe_int_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && defined(PSNIP_SAFE_HAVE_128) && (INT_MAX <= 0x7fffffffffffffffLL) +typedef psnip_safe_int128_t psnip_safe_int_larger; +#else +#undef PSNIP_SAFE_HAVE_LARGER_INT +#endif + +#define PSNIP_SAFE_HAVE_LARGER_UINT +#if PSNIP_SAFE_IS_LARGER(UINT_MAX, ULONG_MAX) +typedef unsigned long psnip_safe_uint_larger; +#elif PSNIP_SAFE_IS_LARGER(UINT_MAX, ULLONG_MAX) +typedef unsigned long long psnip_safe_uint_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && PSNIP_SAFE_IS_LARGER(UINT_MAX, 0xffff) +typedef psnip_uint16_t psnip_safe_uint_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && PSNIP_SAFE_IS_LARGER(UINT_MAX, 0xffffffffUL) +typedef psnip_uint32_t psnip_safe_uint_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && PSNIP_SAFE_IS_LARGER(UINT_MAX, 0xffffffffffffffffULL) +typedef psnip_uint64_t psnip_safe_uint_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && defined(PSNIP_SAFE_HAVE_128) && (UINT_MAX <= 0xffffffffffffffffULL) +typedef psnip_safe_uint128_t psnip_safe_uint_larger; +#else +#undef PSNIP_SAFE_HAVE_LARGER_UINT +#endif + +#define PSNIP_SAFE_HAVE_LARGER_LONG +#if PSNIP_SAFE_IS_LARGER(LONG_MAX, LLONG_MAX) +typedef long long psnip_safe_long_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && PSNIP_SAFE_IS_LARGER(LONG_MAX, 0x7fff) +typedef psnip_int16_t psnip_safe_long_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && PSNIP_SAFE_IS_LARGER(LONG_MAX, 0x7fffffffLL) +typedef psnip_int32_t psnip_safe_long_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && PSNIP_SAFE_IS_LARGER(LONG_MAX, 0x7fffffffffffffffLL) +typedef psnip_int64_t psnip_safe_long_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && defined(PSNIP_SAFE_HAVE_128) && (LONG_MAX <= 0x7fffffffffffffffLL) +typedef psnip_safe_int128_t psnip_safe_long_larger; +#else +#undef PSNIP_SAFE_HAVE_LARGER_LONG +#endif + +#define PSNIP_SAFE_HAVE_LARGER_ULONG +#if PSNIP_SAFE_IS_LARGER(ULONG_MAX, ULLONG_MAX) +typedef unsigned long long psnip_safe_ulong_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && PSNIP_SAFE_IS_LARGER(ULONG_MAX, 0xffff) +typedef psnip_uint16_t psnip_safe_ulong_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && PSNIP_SAFE_IS_LARGER(ULONG_MAX, 0xffffffffUL) +typedef psnip_uint32_t psnip_safe_ulong_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && PSNIP_SAFE_IS_LARGER(ULONG_MAX, 0xffffffffffffffffULL) +typedef psnip_uint64_t psnip_safe_ulong_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && defined(PSNIP_SAFE_HAVE_128) && (ULONG_MAX <= 0xffffffffffffffffULL) +typedef psnip_safe_uint128_t psnip_safe_ulong_larger; +#else +#undef PSNIP_SAFE_HAVE_LARGER_ULONG +#endif + +#define PSNIP_SAFE_HAVE_LARGER_LLONG +#if !defined(PSNIP_SAFE_NO_FIXED) && PSNIP_SAFE_IS_LARGER(LLONG_MAX, 0x7fff) +typedef psnip_int16_t psnip_safe_llong_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && PSNIP_SAFE_IS_LARGER(LLONG_MAX, 0x7fffffffLL) +typedef psnip_int32_t psnip_safe_llong_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && PSNIP_SAFE_IS_LARGER(LLONG_MAX, 0x7fffffffffffffffLL) +typedef psnip_int64_t psnip_safe_llong_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && defined(PSNIP_SAFE_HAVE_128) && (LLONG_MAX <= 0x7fffffffffffffffLL) +typedef psnip_safe_int128_t psnip_safe_llong_larger; +#else +#undef PSNIP_SAFE_HAVE_LARGER_LLONG +#endif + +#define PSNIP_SAFE_HAVE_LARGER_ULLONG +#if !defined(PSNIP_SAFE_NO_FIXED) && PSNIP_SAFE_IS_LARGER(ULLONG_MAX, 0xffff) +typedef psnip_uint16_t psnip_safe_ullong_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && PSNIP_SAFE_IS_LARGER(ULLONG_MAX, 0xffffffffUL) +typedef psnip_uint32_t psnip_safe_ullong_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && PSNIP_SAFE_IS_LARGER(ULLONG_MAX, 0xffffffffffffffffULL) +typedef psnip_uint64_t psnip_safe_ullong_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && defined(PSNIP_SAFE_HAVE_128) && (ULLONG_MAX <= 0xffffffffffffffffULL) +typedef psnip_safe_uint128_t psnip_safe_ullong_larger; +#else +#undef PSNIP_SAFE_HAVE_LARGER_ULLONG +#endif + +#if defined(PSNIP_SAFE_SIZE_MAX) +#define PSNIP_SAFE_HAVE_LARGER_SIZE +#if PSNIP_SAFE_IS_LARGER(PSNIP_SAFE_SIZE_MAX, USHRT_MAX) +typedef unsigned short psnip_safe_size_larger; +#elif PSNIP_SAFE_IS_LARGER(PSNIP_SAFE_SIZE_MAX, UINT_MAX) +typedef unsigned int psnip_safe_size_larger; +#elif PSNIP_SAFE_IS_LARGER(PSNIP_SAFE_SIZE_MAX, ULONG_MAX) +typedef unsigned long psnip_safe_size_larger; +#elif PSNIP_SAFE_IS_LARGER(PSNIP_SAFE_SIZE_MAX, ULLONG_MAX) +typedef unsigned long long psnip_safe_size_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && PSNIP_SAFE_IS_LARGER(PSNIP_SAFE_SIZE_MAX, 0xffff) +typedef psnip_uint16_t psnip_safe_size_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && PSNIP_SAFE_IS_LARGER(PSNIP_SAFE_SIZE_MAX, 0xffffffffUL) +typedef psnip_uint32_t psnip_safe_size_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && PSNIP_SAFE_IS_LARGER(PSNIP_SAFE_SIZE_MAX, 0xffffffffffffffffULL) +typedef psnip_uint64_t psnip_safe_size_larger; +#elif !defined(PSNIP_SAFE_NO_FIXED) && defined(PSNIP_SAFE_HAVE_128) && (PSNIP_SAFE_SIZE_MAX <= 0xffffffffffffffffULL) +typedef psnip_safe_uint128_t psnip_safe_size_larger; +#else +#undef PSNIP_SAFE_HAVE_LARGER_SIZE +#endif +#endif + +#if defined(PSNIP_SAFE_HAVE_LARGER_SCHAR) +PSNIP_SAFE_DEFINE_LARGER_SIGNED_OPS(signed char, schar) +#endif + +#if defined(PSNIP_SAFE_HAVE_LARGER_UCHAR) +PSNIP_SAFE_DEFINE_LARGER_UNSIGNED_OPS(unsigned char, uchar) +#endif + +#if defined(PSNIP_SAFE_HAVE_LARGER_CHAR) +#if CHAR_MIN == 0 +PSNIP_SAFE_DEFINE_LARGER_UNSIGNED_OPS(char, char) +#else +PSNIP_SAFE_DEFINE_LARGER_SIGNED_OPS(char, char) +#endif +#endif + +#if defined(PSNIP_SAFE_HAVE_LARGER_SHORT) +PSNIP_SAFE_DEFINE_LARGER_SIGNED_OPS(short, short) +#endif + +#if defined(PSNIP_SAFE_HAVE_LARGER_USHORT) +PSNIP_SAFE_DEFINE_LARGER_UNSIGNED_OPS(unsigned short, ushort) +#endif + +#if defined(PSNIP_SAFE_HAVE_LARGER_INT) +PSNIP_SAFE_DEFINE_LARGER_SIGNED_OPS(int, int) +#endif + +#if defined(PSNIP_SAFE_HAVE_LARGER_UINT) +PSNIP_SAFE_DEFINE_LARGER_UNSIGNED_OPS(unsigned int, uint) +#endif + +#if defined(PSNIP_SAFE_HAVE_LARGER_LONG) +PSNIP_SAFE_DEFINE_LARGER_SIGNED_OPS(long, long) +#endif + +#if defined(PSNIP_SAFE_HAVE_LARGER_ULONG) +PSNIP_SAFE_DEFINE_LARGER_UNSIGNED_OPS(unsigned long, ulong) +#endif + +#if defined(PSNIP_SAFE_HAVE_LARGER_LLONG) +PSNIP_SAFE_DEFINE_LARGER_SIGNED_OPS(long long, llong) +#endif + +#if defined(PSNIP_SAFE_HAVE_LARGER_ULLONG) +PSNIP_SAFE_DEFINE_LARGER_UNSIGNED_OPS(unsigned long long, ullong) +#endif + +#if defined(PSNIP_SAFE_HAVE_LARGER_SIZE) +PSNIP_SAFE_DEFINE_LARGER_UNSIGNED_OPS(size_t, size) +#endif + +#if !defined(PSNIP_SAFE_NO_FIXED) +PSNIP_SAFE_DEFINE_LARGER_SIGNED_OPS(psnip_int8_t, int8) +PSNIP_SAFE_DEFINE_LARGER_UNSIGNED_OPS(psnip_uint8_t, uint8) +PSNIP_SAFE_DEFINE_LARGER_SIGNED_OPS(psnip_int16_t, int16) +PSNIP_SAFE_DEFINE_LARGER_UNSIGNED_OPS(psnip_uint16_t, uint16) +PSNIP_SAFE_DEFINE_LARGER_SIGNED_OPS(psnip_int32_t, int32) +PSNIP_SAFE_DEFINE_LARGER_UNSIGNED_OPS(psnip_uint32_t, uint32) +#if defined(PSNIP_SAFE_HAVE_128) +PSNIP_SAFE_DEFINE_LARGER_SIGNED_OPS(psnip_int64_t, int64) +PSNIP_SAFE_DEFINE_LARGER_UNSIGNED_OPS(psnip_uint64_t, uint64) +#endif +#endif + +#endif /* !defined(PSNIP_SAFE_NO_PROMOTIONS) */ + +#define PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(T, name, op_name) \ + PSNIP_SAFE__FUNCTION psnip_safe_bool \ + psnip_safe_##name##_##op_name(T* res, T a, T b) { \ + return !__builtin_##op_name##_overflow(a, b, res); \ + } + +#define PSNIP_SAFE_DEFINE_PROMOTED_SIGNED_BINARY_OP(T, name, op_name, min, max) \ + PSNIP_SAFE__FUNCTION psnip_safe_bool \ + psnip_safe_##name##_##op_name(T* res, T a, T b) { \ + const psnip_safe_##name##_larger r = psnip_safe_larger_##name##_##op_name(a, b); \ + *res = (T) r; \ + return (r >= min) && (r <= max); \ + } + +#define PSNIP_SAFE_DEFINE_PROMOTED_UNSIGNED_BINARY_OP(T, name, op_name, max) \ + PSNIP_SAFE__FUNCTION psnip_safe_bool \ + psnip_safe_##name##_##op_name(T* res, T a, T b) { \ + const psnip_safe_##name##_larger r = psnip_safe_larger_##name##_##op_name(a, b); \ + *res = (T) r; \ + return (r <= max); \ + } + +#define PSNIP_SAFE_DEFINE_SIGNED_ADD(T, name, min, max) \ + PSNIP_SAFE__FUNCTION psnip_safe_bool \ + psnip_safe_##name##_add (T* res, T a, T b) { \ + psnip_safe_bool r = !( ((b > 0) && (a > (max - b))) || \ + ((b < 0) && (a < (min - b))) ); \ + if(PSNIP_SAFE_LIKELY(r)) \ + *res = a + b; \ + return r; \ + } + +#define PSNIP_SAFE_DEFINE_UNSIGNED_ADD(T, name, max) \ + PSNIP_SAFE__FUNCTION psnip_safe_bool \ + psnip_safe_##name##_add (T* res, T a, T b) { \ + *res = (T) (a + b); \ + return !PSNIP_SAFE_UNLIKELY((b > 0) && (a > (max - b))); \ + } + +#define PSNIP_SAFE_DEFINE_SIGNED_SUB(T, name, min, max) \ + PSNIP_SAFE__FUNCTION psnip_safe_bool \ + psnip_safe_##name##_sub (T* res, T a, T b) { \ + psnip_safe_bool r = !((b > 0 && a < (min + b)) || \ + (b < 0 && a > (max + b))); \ + if(PSNIP_SAFE_LIKELY(r)) \ + *res = a - b; \ + return r; \ + } + +#define PSNIP_SAFE_DEFINE_UNSIGNED_SUB(T, name, max) \ + PSNIP_SAFE__FUNCTION psnip_safe_bool \ + psnip_safe_##name##_sub (T* res, T a, T b) { \ + *res = a - b; \ + return !PSNIP_SAFE_UNLIKELY(b > a); \ + } + +#define PSNIP_SAFE_DEFINE_SIGNED_MUL(T, name, min, max) \ + PSNIP_SAFE__FUNCTION psnip_safe_bool \ + psnip_safe_##name##_mul (T* res, T a, T b) { \ + psnip_safe_bool r = 1; \ + if (a > 0) { \ + if (b > 0) { \ + if (a > (max / b)) { \ + r = 0; \ + } \ + } else { \ + if (b < (min / a)) { \ + r = 0; \ + } \ + } \ + } else { \ + if (b > 0) { \ + if (a < (min / b)) { \ + r = 0; \ + } \ + } else { \ + if ( (a != 0) && (b < (max / a))) { \ + r = 0; \ + } \ + } \ + } \ + if(PSNIP_SAFE_LIKELY(r)) \ + *res = a * b; \ + return r; \ + } + +#define PSNIP_SAFE_DEFINE_UNSIGNED_MUL(T, name, max) \ + PSNIP_SAFE__FUNCTION psnip_safe_bool \ + psnip_safe_##name##_mul (T* res, T a, T b) { \ + *res = (T) (a * b); \ + return !PSNIP_SAFE_UNLIKELY((a > 0) && (b > 0) && (a > (max / b))); \ + } + +#define PSNIP_SAFE_DEFINE_SIGNED_DIV(T, name, min, max) \ + PSNIP_SAFE__FUNCTION psnip_safe_bool \ + psnip_safe_##name##_div (T* res, T a, T b) { \ + if (PSNIP_SAFE_UNLIKELY(b == 0)) { \ + *res = 0; \ + return 0; \ + } else if (PSNIP_SAFE_UNLIKELY(a == min && b == -1)) { \ + *res = min; \ + return 0; \ + } else { \ + *res = (T) (a / b); \ + return 1; \ + } \ + } + +#define PSNIP_SAFE_DEFINE_UNSIGNED_DIV(T, name, max) \ + PSNIP_SAFE__FUNCTION psnip_safe_bool \ + psnip_safe_##name##_div (T* res, T a, T b) { \ + if (PSNIP_SAFE_UNLIKELY(b == 0)) { \ + *res = 0; \ + return 0; \ + } else { \ + *res = a / b; \ + return 1; \ + } \ + } + +#define PSNIP_SAFE_DEFINE_SIGNED_MOD(T, name, min, max) \ + PSNIP_SAFE__FUNCTION psnip_safe_bool \ + psnip_safe_##name##_mod (T* res, T a, T b) { \ + if (PSNIP_SAFE_UNLIKELY(b == 0)) { \ + *res = 0; \ + return 0; \ + } else if (PSNIP_SAFE_UNLIKELY(a == min && b == -1)) { \ + *res = min; \ + return 0; \ + } else { \ + *res = (T) (a % b); \ + return 1; \ + } \ + } + +#define PSNIP_SAFE_DEFINE_UNSIGNED_MOD(T, name, max) \ + PSNIP_SAFE__FUNCTION psnip_safe_bool \ + psnip_safe_##name##_mod (T* res, T a, T b) { \ + if (PSNIP_SAFE_UNLIKELY(b == 0)) { \ + *res = 0; \ + return 0; \ + } else { \ + *res = a % b; \ + return 1; \ + } \ + } + +#define PSNIP_SAFE_DEFINE_SIGNED_NEG(T, name, min, max) \ + PSNIP_SAFE__FUNCTION psnip_safe_bool \ + psnip_safe_##name##_neg (T* res, T value) { \ + psnip_safe_bool r = value != min; \ + *res = PSNIP_SAFE_LIKELY(r) ? -value : max; \ + return r; \ + } + +#define PSNIP_SAFE_DEFINE_INTSAFE(T, name, op, isf) \ + PSNIP_SAFE__FUNCTION psnip_safe_bool \ + psnip_safe_##name##_##op (T* res, T a, T b) { \ + return isf(a, b, res) == S_OK; \ + } + +#if CHAR_MIN == 0 +#if defined(PSNIP_SAFE_HAVE_BUILTIN_OVERFLOW) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(char, char, add) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(char, char, sub) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(char, char, mul) +#elif defined(PSNIP_SAFE_HAVE_LARGER_CHAR) +PSNIP_SAFE_DEFINE_PROMOTED_UNSIGNED_BINARY_OP(char, char, add, CHAR_MAX) +PSNIP_SAFE_DEFINE_PROMOTED_UNSIGNED_BINARY_OP(char, char, sub, CHAR_MAX) +PSNIP_SAFE_DEFINE_PROMOTED_UNSIGNED_BINARY_OP(char, char, mul, CHAR_MAX) +#else +PSNIP_SAFE_DEFINE_UNSIGNED_ADD(char, char, CHAR_MAX) +PSNIP_SAFE_DEFINE_UNSIGNED_SUB(char, char, CHAR_MAX) +PSNIP_SAFE_DEFINE_UNSIGNED_MUL(char, char, CHAR_MAX) +#endif +PSNIP_SAFE_DEFINE_UNSIGNED_DIV(char, char, CHAR_MAX) +PSNIP_SAFE_DEFINE_UNSIGNED_MOD(char, char, CHAR_MAX) +#else /* CHAR_MIN != 0 */ +#if defined(PSNIP_SAFE_HAVE_BUILTIN_OVERFLOW) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(char, char, add) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(char, char, sub) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(char, char, mul) +#elif defined(PSNIP_SAFE_HAVE_LARGER_CHAR) +PSNIP_SAFE_DEFINE_PROMOTED_SIGNED_BINARY_OP(char, char, add, CHAR_MIN, CHAR_MAX) +PSNIP_SAFE_DEFINE_PROMOTED_SIGNED_BINARY_OP(char, char, sub, CHAR_MIN, CHAR_MAX) +PSNIP_SAFE_DEFINE_PROMOTED_SIGNED_BINARY_OP(char, char, mul, CHAR_MIN, CHAR_MAX) +#else +PSNIP_SAFE_DEFINE_SIGNED_ADD(char, char, CHAR_MIN, CHAR_MAX) +PSNIP_SAFE_DEFINE_SIGNED_SUB(char, char, CHAR_MIN, CHAR_MAX) +PSNIP_SAFE_DEFINE_SIGNED_MUL(char, char, CHAR_MIN, CHAR_MAX) +#endif +PSNIP_SAFE_DEFINE_SIGNED_DIV(char, char, CHAR_MIN, CHAR_MAX) +PSNIP_SAFE_DEFINE_SIGNED_MOD(char, char, CHAR_MIN, CHAR_MAX) +PSNIP_SAFE_DEFINE_SIGNED_NEG(char, char, CHAR_MIN, CHAR_MAX) +#endif + +#if defined(PSNIP_SAFE_HAVE_BUILTIN_OVERFLOW) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(signed char, schar, add) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(signed char, schar, sub) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(signed char, schar, mul) +#elif defined(PSNIP_SAFE_HAVE_LARGER_SCHAR) +PSNIP_SAFE_DEFINE_PROMOTED_SIGNED_BINARY_OP(signed char, schar, add, SCHAR_MIN, SCHAR_MAX) +PSNIP_SAFE_DEFINE_PROMOTED_SIGNED_BINARY_OP(signed char, schar, sub, SCHAR_MIN, SCHAR_MAX) +PSNIP_SAFE_DEFINE_PROMOTED_SIGNED_BINARY_OP(signed char, schar, mul, SCHAR_MIN, SCHAR_MAX) +#else +PSNIP_SAFE_DEFINE_SIGNED_ADD(signed char, schar, SCHAR_MIN, SCHAR_MAX) +PSNIP_SAFE_DEFINE_SIGNED_SUB(signed char, schar, SCHAR_MIN, SCHAR_MAX) +PSNIP_SAFE_DEFINE_SIGNED_MUL(signed char, schar, SCHAR_MIN, SCHAR_MAX) +#endif +PSNIP_SAFE_DEFINE_SIGNED_DIV(signed char, schar, SCHAR_MIN, SCHAR_MAX) +PSNIP_SAFE_DEFINE_SIGNED_MOD(signed char, schar, SCHAR_MIN, SCHAR_MAX) +PSNIP_SAFE_DEFINE_SIGNED_NEG(signed char, schar, SCHAR_MIN, SCHAR_MAX) + +#if defined(PSNIP_SAFE_HAVE_BUILTIN_OVERFLOW) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(unsigned char, uchar, add) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(unsigned char, uchar, sub) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(unsigned char, uchar, mul) +#elif defined(PSNIP_SAFE_HAVE_LARGER_UCHAR) +PSNIP_SAFE_DEFINE_PROMOTED_UNSIGNED_BINARY_OP(unsigned char, uchar, add, UCHAR_MAX) +PSNIP_SAFE_DEFINE_PROMOTED_UNSIGNED_BINARY_OP(unsigned char, uchar, sub, UCHAR_MAX) +PSNIP_SAFE_DEFINE_PROMOTED_UNSIGNED_BINARY_OP(unsigned char, uchar, mul, UCHAR_MAX) +#else +PSNIP_SAFE_DEFINE_UNSIGNED_ADD(unsigned char, uchar, UCHAR_MAX) +PSNIP_SAFE_DEFINE_UNSIGNED_SUB(unsigned char, uchar, UCHAR_MAX) +PSNIP_SAFE_DEFINE_UNSIGNED_MUL(unsigned char, uchar, UCHAR_MAX) +#endif +PSNIP_SAFE_DEFINE_UNSIGNED_DIV(unsigned char, uchar, UCHAR_MAX) +PSNIP_SAFE_DEFINE_UNSIGNED_MOD(unsigned char, uchar, UCHAR_MAX) + +#if defined(PSNIP_SAFE_HAVE_BUILTIN_OVERFLOW) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(short, short, add) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(short, short, sub) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(short, short, mul) +#elif defined(PSNIP_SAFE_HAVE_LARGER_SHORT) +PSNIP_SAFE_DEFINE_PROMOTED_SIGNED_BINARY_OP(short, short, add, SHRT_MIN, SHRT_MAX) +PSNIP_SAFE_DEFINE_PROMOTED_SIGNED_BINARY_OP(short, short, sub, SHRT_MIN, SHRT_MAX) +PSNIP_SAFE_DEFINE_PROMOTED_SIGNED_BINARY_OP(short, short, mul, SHRT_MIN, SHRT_MAX) +#else +PSNIP_SAFE_DEFINE_SIGNED_ADD(short, short, SHRT_MIN, SHRT_MAX) +PSNIP_SAFE_DEFINE_SIGNED_SUB(short, short, SHRT_MIN, SHRT_MAX) +PSNIP_SAFE_DEFINE_SIGNED_MUL(short, short, SHRT_MIN, SHRT_MAX) +#endif +PSNIP_SAFE_DEFINE_SIGNED_DIV(short, short, SHRT_MIN, SHRT_MAX) +PSNIP_SAFE_DEFINE_SIGNED_MOD(short, short, SHRT_MIN, SHRT_MAX) +PSNIP_SAFE_DEFINE_SIGNED_NEG(short, short, SHRT_MIN, SHRT_MAX) + +#if defined(PSNIP_SAFE_HAVE_BUILTIN_OVERFLOW) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(unsigned short, ushort, add) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(unsigned short, ushort, sub) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(unsigned short, ushort, mul) +#elif defined(PSNIP_SAFE_HAVE_INTSAFE_H) +PSNIP_SAFE_DEFINE_INTSAFE(unsigned short, ushort, add, UShortAdd) +PSNIP_SAFE_DEFINE_INTSAFE(unsigned short, ushort, sub, UShortSub) +PSNIP_SAFE_DEFINE_INTSAFE(unsigned short, ushort, mul, UShortMult) +#elif defined(PSNIP_SAFE_HAVE_LARGER_USHORT) +PSNIP_SAFE_DEFINE_PROMOTED_UNSIGNED_BINARY_OP(unsigned short, ushort, add, USHRT_MAX) +PSNIP_SAFE_DEFINE_PROMOTED_UNSIGNED_BINARY_OP(unsigned short, ushort, sub, USHRT_MAX) +PSNIP_SAFE_DEFINE_PROMOTED_UNSIGNED_BINARY_OP(unsigned short, ushort, mul, USHRT_MAX) +#else +PSNIP_SAFE_DEFINE_UNSIGNED_ADD(unsigned short, ushort, USHRT_MAX) +PSNIP_SAFE_DEFINE_UNSIGNED_SUB(unsigned short, ushort, USHRT_MAX) +PSNIP_SAFE_DEFINE_UNSIGNED_MUL(unsigned short, ushort, USHRT_MAX) +#endif +PSNIP_SAFE_DEFINE_UNSIGNED_DIV(unsigned short, ushort, USHRT_MAX) +PSNIP_SAFE_DEFINE_UNSIGNED_MOD(unsigned short, ushort, USHRT_MAX) + +#if defined(PSNIP_SAFE_HAVE_BUILTIN_OVERFLOW) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(int, int, add) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(int, int, sub) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(int, int, mul) +#elif defined(PSNIP_SAFE_HAVE_LARGER_INT) +PSNIP_SAFE_DEFINE_PROMOTED_SIGNED_BINARY_OP(int, int, add, INT_MIN, INT_MAX) +PSNIP_SAFE_DEFINE_PROMOTED_SIGNED_BINARY_OP(int, int, sub, INT_MIN, INT_MAX) +PSNIP_SAFE_DEFINE_PROMOTED_SIGNED_BINARY_OP(int, int, mul, INT_MIN, INT_MAX) +#else +PSNIP_SAFE_DEFINE_SIGNED_ADD(int, int, INT_MIN, INT_MAX) +PSNIP_SAFE_DEFINE_SIGNED_SUB(int, int, INT_MIN, INT_MAX) +PSNIP_SAFE_DEFINE_SIGNED_MUL(int, int, INT_MIN, INT_MAX) +#endif +PSNIP_SAFE_DEFINE_SIGNED_DIV(int, int, INT_MIN, INT_MAX) +PSNIP_SAFE_DEFINE_SIGNED_MOD(int, int, INT_MIN, INT_MAX) +PSNIP_SAFE_DEFINE_SIGNED_NEG(int, int, INT_MIN, INT_MAX) + +#if defined(PSNIP_SAFE_HAVE_BUILTIN_OVERFLOW) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(unsigned int, uint, add) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(unsigned int, uint, sub) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(unsigned int, uint, mul) +#elif defined(PSNIP_SAFE_HAVE_INTSAFE_H) +PSNIP_SAFE_DEFINE_INTSAFE(unsigned int, uint, add, UIntAdd) +PSNIP_SAFE_DEFINE_INTSAFE(unsigned int, uint, sub, UIntSub) +PSNIP_SAFE_DEFINE_INTSAFE(unsigned int, uint, mul, UIntMult) +#elif defined(PSNIP_SAFE_HAVE_LARGER_UINT) +PSNIP_SAFE_DEFINE_PROMOTED_UNSIGNED_BINARY_OP(unsigned int, uint, add, UINT_MAX) +PSNIP_SAFE_DEFINE_PROMOTED_UNSIGNED_BINARY_OP(unsigned int, uint, sub, UINT_MAX) +PSNIP_SAFE_DEFINE_PROMOTED_UNSIGNED_BINARY_OP(unsigned int, uint, mul, UINT_MAX) +#else +PSNIP_SAFE_DEFINE_UNSIGNED_ADD(unsigned int, uint, UINT_MAX) +PSNIP_SAFE_DEFINE_UNSIGNED_SUB(unsigned int, uint, UINT_MAX) +PSNIP_SAFE_DEFINE_UNSIGNED_MUL(unsigned int, uint, UINT_MAX) +#endif +PSNIP_SAFE_DEFINE_UNSIGNED_DIV(unsigned int, uint, UINT_MAX) +PSNIP_SAFE_DEFINE_UNSIGNED_MOD(unsigned int, uint, UINT_MAX) + +#if defined(PSNIP_SAFE_HAVE_BUILTIN_OVERFLOW) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(long, long, add) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(long, long, sub) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(long, long, mul) +#elif defined(PSNIP_SAFE_HAVE_LARGER_LONG) +PSNIP_SAFE_DEFINE_PROMOTED_SIGNED_BINARY_OP(long, long, add, LONG_MIN, LONG_MAX) +PSNIP_SAFE_DEFINE_PROMOTED_SIGNED_BINARY_OP(long, long, sub, LONG_MIN, LONG_MAX) +PSNIP_SAFE_DEFINE_PROMOTED_SIGNED_BINARY_OP(long, long, mul, LONG_MIN, LONG_MAX) +#else +PSNIP_SAFE_DEFINE_SIGNED_ADD(long, long, LONG_MIN, LONG_MAX) +PSNIP_SAFE_DEFINE_SIGNED_SUB(long, long, LONG_MIN, LONG_MAX) +PSNIP_SAFE_DEFINE_SIGNED_MUL(long, long, LONG_MIN, LONG_MAX) +#endif +PSNIP_SAFE_DEFINE_SIGNED_DIV(long, long, LONG_MIN, LONG_MAX) +PSNIP_SAFE_DEFINE_SIGNED_MOD(long, long, LONG_MIN, LONG_MAX) +PSNIP_SAFE_DEFINE_SIGNED_NEG(long, long, LONG_MIN, LONG_MAX) + +#if defined(PSNIP_SAFE_HAVE_BUILTIN_OVERFLOW) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(unsigned long, ulong, add) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(unsigned long, ulong, sub) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(unsigned long, ulong, mul) +#elif defined(PSNIP_SAFE_HAVE_INTSAFE_H) +PSNIP_SAFE_DEFINE_INTSAFE(unsigned long, ulong, add, ULongAdd) +PSNIP_SAFE_DEFINE_INTSAFE(unsigned long, ulong, sub, ULongSub) +PSNIP_SAFE_DEFINE_INTSAFE(unsigned long, ulong, mul, ULongMult) +#elif defined(PSNIP_SAFE_HAVE_LARGER_ULONG) +PSNIP_SAFE_DEFINE_PROMOTED_UNSIGNED_BINARY_OP(unsigned long, ulong, add, ULONG_MAX) +PSNIP_SAFE_DEFINE_PROMOTED_UNSIGNED_BINARY_OP(unsigned long, ulong, sub, ULONG_MAX) +PSNIP_SAFE_DEFINE_PROMOTED_UNSIGNED_BINARY_OP(unsigned long, ulong, mul, ULONG_MAX) +#else +PSNIP_SAFE_DEFINE_UNSIGNED_ADD(unsigned long, ulong, ULONG_MAX) +PSNIP_SAFE_DEFINE_UNSIGNED_SUB(unsigned long, ulong, ULONG_MAX) +PSNIP_SAFE_DEFINE_UNSIGNED_MUL(unsigned long, ulong, ULONG_MAX) +#endif +PSNIP_SAFE_DEFINE_UNSIGNED_DIV(unsigned long, ulong, ULONG_MAX) +PSNIP_SAFE_DEFINE_UNSIGNED_MOD(unsigned long, ulong, ULONG_MAX) + +#if defined(PSNIP_SAFE_HAVE_BUILTIN_OVERFLOW) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(long long, llong, add) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(long long, llong, sub) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(long long, llong, mul) +#elif defined(PSNIP_SAFE_HAVE_LARGER_LLONG) +PSNIP_SAFE_DEFINE_PROMOTED_SIGNED_BINARY_OP(long long, llong, add, LLONG_MIN, LLONG_MAX) +PSNIP_SAFE_DEFINE_PROMOTED_SIGNED_BINARY_OP(long long, llong, sub, LLONG_MIN, LLONG_MAX) +PSNIP_SAFE_DEFINE_PROMOTED_SIGNED_BINARY_OP(long long, llong, mul, LLONG_MIN, LLONG_MAX) +#else +PSNIP_SAFE_DEFINE_SIGNED_ADD(long long, llong, LLONG_MIN, LLONG_MAX) +PSNIP_SAFE_DEFINE_SIGNED_SUB(long long, llong, LLONG_MIN, LLONG_MAX) +PSNIP_SAFE_DEFINE_SIGNED_MUL(long long, llong, LLONG_MIN, LLONG_MAX) +#endif +PSNIP_SAFE_DEFINE_SIGNED_DIV(long long, llong, LLONG_MIN, LLONG_MAX) +PSNIP_SAFE_DEFINE_SIGNED_MOD(long long, llong, LLONG_MIN, LLONG_MAX) +PSNIP_SAFE_DEFINE_SIGNED_NEG(long long, llong, LLONG_MIN, LLONG_MAX) + +#if defined(PSNIP_SAFE_HAVE_BUILTIN_OVERFLOW) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(unsigned long long, ullong, add) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(unsigned long long, ullong, sub) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(unsigned long long, ullong, mul) +#elif defined(PSNIP_SAFE_HAVE_INTSAFE_H) +PSNIP_SAFE_DEFINE_INTSAFE(unsigned long long, ullong, add, ULongLongAdd) +PSNIP_SAFE_DEFINE_INTSAFE(unsigned long long, ullong, sub, ULongLongSub) +PSNIP_SAFE_DEFINE_INTSAFE(unsigned long long, ullong, mul, ULongLongMult) +#elif defined(PSNIP_SAFE_HAVE_LARGER_ULLONG) +PSNIP_SAFE_DEFINE_PROMOTED_UNSIGNED_BINARY_OP(unsigned long long, ullong, add, ULLONG_MAX) +PSNIP_SAFE_DEFINE_PROMOTED_UNSIGNED_BINARY_OP(unsigned long long, ullong, sub, ULLONG_MAX) +PSNIP_SAFE_DEFINE_PROMOTED_UNSIGNED_BINARY_OP(unsigned long long, ullong, mul, ULLONG_MAX) +#else +PSNIP_SAFE_DEFINE_UNSIGNED_ADD(unsigned long long, ullong, ULLONG_MAX) +PSNIP_SAFE_DEFINE_UNSIGNED_SUB(unsigned long long, ullong, ULLONG_MAX) +PSNIP_SAFE_DEFINE_UNSIGNED_MUL(unsigned long long, ullong, ULLONG_MAX) +#endif +PSNIP_SAFE_DEFINE_UNSIGNED_DIV(unsigned long long, ullong, ULLONG_MAX) +PSNIP_SAFE_DEFINE_UNSIGNED_MOD(unsigned long long, ullong, ULLONG_MAX) + +#if defined(PSNIP_SAFE_HAVE_BUILTIN_OVERFLOW) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(size_t, size, add) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(size_t, size, sub) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(size_t, size, mul) +#elif defined(PSNIP_SAFE_HAVE_INTSAFE_H) +PSNIP_SAFE_DEFINE_INTSAFE(size_t, size, add, SizeTAdd) +PSNIP_SAFE_DEFINE_INTSAFE(size_t, size, sub, SizeTSub) +PSNIP_SAFE_DEFINE_INTSAFE(size_t, size, mul, SizeTMult) +#elif defined(PSNIP_SAFE_HAVE_LARGER_SIZE) +PSNIP_SAFE_DEFINE_PROMOTED_UNSIGNED_BINARY_OP(size_t, size, add, PSNIP_SAFE__SIZE_MAX_RT) +PSNIP_SAFE_DEFINE_PROMOTED_UNSIGNED_BINARY_OP(size_t, size, sub, PSNIP_SAFE__SIZE_MAX_RT) +PSNIP_SAFE_DEFINE_PROMOTED_UNSIGNED_BINARY_OP(size_t, size, mul, PSNIP_SAFE__SIZE_MAX_RT) +#else +PSNIP_SAFE_DEFINE_UNSIGNED_ADD(size_t, size, PSNIP_SAFE__SIZE_MAX_RT) +PSNIP_SAFE_DEFINE_UNSIGNED_SUB(size_t, size, PSNIP_SAFE__SIZE_MAX_RT) +PSNIP_SAFE_DEFINE_UNSIGNED_MUL(size_t, size, PSNIP_SAFE__SIZE_MAX_RT) +#endif +PSNIP_SAFE_DEFINE_UNSIGNED_DIV(size_t, size, PSNIP_SAFE__SIZE_MAX_RT) +PSNIP_SAFE_DEFINE_UNSIGNED_MOD(size_t, size, PSNIP_SAFE__SIZE_MAX_RT) + +#if !defined(PSNIP_SAFE_NO_FIXED) + +#if defined(PSNIP_SAFE_HAVE_BUILTIN_OVERFLOW) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(psnip_int8_t, int8, add) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(psnip_int8_t, int8, sub) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(psnip_int8_t, int8, mul) +#elif defined(PSNIP_SAFE_HAVE_LARGER_INT8) +PSNIP_SAFE_DEFINE_PROMOTED_SIGNED_BINARY_OP(psnip_int8_t, int8, add, (-0x7fLL-1), 0x7f) +PSNIP_SAFE_DEFINE_PROMOTED_SIGNED_BINARY_OP(psnip_int8_t, int8, sub, (-0x7fLL-1), 0x7f) +PSNIP_SAFE_DEFINE_PROMOTED_SIGNED_BINARY_OP(psnip_int8_t, int8, mul, (-0x7fLL-1), 0x7f) +#else +PSNIP_SAFE_DEFINE_SIGNED_ADD(psnip_int8_t, int8, (-0x7fLL-1), 0x7f) +PSNIP_SAFE_DEFINE_SIGNED_SUB(psnip_int8_t, int8, (-0x7fLL-1), 0x7f) +PSNIP_SAFE_DEFINE_SIGNED_MUL(psnip_int8_t, int8, (-0x7fLL-1), 0x7f) +#endif +PSNIP_SAFE_DEFINE_SIGNED_DIV(psnip_int8_t, int8, (-0x7fLL-1), 0x7f) +PSNIP_SAFE_DEFINE_SIGNED_MOD(psnip_int8_t, int8, (-0x7fLL-1), 0x7f) +PSNIP_SAFE_DEFINE_SIGNED_NEG(psnip_int8_t, int8, (-0x7fLL-1), 0x7f) + +#if defined(PSNIP_SAFE_HAVE_BUILTIN_OVERFLOW) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(psnip_uint8_t, uint8, add) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(psnip_uint8_t, uint8, sub) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(psnip_uint8_t, uint8, mul) +#elif defined(PSNIP_SAFE_HAVE_LARGER_UINT8) +PSNIP_SAFE_DEFINE_PROMOTED_UNSIGNED_BINARY_OP(psnip_uint8_t, uint8, add, 0xff) +PSNIP_SAFE_DEFINE_PROMOTED_UNSIGNED_BINARY_OP(psnip_uint8_t, uint8, sub, 0xff) +PSNIP_SAFE_DEFINE_PROMOTED_UNSIGNED_BINARY_OP(psnip_uint8_t, uint8, mul, 0xff) +#else +PSNIP_SAFE_DEFINE_UNSIGNED_ADD(psnip_uint8_t, uint8, 0xff) +PSNIP_SAFE_DEFINE_UNSIGNED_SUB(psnip_uint8_t, uint8, 0xff) +PSNIP_SAFE_DEFINE_UNSIGNED_MUL(psnip_uint8_t, uint8, 0xff) +#endif +PSNIP_SAFE_DEFINE_UNSIGNED_DIV(psnip_uint8_t, uint8, 0xff) +PSNIP_SAFE_DEFINE_UNSIGNED_MOD(psnip_uint8_t, uint8, 0xff) + +#if defined(PSNIP_SAFE_HAVE_BUILTIN_OVERFLOW) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(psnip_int16_t, int16, add) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(psnip_int16_t, int16, sub) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(psnip_int16_t, int16, mul) +#elif defined(PSNIP_SAFE_HAVE_LARGER_INT16) +PSNIP_SAFE_DEFINE_PROMOTED_SIGNED_BINARY_OP(psnip_int16_t, int16, add, (-32767-1), 0x7fff) +PSNIP_SAFE_DEFINE_PROMOTED_SIGNED_BINARY_OP(psnip_int16_t, int16, sub, (-32767-1), 0x7fff) +PSNIP_SAFE_DEFINE_PROMOTED_SIGNED_BINARY_OP(psnip_int16_t, int16, mul, (-32767-1), 0x7fff) +#else +PSNIP_SAFE_DEFINE_SIGNED_ADD(psnip_int16_t, int16, (-32767-1), 0x7fff) +PSNIP_SAFE_DEFINE_SIGNED_SUB(psnip_int16_t, int16, (-32767-1), 0x7fff) +PSNIP_SAFE_DEFINE_SIGNED_MUL(psnip_int16_t, int16, (-32767-1), 0x7fff) +#endif +PSNIP_SAFE_DEFINE_SIGNED_DIV(psnip_int16_t, int16, (-32767-1), 0x7fff) +PSNIP_SAFE_DEFINE_SIGNED_MOD(psnip_int16_t, int16, (-32767-1), 0x7fff) +PSNIP_SAFE_DEFINE_SIGNED_NEG(psnip_int16_t, int16, (-32767-1), 0x7fff) + +#if defined(PSNIP_SAFE_HAVE_BUILTIN_OVERFLOW) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(psnip_uint16_t, uint16, add) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(psnip_uint16_t, uint16, sub) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(psnip_uint16_t, uint16, mul) +#elif defined(PSNIP_SAFE_HAVE_INTSAFE_H) && defined(_WIN32) +PSNIP_SAFE_DEFINE_INTSAFE(psnip_uint16_t, uint16, add, UShortAdd) +PSNIP_SAFE_DEFINE_INTSAFE(psnip_uint16_t, uint16, sub, UShortSub) +PSNIP_SAFE_DEFINE_INTSAFE(psnip_uint16_t, uint16, mul, UShortMult) +#elif defined(PSNIP_SAFE_HAVE_LARGER_UINT16) +PSNIP_SAFE_DEFINE_PROMOTED_UNSIGNED_BINARY_OP(psnip_uint16_t, uint16, add, 0xffff) +PSNIP_SAFE_DEFINE_PROMOTED_UNSIGNED_BINARY_OP(psnip_uint16_t, uint16, sub, 0xffff) +PSNIP_SAFE_DEFINE_PROMOTED_UNSIGNED_BINARY_OP(psnip_uint16_t, uint16, mul, 0xffff) +#else +PSNIP_SAFE_DEFINE_UNSIGNED_ADD(psnip_uint16_t, uint16, 0xffff) +PSNIP_SAFE_DEFINE_UNSIGNED_SUB(psnip_uint16_t, uint16, 0xffff) +PSNIP_SAFE_DEFINE_UNSIGNED_MUL(psnip_uint16_t, uint16, 0xffff) +#endif +PSNIP_SAFE_DEFINE_UNSIGNED_DIV(psnip_uint16_t, uint16, 0xffff) +PSNIP_SAFE_DEFINE_UNSIGNED_MOD(psnip_uint16_t, uint16, 0xffff) + +#if defined(PSNIP_SAFE_HAVE_BUILTIN_OVERFLOW) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(psnip_int32_t, int32, add) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(psnip_int32_t, int32, sub) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(psnip_int32_t, int32, mul) +#elif defined(PSNIP_SAFE_HAVE_LARGER_INT32) +PSNIP_SAFE_DEFINE_PROMOTED_SIGNED_BINARY_OP(psnip_int32_t, int32, add, (-0x7fffffffLL-1), 0x7fffffffLL) +PSNIP_SAFE_DEFINE_PROMOTED_SIGNED_BINARY_OP(psnip_int32_t, int32, sub, (-0x7fffffffLL-1), 0x7fffffffLL) +PSNIP_SAFE_DEFINE_PROMOTED_SIGNED_BINARY_OP(psnip_int32_t, int32, mul, (-0x7fffffffLL-1), 0x7fffffffLL) +#else +PSNIP_SAFE_DEFINE_SIGNED_ADD(psnip_int32_t, int32, (-0x7fffffffLL-1), 0x7fffffffLL) +PSNIP_SAFE_DEFINE_SIGNED_SUB(psnip_int32_t, int32, (-0x7fffffffLL-1), 0x7fffffffLL) +PSNIP_SAFE_DEFINE_SIGNED_MUL(psnip_int32_t, int32, (-0x7fffffffLL-1), 0x7fffffffLL) +#endif +PSNIP_SAFE_DEFINE_SIGNED_DIV(psnip_int32_t, int32, (-0x7fffffffLL-1), 0x7fffffffLL) +PSNIP_SAFE_DEFINE_SIGNED_MOD(psnip_int32_t, int32, (-0x7fffffffLL-1), 0x7fffffffLL) +PSNIP_SAFE_DEFINE_SIGNED_NEG(psnip_int32_t, int32, (-0x7fffffffLL-1), 0x7fffffffLL) + +#if defined(PSNIP_SAFE_HAVE_BUILTIN_OVERFLOW) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(psnip_uint32_t, uint32, add) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(psnip_uint32_t, uint32, sub) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(psnip_uint32_t, uint32, mul) +#elif defined(PSNIP_SAFE_HAVE_INTSAFE_H) && defined(_WIN32) +PSNIP_SAFE_DEFINE_INTSAFE(psnip_uint32_t, uint32, add, UIntAdd) +PSNIP_SAFE_DEFINE_INTSAFE(psnip_uint32_t, uint32, sub, UIntSub) +PSNIP_SAFE_DEFINE_INTSAFE(psnip_uint32_t, uint32, mul, UIntMult) +#elif defined(PSNIP_SAFE_HAVE_LARGER_UINT32) +PSNIP_SAFE_DEFINE_PROMOTED_UNSIGNED_BINARY_OP(psnip_uint32_t, uint32, add, 0xffffffffUL) +PSNIP_SAFE_DEFINE_PROMOTED_UNSIGNED_BINARY_OP(psnip_uint32_t, uint32, sub, 0xffffffffUL) +PSNIP_SAFE_DEFINE_PROMOTED_UNSIGNED_BINARY_OP(psnip_uint32_t, uint32, mul, 0xffffffffUL) +#else +PSNIP_SAFE_DEFINE_UNSIGNED_ADD(psnip_uint32_t, uint32, 0xffffffffUL) +PSNIP_SAFE_DEFINE_UNSIGNED_SUB(psnip_uint32_t, uint32, 0xffffffffUL) +PSNIP_SAFE_DEFINE_UNSIGNED_MUL(psnip_uint32_t, uint32, 0xffffffffUL) +#endif +PSNIP_SAFE_DEFINE_UNSIGNED_DIV(psnip_uint32_t, uint32, 0xffffffffUL) +PSNIP_SAFE_DEFINE_UNSIGNED_MOD(psnip_uint32_t, uint32, 0xffffffffUL) + +#if defined(PSNIP_SAFE_HAVE_BUILTIN_OVERFLOW) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(psnip_int64_t, int64, add) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(psnip_int64_t, int64, sub) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(psnip_int64_t, int64, mul) +#elif defined(PSNIP_SAFE_HAVE_LARGER_INT64) +PSNIP_SAFE_DEFINE_PROMOTED_SIGNED_BINARY_OP(psnip_int64_t, int64, add, (-0x7fffffffffffffffLL-1), 0x7fffffffffffffffLL) +PSNIP_SAFE_DEFINE_PROMOTED_SIGNED_BINARY_OP(psnip_int64_t, int64, sub, (-0x7fffffffffffffffLL-1), 0x7fffffffffffffffLL) +PSNIP_SAFE_DEFINE_PROMOTED_SIGNED_BINARY_OP(psnip_int64_t, int64, mul, (-0x7fffffffffffffffLL-1), 0x7fffffffffffffffLL) +#else +PSNIP_SAFE_DEFINE_SIGNED_ADD(psnip_int64_t, int64, (-0x7fffffffffffffffLL-1), 0x7fffffffffffffffLL) +PSNIP_SAFE_DEFINE_SIGNED_SUB(psnip_int64_t, int64, (-0x7fffffffffffffffLL-1), 0x7fffffffffffffffLL) +PSNIP_SAFE_DEFINE_SIGNED_MUL(psnip_int64_t, int64, (-0x7fffffffffffffffLL-1), 0x7fffffffffffffffLL) +#endif +PSNIP_SAFE_DEFINE_SIGNED_DIV(psnip_int64_t, int64, (-0x7fffffffffffffffLL-1), 0x7fffffffffffffffLL) +PSNIP_SAFE_DEFINE_SIGNED_MOD(psnip_int64_t, int64, (-0x7fffffffffffffffLL-1), 0x7fffffffffffffffLL) +PSNIP_SAFE_DEFINE_SIGNED_NEG(psnip_int64_t, int64, (-0x7fffffffffffffffLL-1), 0x7fffffffffffffffLL) + +#if defined(PSNIP_SAFE_HAVE_BUILTIN_OVERFLOW) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(psnip_uint64_t, uint64, add) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(psnip_uint64_t, uint64, sub) +PSNIP_SAFE_DEFINE_BUILTIN_BINARY_OP(psnip_uint64_t, uint64, mul) +#elif defined(PSNIP_SAFE_HAVE_INTSAFE_H) && defined(_WIN32) +PSNIP_SAFE_DEFINE_INTSAFE(psnip_uint64_t, uint64, add, ULongLongAdd) +PSNIP_SAFE_DEFINE_INTSAFE(psnip_uint64_t, uint64, sub, ULongLongSub) +PSNIP_SAFE_DEFINE_INTSAFE(psnip_uint64_t, uint64, mul, ULongLongMult) +#elif defined(PSNIP_SAFE_HAVE_LARGER_UINT64) +PSNIP_SAFE_DEFINE_PROMOTED_UNSIGNED_BINARY_OP(psnip_uint64_t, uint64, add, 0xffffffffffffffffULL) +PSNIP_SAFE_DEFINE_PROMOTED_UNSIGNED_BINARY_OP(psnip_uint64_t, uint64, sub, 0xffffffffffffffffULL) +PSNIP_SAFE_DEFINE_PROMOTED_UNSIGNED_BINARY_OP(psnip_uint64_t, uint64, mul, 0xffffffffffffffffULL) +#else +PSNIP_SAFE_DEFINE_UNSIGNED_ADD(psnip_uint64_t, uint64, 0xffffffffffffffffULL) +PSNIP_SAFE_DEFINE_UNSIGNED_SUB(psnip_uint64_t, uint64, 0xffffffffffffffffULL) +PSNIP_SAFE_DEFINE_UNSIGNED_MUL(psnip_uint64_t, uint64, 0xffffffffffffffffULL) +#endif +PSNIP_SAFE_DEFINE_UNSIGNED_DIV(psnip_uint64_t, uint64, 0xffffffffffffffffULL) +PSNIP_SAFE_DEFINE_UNSIGNED_MOD(psnip_uint64_t, uint64, 0xffffffffffffffffULL) + +#endif /* !defined(PSNIP_SAFE_NO_FIXED) */ + +#define PSNIP_SAFE_C11_GENERIC_SELECTION(res, op) \ + _Generic((*res), \ + char: psnip_safe_char_##op, \ + unsigned char: psnip_safe_uchar_##op, \ + short: psnip_safe_short_##op, \ + unsigned short: psnip_safe_ushort_##op, \ + int: psnip_safe_int_##op, \ + unsigned int: psnip_safe_uint_##op, \ + long: psnip_safe_long_##op, \ + unsigned long: psnip_safe_ulong_##op, \ + long long: psnip_safe_llong_##op, \ + unsigned long long: psnip_safe_ullong_##op) + +#define PSNIP_SAFE_C11_GENERIC_BINARY_OP(op, res, a, b) \ + PSNIP_SAFE_C11_GENERIC_SELECTION(res, op)(res, a, b) +#define PSNIP_SAFE_C11_GENERIC_UNARY_OP(op, res, v) \ + PSNIP_SAFE_C11_GENERIC_SELECTION(res, op)(res, v) + +#if defined(PSNIP_SAFE_HAVE_BUILTIN_OVERFLOW) +#define psnip_safe_add(res, a, b) !__builtin_add_overflow(a, b, res) +#define psnip_safe_sub(res, a, b) !__builtin_sub_overflow(a, b, res) +#define psnip_safe_mul(res, a, b) !__builtin_mul_overflow(a, b, res) +#define psnip_safe_div(res, a, b) PSNIP_SAFE_C11_GENERIC_BINARY_OP(div, res, a, b) +#define psnip_safe_mod(res, a, b) PSNIP_SAFE_C11_GENERIC_BINARY_OP(mod, res, a, b) +#define psnip_safe_neg(res, v) PSNIP_SAFE_C11_GENERIC_UNARY_OP (neg, res, v) + +#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +/* The are no fixed-length or size selections because they cause an + * error about _Generic specifying two compatible types. Hopefully + * this doesn't cause problems on exotic platforms, but if it does + * please let me know and I'll try to figure something out. */ + +#define psnip_safe_add(res, a, b) PSNIP_SAFE_C11_GENERIC_BINARY_OP(add, res, a, b) +#define psnip_safe_sub(res, a, b) PSNIP_SAFE_C11_GENERIC_BINARY_OP(sub, res, a, b) +#define psnip_safe_mul(res, a, b) PSNIP_SAFE_C11_GENERIC_BINARY_OP(mul, res, a, b) +#define psnip_safe_div(res, a, b) PSNIP_SAFE_C11_GENERIC_BINARY_OP(div, res, a, b) +#define psnip_safe_mod(res, a, b) PSNIP_SAFE_C11_GENERIC_BINARY_OP(mod, res, a, b) +#define psnip_safe_neg(res, v) PSNIP_SAFE_C11_GENERIC_UNARY_OP (neg, res, v) +#endif + +#include <setjmp.h> + +#define ws_safe_op_jmp(op, res, a, b, env) \ + do { \ + if(!psnip_safe_##op(res, a, b)) { \ + longjmp(env, 1); \ + } \ + } while (0) + +#define ws_safe_add_jmp(res, a, b, env) ws_safe_op_jmp(add, res, a, b, env) +#define ws_safe_sub_jmp(res, a, b, env) ws_safe_op_jmp(sub, res, a, b, env) +#define ws_safe_mul_jmp(res, a, b, env) ws_safe_op_jmp(mul, res, a, b, env) +#define ws_safe_div_jmp(res, a, b, env) ws_safe_op_jmp(div, res, a, b, env) +#define ws_safe_mod_jmp(res, a, b, env) ws_safe_op_jmp(mod, res, a, b, env) +#define ws_safe_neg_jmp(res, a, b, env) ws_safe_op_jmp(neg, res, a, b, env) + +#endif /* !defined(PSNIP_SAFE_H) */ diff --git a/wsutil/sign_ext.h b/wsutil/sign_ext.h new file mode 100644 index 00000000..67401edb --- /dev/null +++ b/wsutil/sign_ext.h @@ -0,0 +1,71 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WSUTIL_SIGN_EXT_H__ +#define __WSUTIL_SIGN_EXT_H__ + +#include <inttypes.h> + +#include <glib.h> + +#include <wsutil/ws_assert.h> + +/* sign extension routines */ + +static inline uint32_t +ws_sign_ext32(uint32_t val, int no_of_bits) +{ + ws_assert (no_of_bits >= 0 && no_of_bits <= 32); + + if ((no_of_bits == 0) || (no_of_bits == 32)) + return val; + + /* + * Don't shift signed values left; that's not valid in C99, at + * least, if the value is negative or if the shift count is + * the number of bits in the value - 1, and we might get + * compile-time or run-time complaints about that. + */ + if (val & (1U << (no_of_bits-1))) + val |= (0xFFFFFFFFU << no_of_bits); + + return val; +} + +static inline uint64_t +ws_sign_ext64(uint64_t val, int no_of_bits) +{ + ws_assert (no_of_bits >= 0 && no_of_bits <= 64); + + if ((no_of_bits == 0) || (no_of_bits == 64)) + return val; + + /* + * Don't shift signed values left; that's not valid in C99, at + * least, if the value is negative or if the shift count is + * the number of bits in the value - 1, and we might get + * compile-time or run-time complaints about that. + */ + if (val & (G_GUINT64_CONSTANT(1) << (no_of_bits-1))) + val |= (G_GUINT64_CONSTANT(0xFFFFFFFFFFFFFFFF) << no_of_bits); + + return val; +} + +/* +static inline uint64_t +ws_sign_ext64(uint64_t val, int no_of_bits) +{ + int64_t sval = (val << (64 - no_of_bits)); + + return (uint64_t) (sval >> (64 - no_of_bits)); +} +*/ + +#endif /* __WSUTIL_SIGN_EXT_H__ */ diff --git a/wsutil/sober128.c b/wsutil/sober128.c new file mode 100644 index 00000000..c84c114e --- /dev/null +++ b/wsutil/sober128.c @@ -0,0 +1,488 @@ +/* This file is derived from sober128 implementation in corosync + cluster engine. corosync cluster engine borrows the implementation + from LibTomCrypt. + + The latest version of the original code can be found at + http://www.libtom.net/LibTomCrypt/ according to which this code is in the + Public Domain + */ + +/* About LibTomCrypt: + * --------------------------------------------------------------------- + * LibTomCrypt, modular cryptographic library -- Tom St Denis + * + * LibTomCrypt is a library that provides various cryptographic + * algorithms in a highly modular and flexible manner. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tomstdenis@iahu.ca, http://www.libtom.net/LibTomCrypt/ + */ + +#include "sober128.h" + +typedef unsigned long ulong32; +typedef unsigned long long ulong64; + +/* ---- HELPER MACROS ---- */ +#define STORE32L(x, y) \ + { (y)[3] = (unsigned char)(((x)>>24)&255); (y)[2] = (unsigned char)(((x)>>16)&255); \ + (y)[1] = (unsigned char)(((x)>>8)&255); (y)[0] = (unsigned char)((x)&255); } + +#define LOAD32L(x, y) \ + { x = ((unsigned long)((y)[3] & 255)<<24) | \ + ((unsigned long)((y)[2] & 255)<<16) | \ + ((unsigned long)((y)[1] & 255)<<8) | \ + ((unsigned long)((y)[0] & 255)); } + +/* rotates the hard way */ +#define ROR(x, y) ( ((((unsigned long)(x)&0xFFFFFFFFUL)>>(unsigned long)((y)&31)) | ((unsigned long)(x)<<(unsigned long)(32-((y)&31)))) & 0xFFFFFFFFUL) + + +/* Id: s128multab.h 213 2003-12-16 04:27:12Z ggr $ */ +/* @(#)TuringMultab.h 1.3 (QUALCOMM) 02/09/03 */ +/* Multiplication table for Turing using 0xD02B4367 */ + +static const ulong32 Multab[256] = { + 0x00000000, 0xD02B4367, 0xED5686CE, 0x3D7DC5A9, + 0x97AC41D1, 0x478702B6, 0x7AFAC71F, 0xAAD18478, + 0x631582EF, 0xB33EC188, 0x8E430421, 0x5E684746, + 0xF4B9C33E, 0x24928059, 0x19EF45F0, 0xC9C40697, + 0xC62A4993, 0x16010AF4, 0x2B7CCF5D, 0xFB578C3A, + 0x51860842, 0x81AD4B25, 0xBCD08E8C, 0x6CFBCDEB, + 0xA53FCB7C, 0x7514881B, 0x48694DB2, 0x98420ED5, + 0x32938AAD, 0xE2B8C9CA, 0xDFC50C63, 0x0FEE4F04, + 0xC154926B, 0x117FD10C, 0x2C0214A5, 0xFC2957C2, + 0x56F8D3BA, 0x86D390DD, 0xBBAE5574, 0x6B851613, + 0xA2411084, 0x726A53E3, 0x4F17964A, 0x9F3CD52D, + 0x35ED5155, 0xE5C61232, 0xD8BBD79B, 0x089094FC, + 0x077EDBF8, 0xD755989F, 0xEA285D36, 0x3A031E51, + 0x90D29A29, 0x40F9D94E, 0x7D841CE7, 0xADAF5F80, + 0x646B5917, 0xB4401A70, 0x893DDFD9, 0x59169CBE, + 0xF3C718C6, 0x23EC5BA1, 0x1E919E08, 0xCEBADD6F, + 0xCFA869D6, 0x1F832AB1, 0x22FEEF18, 0xF2D5AC7F, + 0x58042807, 0x882F6B60, 0xB552AEC9, 0x6579EDAE, + 0xACBDEB39, 0x7C96A85E, 0x41EB6DF7, 0x91C02E90, + 0x3B11AAE8, 0xEB3AE98F, 0xD6472C26, 0x066C6F41, + 0x09822045, 0xD9A96322, 0xE4D4A68B, 0x34FFE5EC, + 0x9E2E6194, 0x4E0522F3, 0x7378E75A, 0xA353A43D, + 0x6A97A2AA, 0xBABCE1CD, 0x87C12464, 0x57EA6703, + 0xFD3BE37B, 0x2D10A01C, 0x106D65B5, 0xC04626D2, + 0x0EFCFBBD, 0xDED7B8DA, 0xE3AA7D73, 0x33813E14, + 0x9950BA6C, 0x497BF90B, 0x74063CA2, 0xA42D7FC5, + 0x6DE97952, 0xBDC23A35, 0x80BFFF9C, 0x5094BCFB, + 0xFA453883, 0x2A6E7BE4, 0x1713BE4D, 0xC738FD2A, + 0xC8D6B22E, 0x18FDF149, 0x258034E0, 0xF5AB7787, + 0x5F7AF3FF, 0x8F51B098, 0xB22C7531, 0x62073656, + 0xABC330C1, 0x7BE873A6, 0x4695B60F, 0x96BEF568, + 0x3C6F7110, 0xEC443277, 0xD139F7DE, 0x0112B4B9, + 0xD31DD2E1, 0x03369186, 0x3E4B542F, 0xEE601748, + 0x44B19330, 0x949AD057, 0xA9E715FE, 0x79CC5699, + 0xB008500E, 0x60231369, 0x5D5ED6C0, 0x8D7595A7, + 0x27A411DF, 0xF78F52B8, 0xCAF29711, 0x1AD9D476, + 0x15379B72, 0xC51CD815, 0xF8611DBC, 0x284A5EDB, + 0x829BDAA3, 0x52B099C4, 0x6FCD5C6D, 0xBFE61F0A, + 0x7622199D, 0xA6095AFA, 0x9B749F53, 0x4B5FDC34, + 0xE18E584C, 0x31A51B2B, 0x0CD8DE82, 0xDCF39DE5, + 0x1249408A, 0xC26203ED, 0xFF1FC644, 0x2F348523, + 0x85E5015B, 0x55CE423C, 0x68B38795, 0xB898C4F2, + 0x715CC265, 0xA1778102, 0x9C0A44AB, 0x4C2107CC, + 0xE6F083B4, 0x36DBC0D3, 0x0BA6057A, 0xDB8D461D, + 0xD4630919, 0x04484A7E, 0x39358FD7, 0xE91ECCB0, + 0x43CF48C8, 0x93E40BAF, 0xAE99CE06, 0x7EB28D61, + 0xB7768BF6, 0x675DC891, 0x5A200D38, 0x8A0B4E5F, + 0x20DACA27, 0xF0F18940, 0xCD8C4CE9, 0x1DA70F8E, + 0x1CB5BB37, 0xCC9EF850, 0xF1E33DF9, 0x21C87E9E, + 0x8B19FAE6, 0x5B32B981, 0x664F7C28, 0xB6643F4F, + 0x7FA039D8, 0xAF8B7ABF, 0x92F6BF16, 0x42DDFC71, + 0xE80C7809, 0x38273B6E, 0x055AFEC7, 0xD571BDA0, + 0xDA9FF2A4, 0x0AB4B1C3, 0x37C9746A, 0xE7E2370D, + 0x4D33B375, 0x9D18F012, 0xA06535BB, 0x704E76DC, + 0xB98A704B, 0x69A1332C, 0x54DCF685, 0x84F7B5E2, + 0x2E26319A, 0xFE0D72FD, 0xC370B754, 0x135BF433, + 0xDDE1295C, 0x0DCA6A3B, 0x30B7AF92, 0xE09CECF5, + 0x4A4D688D, 0x9A662BEA, 0xA71BEE43, 0x7730AD24, + 0xBEF4ABB3, 0x6EDFE8D4, 0x53A22D7D, 0x83896E1A, + 0x2958EA62, 0xF973A905, 0xC40E6CAC, 0x14252FCB, + 0x1BCB60CF, 0xCBE023A8, 0xF69DE601, 0x26B6A566, + 0x8C67211E, 0x5C4C6279, 0x6131A7D0, 0xB11AE4B7, + 0x78DEE220, 0xA8F5A147, 0x958864EE, 0x45A32789, + 0xEF72A3F1, 0x3F59E096, 0x0224253F, 0xD20F6658, +}; + +/* Id: s128sbox.h 213 2003-12-16 04:27:12Z ggr $ */ +/* Sbox for SOBER-128 */ +/* + * This is really the combination of two SBoxes; the least significant + * 24 bits comes from: + * 8->32 Sbox generated by Millan et. al. at Queensland University of + * Technology. See: E. Dawson, W. Millan, L. Burnett, G. Carter, + * "On the Design of 8*32 S-boxes". Unpublished report, by the + * Information Systems Research Centre, + * Queensland University of Technology, 1999. + * + * The most significant 8 bits are the Skipjack "F table", which can be + * found at http://csrc.nist.gov/CryptoToolkit/skipjack/skipjack.pdf . + * In this optimised table, though, the intent is to XOR the word from + * the table selected by the high byte with the input word. Thus, the + * high byte is actually the Skipjack F-table entry XORED with its + * table index. + */ +static const ulong32 Sbox[256] = { + 0xa3aa1887, 0xd65e435c, 0x0b65c042, 0x800e6ef4, + 0xfc57ee20, 0x4d84fed3, 0xf066c502, 0xf354e8ae, + 0xbb2ee9d9, 0x281f38d4, 0x1f829b5d, 0x735cdf3c, + 0x95864249, 0xbc2e3963, 0xa1f4429f, 0xf6432c35, + 0xf7f40325, 0x3cc0dd70, 0x5f973ded, 0x9902dc5e, + 0xda175b42, 0x590012bf, 0xdc94d78c, 0x39aab26b, + 0x4ac11b9a, 0x8c168146, 0xc3ea8ec5, 0x058ac28f, + 0x52ed5c0f, 0x25b4101c, 0x5a2db082, 0x370929e1, + 0x2a1843de, 0xfe8299fc, 0x202fbc4b, 0x833915dd, + 0x33a803fa, 0xd446b2de, 0x46233342, 0x4fcee7c3, + 0x3ad607ef, 0x9e97ebab, 0x507f859b, 0xe81f2e2f, + 0xc55b71da, 0xd7e2269a, 0x1339c3d1, 0x7ca56b36, + 0xa6c9def2, 0xb5c9fc5f, 0x5927b3a3, 0x89a56ddf, + 0xc625b510, 0x560f85a7, 0xace82e71, 0x2ecb8816, + 0x44951e2a, 0x97f5f6af, 0xdfcbc2b3, 0xce4ff55d, + 0xcb6b6214, 0x2b0b83e3, 0x549ea6f5, 0x9de041af, + 0x792f1f17, 0xf73b99ee, 0x39a65ec0, 0x4c7016c6, + 0x857709a4, 0xd6326e01, 0xc7b280d9, 0x5cfb1418, + 0xa6aff227, 0xfd548203, 0x506b9d96, 0xa117a8c0, + 0x9cd5bf6e, 0xdcee7888, 0x61fcfe64, 0xf7a193cd, + 0x050d0184, 0xe8ae4930, 0x88014f36, 0xd6a87088, + 0x6bad6c2a, 0x1422c678, 0xe9204de7, 0xb7c2e759, + 0x0200248e, 0x013b446b, 0xda0d9fc2, 0x0414a895, + 0x3a6cc3a1, 0x56fef170, 0x86c19155, 0xcf7b8a66, + 0x551b5e69, 0xb4a8623e, 0xa2bdfa35, 0xc4f068cc, + 0x573a6acd, 0x6355e936, 0x03602db9, 0x0edf13c1, + 0x2d0bb16d, 0x6980b83c, 0xfeb23763, 0x3dd8a911, + 0x01b6bc13, 0xf55579d7, 0xf55c2fa8, 0x19f4196e, + 0xe7db5476, 0x8d64a866, 0xc06e16ad, 0xb17fc515, + 0xc46feb3c, 0x8bc8a306, 0xad6799d9, 0x571a9133, + 0x992466dd, 0x92eb5dcd, 0xac118f50, 0x9fafb226, + 0xa1b9cef3, 0x3ab36189, 0x347a19b1, 0x62c73084, + 0xc27ded5c, 0x6c8bc58f, 0x1cdde421, 0xed1e47fb, + 0xcdcc715e, 0xb9c0ff99, 0x4b122f0f, 0xc4d25184, + 0xaf7a5e6c, 0x5bbf18bc, 0x8dd7c6e0, 0x5fb7e420, + 0x521f523f, 0x4ad9b8a2, 0xe9da1a6b, 0x97888c02, + 0x19d1e354, 0x5aba7d79, 0xa2cc7753, 0x8c2d9655, + 0x19829da1, 0x531590a7, 0x19c1c149, 0x3d537f1c, + 0x50779b69, 0xed71f2b7, 0x463c58fa, 0x52dc4418, + 0xc18c8c76, 0xc120d9f0, 0xafa80d4d, 0x3b74c473, + 0xd09410e9, 0x290e4211, 0xc3c8082b, 0x8f6b334a, + 0x3bf68ed2, 0xa843cc1b, 0x8d3c0ff3, 0x20e564a0, + 0xf8f55a4f, 0x2b40f8e7, 0xfea7f15f, 0xcf00fe21, + 0x8a6d37d6, 0xd0d506f1, 0xade00973, 0xefbbde36, + 0x84670fa8, 0xfa31ab9e, 0xaedab618, 0xc01f52f5, + 0x6558eb4f, 0x71b9e343, 0x4b8d77dd, 0x8cb93da6, + 0x740fd52d, 0x425412f8, 0xc5a63360, 0x10e53ad0, + 0x5a700f1c, 0x8324ed0b, 0xe53dc1ec, 0x1a366795, + 0x6d549d15, 0xc5ce46d7, 0xe17abe76, 0x5f48e0a0, + 0xd0f07c02, 0x941249b7, 0xe49ed6ba, 0x37a47f78, + 0xe1cfffbd, 0xb007ca84, 0xbb65f4da, 0xb59f35da, + 0x33d2aa44, 0x417452ac, 0xc0d674a7, 0x2d61a46a, + 0xdc63152a, 0x3e12b7aa, 0x6e615927, 0xa14fb118, + 0xa151758d, 0xba81687b, 0xe152f0b3, 0x764254ed, + 0x34c77271, 0x0a31acab, 0x54f94aec, 0xb9e994cd, + 0x574d9e81, 0x5b623730, 0xce8a21e8, 0x37917f0b, + 0xe8a9b5d6, 0x9697adf8, 0xf3d30431, 0x5dcac921, + 0x76b35d46, 0xaa430a36, 0xc2194022, 0x22bca65e, + 0xdaec70ba, 0xdfaea8cc, 0x777bae8b, 0x242924d5, + 0x1f098a5a, 0x4b396b81, 0x55de2522, 0x435c1cb8, + 0xaeb8fe1d, 0x9db3c697, 0x5b164f83, 0xe0c16376, + 0xa319224c, 0xd0203b35, 0x433ac0fe, 0x1466a19a, + 0x45f0b24f, 0x51fda998, 0xc0d52d71, 0xfa0896a8, + 0xf9e6053f, 0xa4b0d300, 0xd499cbcc, 0xb95e3d40, +}; + + +/* Implementation of SOBER-128 by Tom St Denis. + * Based on s128fast.c reference code supplied by Greg Rose of QUALCOMM. + */ + +/* don't change these... */ +#define N 17 +#define FOLD N /* how many iterations of folding to do */ +#define INITKONST 0x6996c53a /* value of KONST to use during key loading */ +#define KEYP 15 /* where to insert key words */ +#define FOLDP 4 /* where to insert non-linear feedback */ + +static ulong32 BYTE2WORD(const unsigned char *b) +{ + ulong32 t; + LOAD32L(t, b); + return t; +} + +static void XORWORD(ulong32 w, unsigned char *b) +{ + ulong32 t; + LOAD32L(t, b); + t ^= w; + STORE32L(t, b); +} + +/* give correct offset for the current position of the register, + * where logically R[0] is at position "zero". + */ +#define OFF(zero, i) (((zero)+(i)) % N) + +/* step the LFSR */ +/* After stepping, "zero" moves right one place */ +#define STEP(R,z) \ + R[OFF(z,0)] = R[OFF(z,15)] ^ R[OFF(z,4)] ^ (R[OFF(z,0)] << 8) ^ Multab[(R[OFF(z,0)] >> 24) & 0xFF]; + +static void cycle(ulong32 *R) +{ + ulong32 t; + int i; + + STEP(R,0); + t = R[0]; + for (i = 1; i < N; ++i) { + R[i-1] = R[i]; + } + R[N-1] = t; +} + +/* Return a non-linear function of some parts of the register. + */ +#define NLFUNC(c,z) \ +{ \ + t = c->R[OFF(z,0)] + c->R[OFF(z,16)]; \ + t ^= Sbox[(t >> 24) & 0xFF]; \ + t = ROR(t, 8); \ + t = ((t + c->R[OFF(z,1)]) ^ c->konst) + c->R[OFF(z,6)]; \ + t ^= Sbox[(t >> 24) & 0xFF]; \ + t = t + c->R[OFF(z,13)]; \ +} + +static ulong32 nltap(sober128_prng *c) +{ + ulong32 t; + NLFUNC(c, 0); + return t; +} + +/* initialise to known state + */ +int sober128_start(sober128_prng *c) +{ + int i; + + /* Register initialised to Fibonacci numbers */ + c->R[0] = 1; + c->R[1] = 1; + for (i = 2; i < N; ++i) { + c->R[i] = c->R[i-1] + c->R[i-2]; + } + c->konst = INITKONST; + + /* next add_entropy will be the key */ + c->flag = 1; + c->set = 0; + + return 0; +} + +/* Save the current register state + */ +static void s128_savestate(sober128_prng *c) +{ + int i; + for (i = 0; i < N; ++i) { + c->initR[i] = c->R[i]; + } +} + +/* initialise to previously saved register state + */ +static void s128_reloadstate(sober128_prng *c) +{ + int i; + + for (i = 0; i < N; ++i) { + c->R[i] = c->initR[i]; + } +} + +/* Initialise "konst" + */ +static void s128_genkonst(sober128_prng *c) +{ + ulong32 newkonst; + + do { + cycle(c->R); + newkonst = nltap(c); + } while ((newkonst & 0xFF000000) == 0); + c->konst = newkonst; +} + +/* Load key material into the register + */ +#define ADDKEY(k) \ + c->R[KEYP] += (k); + +#define XORNL(nl) \ + c->R[FOLDP] ^= (nl); + +/* nonlinear diffusion of register for key */ +#define DROUND(z) STEP(c->R,z); NLFUNC(c,(z+1)); c->R[OFF((z+1),FOLDP)] ^= t; +static void s128_diffuse(sober128_prng *c) +{ + ulong32 t; + /* relies on FOLD == N == 17! */ + DROUND(0); + DROUND(1); + DROUND(2); + DROUND(3); + DROUND(4); + DROUND(5); + DROUND(6); + DROUND(7); + DROUND(8); + DROUND(9); + DROUND(10); + DROUND(11); + DROUND(12); + DROUND(13); + DROUND(14); + DROUND(15); + DROUND(16); +} + +int sober128_add_entropy(const unsigned char *buf, unsigned long len, sober128_prng *c) +{ + ulong32 i, k; + + + if (c->flag == 1) { + /* this is the first call to the add_entropy so this input is the key */ + /* len must be multiple of 4 bytes */ + /* assert ((len & 3) == 0); */ + + for (i = 0; i < len/4; i++) { + k = BYTE2WORD(&buf[i*4]); + ADDKEY(k); + cycle(c->R); + XORNL(nltap(c)); + } + + /* also fold in the length of the key */ + ADDKEY(len); + + /* now diffuse */ + s128_diffuse(c); + + s128_genkonst(c); + s128_savestate(c); + c->nbuf = 0; + c->flag = 0; + c->set = 1; + } else { + /* ok we are adding an IV then... */ + s128_reloadstate(c); + + /* len must be multiple of 4 bytes */ + /* assert ((len & 3) == 0); */ + + for (i = 0; i < len/4; i++) { + k = BYTE2WORD(&buf[i*4]); + ADDKEY(k); + cycle(c->R); + XORNL(nltap(c)); + } + + /* also fold in the length of the key */ + ADDKEY(len); + + /* now diffuse */ + s128_diffuse(c); + c->nbuf = 0; + } + + return 0; +} + +/* XOR pseudo-random bytes into buffer + */ +#define SROUND(z) STEP(c->R,z); NLFUNC(c,(z+1)); XORWORD(t, buf+(z*4)); + +unsigned long sober128_read(unsigned char *buf, unsigned long nbytes, sober128_prng *c) +{ + ulong32 t, tlen; + + tlen = nbytes; + + /* handle any previously buffered bytes */ + while (c->nbuf != 0 && nbytes != 0) { + *buf++ ^= c->sbuf & 0xFF; + c->sbuf >>= 8; + c->nbuf -= 8; + --nbytes; + } + +#ifndef SMALL_CODE + /* do lots at a time, if there's enough to do */ + while (nbytes >= N*4) { + SROUND(0); + SROUND(1); + SROUND(2); + SROUND(3); + SROUND(4); + SROUND(5); + SROUND(6); + SROUND(7); + SROUND(8); + SROUND(9); + SROUND(10); + SROUND(11); + SROUND(12); + SROUND(13); + SROUND(14); + SROUND(15); + SROUND(16); + buf += 4*N; + nbytes -= 4*N; + } +#endif + + /* do small or odd size buffers the slow way */ + while (4 <= nbytes) { + cycle(c->R); + t = nltap(c); + XORWORD(t, buf); + buf += 4; + nbytes -= 4; + } + + /* handle any trailing bytes */ + if (nbytes != 0) { + cycle(c->R); + c->sbuf = nltap(c); + c->nbuf = 32; + while (c->nbuf != 0 && nbytes != 0) { + *buf++ ^= c->sbuf & 0xFF; + c->sbuf >>= 8; + c->nbuf -= 8; + --nbytes; + } + } + + return tlen; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/sober128.h b/wsutil/sober128.h new file mode 100644 index 00000000..0238f0ef --- /dev/null +++ b/wsutil/sober128.h @@ -0,0 +1,48 @@ +/** @file + This file is derived from sober128 implementation in corosync + cluster engine. corosync cluster engine borrows the implementation + from LibTomCrypt. + + The latest version of the original code can be found at + http://www.libtom.net/LibTomCrypt/ according to which this code is in the + Public Domain +*/ + +/* About LibTomCrypt: + * --------------------------------------------------------------------- + * LibTomCrypt, modular cryptographic library -- Tom St Denis + * + * LibTomCrypt is a library that provides various cryptographic + * algorithms in a highly modular and flexible manner. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tomstdenis@iahu.ca, http://www.libtom.net/LibTomCrypt/ + */ + +#ifndef _SOBER127_H +#define _SOBER127_H + +#include "ws_symbol_export.h" + +typedef struct _sober128_prng { + unsigned long R[17], /* Working storage for the shift register */ + initR[17], /* saved register contents */ + konst, /* key dependent constant */ + sbuf; /* partial word encryption buffer */ + + int nbuf, /* number of part-word stream bits buffered */ + flag, /* first add_entropy call or not? */ + set; /* did we call add_entropy to set key? */ + +} sober128_prng; + +WS_DLL_PUBLIC +int sober128_start(sober128_prng *prng); +WS_DLL_PUBLIC +int sober128_add_entropy(const unsigned char *buf, unsigned long len, sober128_prng *prng); +WS_DLL_PUBLIC +unsigned long sober128_read(unsigned char *buf, unsigned long len, sober128_prng *prng); + +#endif /* sober128.h */ diff --git a/wsutil/socket.c b/wsutil/socket.c new file mode 100644 index 00000000..88297f80 --- /dev/null +++ b/wsutil/socket.c @@ -0,0 +1,150 @@ +/* socket.c + * Socket wrappers + * + * Copyright 2019, Gerald Combs + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" +#include "socket.h" + +#include <stdlib.h> +#include <errno.h> + +#include <wsutil/inet_addr.h> + +#ifdef _WIN32 +#include <wsutil/win32-utils.h> +#define in_port_t uint16_t +#endif + +char * +ws_init_sockets(void) +{ + char *errmsg = NULL; +#ifdef _WIN32 + int err; + WORD wVersionRequested; + WSADATA wsaData; + + wVersionRequested = MAKEWORD(2, 2); + err = WSAStartup(wVersionRequested, &wsaData); + if (err != 0) { + errmsg = ws_strdup_printf("Couldn't initialize Windows Sockets: %s", + win32strerror(err)); + } +#endif + return errmsg; +} + +void +ws_cleanup_sockets(void) +{ +#ifdef _WIN32 + /* XXX - any reason to check the error return? */ + WSACleanup(); +#endif +} + +int +ws_socket_ptoa(struct sockaddr_storage *dst, const char *src, + uint16_t def_port) +{ + int ret = -1, af = -1; + char *addr_src, *p; + char *addr_str = NULL, *port_str = NULL; + union { + ws_in4_addr ip4; + ws_in6_addr ip6; + } addr; + char *endptr; + long num; + in_port_t port; + + addr_src = g_strdup(src); + + /* Is it an IPv6/IPv4 literal address enclosed in braces? */ + if (*addr_src == '[') { + addr_str = addr_src + 1; + if ((p = strchr(addr_str, ']')) == NULL) { + errno = EINVAL; + goto out; + } + *p++ = '\0'; + if (*p == ':') { + port_str = p + 1; + } + else if (*p != '\0') { + errno = EINVAL; + goto out; + } + if (ws_inet_pton6(addr_str, &addr.ip6)) { + af = AF_INET6; + } + else if (ws_inet_pton4(addr_str, &addr.ip4)) { + af = AF_INET; + } + else { + errno = EINVAL; + goto out; + } + } + else { + /* It is an IPv4 dotted decimal. */ + addr_str = addr_src; + if ((p = strchr(addr_str, ':')) != NULL) { + *p++ = '\0'; + port_str = p; + } + if (ws_inet_pton4(addr_str, &addr.ip4)) { + af = AF_INET; + } + else { + errno = EINVAL; + goto out; + } + } + + if (port_str != NULL && *port_str != '\0') { + num = strtol(port_str, &endptr, 10); + /* We want the entire string to be a valid decimal representation. */ + if (endptr == port_str || *endptr != '\0' || num < 0 || num > UINT16_MAX) { + errno = EINVAL; + goto out; + } + port = g_htons(num); + } + else { + port = g_htons(def_port); + } + + /* sockaddr_storage is guaranteed to fit any sockaddr type. */ + if (af == AF_INET6) { + struct sockaddr_in6 *sa = (struct sockaddr_in6 *)dst; + memset(sa, 0, sizeof(struct sockaddr_in6)); + sa->sin6_family = AF_INET6; + sa->sin6_port = port; + memcpy(&sa->sin6_addr, &addr.ip6, sizeof(struct in6_addr)); + ret = 0; + } + else if (af == AF_INET) { + struct sockaddr_in *sa = (struct sockaddr_in *)dst; + memset(sa, 0, sizeof(struct sockaddr_in)); + sa->sin_family = AF_INET; + sa->sin_port = port; + memcpy(&sa->sin_addr, &addr.ip4, sizeof(struct in_addr)); + ret = 0; + } + else { + ws_assert_not_reached(); + } + +out: + g_free(addr_src); + return ret; +} diff --git a/wsutil/socket.h b/wsutil/socket.h new file mode 100644 index 00000000..7393db13 --- /dev/null +++ b/wsutil/socket.h @@ -0,0 +1,97 @@ +/** @file + * Socket wrappers + * + * Copyright 2016, Dario Lombardo + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#ifndef __SOCKET_H__ +#define __SOCKET_H__ + +#include <wireshark.h> + +#if defined(_WIN32) && !defined(__CYGWIN__) + #include <windows.h> + #include <ws2tcpip.h> + #include <winsock2.h> + #include <process.h> + + #define socket_handle_t SOCKET + #define socklen_t int +#else + /* + * UN*X, or Windows pretending to be UN*X with the aid of Cygwin. + */ + #ifdef HAVE_UNISTD_H + /* + * For close(). + */ + #include <unistd.h> + #endif + #ifdef HAVE_SYS_SOCKET_H + #include <sys/socket.h> + #endif + + #define closesocket(socket) close(socket) + #define socket_handle_t int +#ifndef INVALID_SOCKET + #define INVALID_SOCKET (-1) +#endif + #define SOCKET_ERROR (-1) +#endif + +#ifdef HAVE_ARPA_INET_H + #include <arpa/inet.h> +#endif + +#ifdef HAVE_NETINET_IN_H + #include <netinet/in.h> +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Initialize sockets. + * + * Returns NULL on success, a g_malloc()ed error message on failure. + */ +WS_DLL_PUBLIC char *ws_init_sockets(void); + +/* + * Clean up sockets. + */ +WS_DLL_PUBLIC void ws_cleanup_sockets(void); + +/* + * Convert the strings ipv4_address:port or [ipv6_address]:port to a + * sockaddr object. Ports are optional. Receives default port + * in host byte order. + */ +WS_DLL_PUBLIC int +ws_socket_ptoa(struct sockaddr_storage *dst, const char *src, + uint16_t def_port); + +#ifdef __cplusplus +} +#endif + +#endif /* __SOCKET_H__ */ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 8 + * tab-width: 8 + * indent-tabs-mode: t + * End: + * + * vi: set shiftwidth=8 tabstop=8 noexpandtab: + * :indentSize=8:tabSize=8:noTabs=false: + */ diff --git a/wsutil/str_util.c b/wsutil/str_util.c new file mode 100644 index 00000000..4243b22b --- /dev/null +++ b/wsutil/str_util.c @@ -0,0 +1,1309 @@ +/* str_util.c + * String utility routines + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#define _GNU_SOURCE +#include "config.h" +#include "str_util.h" + +#include <string.h> + +#include <ws_codepoints.h> + +#include <wsutil/to_str.h> + + +static const char hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + +char * +wmem_strconcat(wmem_allocator_t *allocator, const char *first, ...) +{ + size_t len; + va_list args; + char *s; + char *concat; + char *ptr; + + if (!first) + return NULL; + + len = 1 + strlen(first); + va_start(args, first); + while ((s = va_arg(args, char*))) { + len += strlen(s); + } + va_end(args); + + ptr = concat = (char *)wmem_alloc(allocator, len); + + ptr = g_stpcpy(ptr, first); + va_start(args, first); + while ((s = va_arg(args, char*))) { + ptr = g_stpcpy(ptr, s); + } + va_end(args); + + return concat; +} + +char * +wmem_strjoin(wmem_allocator_t *allocator, + const char *separator, const char *first, ...) +{ + size_t len; + va_list args; + size_t separator_len; + char *s; + char *concat; + char *ptr; + + if (!first) + return NULL; + + if (separator == NULL) { + separator = ""; + } + + separator_len = strlen (separator); + + len = 1 + strlen(first); /* + 1 for null byte */ + va_start(args, first); + while ((s = va_arg(args, char*))) { + len += (separator_len + strlen(s)); + } + va_end(args); + + ptr = concat = (char *)wmem_alloc(allocator, len); + ptr = g_stpcpy(ptr, first); + va_start(args, first); + while ((s = va_arg(args, char*))) { + ptr = g_stpcpy(ptr, separator); + ptr = g_stpcpy(ptr, s); + } + va_end(args); + + return concat; + +} + +char * +wmem_strjoinv(wmem_allocator_t *allocator, + const char *separator, char **str_array) +{ + char *string = NULL; + + ws_return_val_if(!str_array, NULL); + + if (separator == NULL) { + separator = ""; + } + + if (str_array[0]) { + int i; + char *ptr; + size_t len, separator_len; + + separator_len = strlen(separator); + + /* Get first part of length. Plus one for null byte. */ + len = 1 + strlen(str_array[0]); + /* Get the full length, including the separators. */ + for (i = 1; str_array[i] != NULL; i++) { + len += separator_len; + len += strlen(str_array[i]); + } + + /* Allocate and build the string. */ + string = (char *)wmem_alloc(allocator, len); + ptr = g_stpcpy(string, str_array[0]); + for (i = 1; str_array[i] != NULL; i++) { + ptr = g_stpcpy(ptr, separator); + ptr = g_stpcpy(ptr, str_array[i]); + } + } else { + string = wmem_strdup(allocator, ""); + } + + return string; + +} + +char ** +wmem_strsplit(wmem_allocator_t *allocator, const char *src, + const char *delimiter, int max_tokens) +{ + char *splitted; + char *s; + unsigned tokens; + unsigned sep_len; + unsigned i; + char **vec; + + if (!src || !delimiter || !delimiter[0]) + return NULL; + + /* An empty string results in an empty vector. */ + if (!src[0]) { + vec = wmem_new0(allocator, char *); + return vec; + } + + splitted = wmem_strdup(allocator, src); + sep_len = (unsigned)strlen(delimiter); + + if (max_tokens < 1) + max_tokens = INT_MAX; + + /* Calculate the number of fields. */ + s = splitted; + tokens = 1; + while (tokens < (unsigned)max_tokens && (s = strstr(s, delimiter))) { + s += sep_len; + tokens++; + } + + vec = wmem_alloc_array(allocator, char *, tokens + 1); + + /* Populate the array of string tokens. */ + s = splitted; + vec[0] = s; + tokens = 1; + while (tokens < (unsigned)max_tokens && (s = strstr(s, delimiter))) { + for (i = 0; i < sep_len; i++) + s[i] = '\0'; + s += sep_len; + vec[tokens] = s; + tokens++; + + } + + vec[tokens] = NULL; + + return vec; +} + +/* + * wmem_ascii_strdown: + * based on g_ascii_strdown. + */ +char* +wmem_ascii_strdown(wmem_allocator_t *allocator, const char *str, ssize_t len) +{ + char *result, *s; + + g_return_val_if_fail (str != NULL, NULL); + + if (len < 0) + len = strlen (str); + + result = wmem_strndup(allocator, str, len); + for (s = result; *s; s++) + *s = g_ascii_tolower (*s); + + return result; +} + +int +ws_xton(char ch) +{ + switch (ch) { + case '0': return 0; + case '1': return 1; + case '2': return 2; + case '3': return 3; + case '4': return 4; + case '5': return 5; + case '6': return 6; + case '7': return 7; + case '8': return 8; + case '9': return 9; + case 'a': case 'A': return 10; + case 'b': case 'B': return 11; + case 'c': case 'C': return 12; + case 'd': case 'D': return 13; + case 'e': case 'E': return 14; + case 'f': case 'F': return 15; + default: return -1; + } +} + +/* Convert all ASCII letters to lower case, in place. */ +char * +ascii_strdown_inplace(char *str) +{ + char *s; + + for (s = str; *s; s++) + /* What 'g_ascii_tolower (char c)' does, this should be slightly more efficient */ + *s = g_ascii_isupper (*s) ? *s - 'A' + 'a' : *s; + + return (str); +} + +/* Convert all ASCII letters to upper case, in place. */ +char * +ascii_strup_inplace(char *str) +{ + char *s; + + for (s = str; *s; s++) + /* What 'g_ascii_toupper (char c)' does, this should be slightly more efficient */ + *s = g_ascii_islower (*s) ? *s - 'a' + 'A' : *s; + + return (str); +} + +/* Check if an entire string is printable. */ +bool +isprint_string(const char *str) +{ + unsigned pos; + + /* Loop until we reach the end of the string (a null) */ + for(pos = 0; str[pos] != '\0'; pos++){ + if(!g_ascii_isprint(str[pos])){ + /* The string contains a non-printable character */ + return false; + } + } + + /* The string contains only printable characters */ + return true; +} + +/* Check if an entire UTF-8 string is printable. */ +bool +isprint_utf8_string(const char *str, const unsigned length) +{ + const char *strend = str + length; + + if (!g_utf8_validate(str, length, NULL)) { + return false; + } + + while (str < strend) { + /* This returns false for G_UNICODE_CONTROL | G_UNICODE_FORMAT | + * G_UNICODE_UNASSIGNED | G_UNICODE_SURROGATE + * XXX: Could it be ok to have certain format characters, e.g. + * U+00AD SOFT HYPHEN? If so, format_text() should be changed too. + */ + if (!g_unichar_isprint(g_utf8_get_char(str))) { + return false; + } + str = g_utf8_next_char(str); + } + + return true; +} + +/* Check if an entire string is digits. */ +bool +isdigit_string(const unsigned char *str) +{ + unsigned pos; + + /* Loop until we reach the end of the string (a null) */ + for(pos = 0; str[pos] != '\0'; pos++){ + if(!g_ascii_isdigit(str[pos])){ + /* The string contains a non-digit character */ + return false; + } + } + + /* The string contains only digits */ + return true; +} + +const char * +ws_ascii_strcasestr(const char *haystack, const char *needle) +{ + /* Do not use strcasestr() here, even if a system has it, as it is + * locale-dependent (and has different results for e.g. Turkic languages.) + * FreeBSD, NetBSD, macOS have a strcasestr_l() that could be used. + */ + size_t hlen = strlen(haystack); + size_t nlen = strlen(needle); + + while (hlen-- >= nlen) { + if (!g_ascii_strncasecmp(haystack, needle, nlen)) + return haystack; + haystack++; + } + return NULL; +} + +#define FORMAT_SIZE_UNIT_MASK 0x00ff +#define FORMAT_SIZE_PFX_MASK 0xff00 + +static const char *thousands_grouping_fmt = NULL; + +DIAG_OFF(format) +static void test_printf_thousands_grouping(void) { + /* test whether wmem_strbuf works with "'" flag character */ + wmem_strbuf_t *buf = wmem_strbuf_new(NULL, NULL); + wmem_strbuf_append_printf(buf, "%'d", 22); + if (g_strcmp0(wmem_strbuf_get_str(buf), "22") == 0) { + thousands_grouping_fmt = "%'"PRId64; + } else { + /* Don't use */ + thousands_grouping_fmt = "%"PRId64; + } + wmem_strbuf_destroy(buf); +} +DIAG_ON(format) + +/* Given a size, return its value in a human-readable format */ +/* This doesn't handle fractional values. We might want to make size a double. */ +char * +format_size_wmem(wmem_allocator_t *allocator, int64_t size, + format_size_units_e unit, uint16_t flags) +{ + wmem_strbuf_t *human_str = wmem_strbuf_new(allocator, NULL); + int power = 1000; + int pfx_off = 0; + bool is_small = false; + static const char *prefix[] = {" T", " G", " M", " k", " Ti", " Gi", " Mi", " Ki"}; + char *ret_val; + + if (thousands_grouping_fmt == NULL) + test_printf_thousands_grouping(); + + if (flags & FORMAT_SIZE_PREFIX_IEC) { + pfx_off = 4; + power = 1024; + } + + if (size / power / power / power / power >= 10) { + wmem_strbuf_append_printf(human_str, thousands_grouping_fmt, size / power / power / power / power); + wmem_strbuf_append(human_str, prefix[pfx_off]); + } else if (size / power / power / power >= 10) { + wmem_strbuf_append_printf(human_str, thousands_grouping_fmt, size / power / power / power); + wmem_strbuf_append(human_str, prefix[pfx_off+1]); + } else if (size / power / power >= 10) { + wmem_strbuf_append_printf(human_str, thousands_grouping_fmt, size / power / power); + wmem_strbuf_append(human_str, prefix[pfx_off+2]); + } else if (size / power >= 10) { + wmem_strbuf_append_printf(human_str, thousands_grouping_fmt, size / power); + wmem_strbuf_append(human_str, prefix[pfx_off+3]); + } else { + wmem_strbuf_append_printf(human_str, thousands_grouping_fmt, size); + is_small = true; + } + + switch (unit) { + case FORMAT_SIZE_UNIT_NONE: + break; + case FORMAT_SIZE_UNIT_BYTES: + wmem_strbuf_append(human_str, is_small ? " bytes" : "B"); + break; + case FORMAT_SIZE_UNIT_BITS: + wmem_strbuf_append(human_str, is_small ? " bits" : "b"); + break; + case FORMAT_SIZE_UNIT_BITS_S: + wmem_strbuf_append(human_str, is_small ? " bits/s" : "bps"); + break; + case FORMAT_SIZE_UNIT_BYTES_S: + wmem_strbuf_append(human_str, is_small ? " bytes/s" : "Bps"); + break; + case FORMAT_SIZE_UNIT_PACKETS: + wmem_strbuf_append(human_str, is_small ? " packets" : "packets"); + break; + case FORMAT_SIZE_UNIT_PACKETS_S: + wmem_strbuf_append(human_str, is_small ? " packets/s" : "packets/s"); + break; + default: + ws_assert_not_reached(); + } + + ret_val = wmem_strbuf_finalize(human_str); + return g_strchomp(ret_val); +} + +char +printable_char_or_period(char c) +{ + return g_ascii_isprint(c) ? c : '.'; +} + +/* + * This is used by the display filter engine and must be compatible + * with display filter syntax. + */ +static inline bool +escape_char(char c, char *p) +{ + int r = -1; + ws_assert(p); + + /* + * Backslashes and double-quotes must + * be escaped. Whitespace is also escaped. + */ + switch (c) { + case '\a': r = 'a'; break; + case '\b': r = 'b'; break; + case '\f': r = 'f'; break; + case '\n': r = 'n'; break; + case '\r': r = 'r'; break; + case '\t': r = 't'; break; + case '\v': r = 'v'; break; + case '"': r = '"'; break; + case '\\': r = '\\'; break; + case '\0': r = '0'; break; + } + + if (r != -1) { + *p = r; + return true; + } + return false; +} + +static inline bool +escape_null(char c, char *p) +{ + ws_assert(p); + if (c == '\0') { + *p = '0'; + return true; + } + return false; +} + +static char * +escape_string_len(wmem_allocator_t *alloc, const char *string, ssize_t len, + bool (*escape_func)(char c, char *p), bool add_quotes) +{ + char c, r; + wmem_strbuf_t *buf; + size_t alloc_size; + ssize_t i; + + if (len < 0) + len = strlen(string); + + alloc_size = len; + if (add_quotes) + alloc_size += 2; + + buf = wmem_strbuf_new_sized(alloc, alloc_size); + + if (add_quotes) + wmem_strbuf_append_c(buf, '"'); + + for (i = 0; i < len; i++) { + c = string[i]; + if ((escape_func(c, &r))) { + wmem_strbuf_append_c(buf, '\\'); + wmem_strbuf_append_c(buf, r); + } + else { + /* Other UTF-8 bytes are passed through. */ + wmem_strbuf_append_c(buf, c); + } + } + + if (add_quotes) + wmem_strbuf_append_c(buf, '"'); + + return wmem_strbuf_finalize(buf); +} + +char * +ws_escape_string_len(wmem_allocator_t *alloc, const char *string, ssize_t len, bool add_quotes) +{ + return escape_string_len(alloc, string, len, escape_char, add_quotes); +} + +char * +ws_escape_string(wmem_allocator_t *alloc, const char *string, bool add_quotes) +{ + return escape_string_len(alloc, string, -1, escape_char, add_quotes); +} + +char *ws_escape_null(wmem_allocator_t *alloc, const char *string, size_t len, bool add_quotes) +{ + return escape_string_len(alloc, string, len, escape_null, add_quotes); +} + +const char * +ws_strerrorname_r(int errnum, char *buf, size_t buf_size) +{ +#ifdef HAVE_STRERRORNAME_NP + const char *errstr = strerrorname_np(errnum); + if (errstr != NULL) { + (void)g_strlcpy(buf, errstr, buf_size); + return buf; + } +#endif + snprintf(buf, buf_size, "Errno(%d)", errnum); + return buf; +} + +char * +ws_strdup_underline(wmem_allocator_t *allocator, long offset, size_t len) +{ + if (offset < 0) + return NULL; + + wmem_strbuf_t *buf = wmem_strbuf_new_sized(allocator, offset + len); + + for (int i = 0; i < offset; i++) { + wmem_strbuf_append_c(buf, ' '); + } + wmem_strbuf_append_c(buf, '^'); + + for (size_t l = len; l > 1; l--) { + wmem_strbuf_append_c(buf, '~'); + } + + return wmem_strbuf_finalize(buf); +} + +#define INITIAL_FMTBUF_SIZE 128 + +/* + * Declare, and initialize, the variables used for an output buffer. + */ +#define FMTBUF_VARS \ + char *fmtbuf = (char*)wmem_alloc(allocator, INITIAL_FMTBUF_SIZE); \ + unsigned fmtbuf_len = INITIAL_FMTBUF_SIZE; \ + unsigned column = 0 + +/* + * Expand the buffer to be large enough to add nbytes bytes, plus a + * terminating '\0'. + */ +#define FMTBUF_EXPAND(nbytes) \ + /* \ + * Is there enough room for those bytes and also enough room for \ + * a terminating '\0'? \ + */ \ + if (column+(nbytes+1) >= fmtbuf_len) { \ + /* \ + * Double the buffer's size if it's not big enough. \ + * The size of the buffer starts at 128, so doubling its size \ + * adds at least another 128 bytes, which is more than enough \ + * for one more character plus a terminating '\0'. \ + */ \ + fmtbuf_len *= 2; \ + fmtbuf = (char *)wmem_realloc(allocator, fmtbuf, fmtbuf_len); \ + } + +/* + * Put a byte into the buffer; space must have been ensured for it. + */ +#define FMTBUF_PUTCHAR(b) \ + fmtbuf[column] = (b); \ + column++ + +/* + * Add the one-byte argument, as an octal escape sequence, to the end + * of the buffer. + */ +#define FMTBUF_PUTBYTE_OCTAL(b) \ + FMTBUF_PUTCHAR((((b)>>6)&03) + '0'); \ + FMTBUF_PUTCHAR((((b)>>3)&07) + '0'); \ + FMTBUF_PUTCHAR((((b)>>0)&07) + '0') + +/* + * Add the one-byte argument, as a hex escape sequence, to the end + * of the buffer. + */ +#define FMTBUF_PUTBYTE_HEX(b) \ + FMTBUF_PUTCHAR('\\'); \ + FMTBUF_PUTCHAR('x'); \ + FMTBUF_PUTCHAR(hex[((b) >> 4) & 0xF]); \ + FMTBUF_PUTCHAR(hex[((b) >> 0) & 0xF]) + +/* + * Put the trailing '\0' at the end of the buffer. + */ +#define FMTBUF_ENDSTR \ + fmtbuf[column] = '\0' + +static char * +format_text_internal(wmem_allocator_t *allocator, + const unsigned char *string, size_t len, + bool replace_space) +{ + FMTBUF_VARS; + const unsigned char *stringend = string + len; + unsigned char c; + + while (string < stringend) { + /* + * Get the first byte of this character. + */ + c = *string++; + if (g_ascii_isprint(c)) { + /* + * Printable ASCII, so not part of a multi-byte UTF-8 sequence. + * Make sure there's enough room for one more byte, and add + * the character. + */ + FMTBUF_EXPAND(1); + FMTBUF_PUTCHAR(c); + } else if (replace_space && g_ascii_isspace(c)) { + /* + * ASCII, so not part of a multi-byte UTF-8 sequence, but + * not printable, but is a space character; show it as a + * blank. + * + * Make sure there's enough room for one more byte, and add + * the blank. + */ + FMTBUF_EXPAND(1); + FMTBUF_PUTCHAR(' '); + } else if (c < 128) { + /* + * ASCII, so not part of a multi-byte UTF-8 sequence, but not + * printable. + * + * That requires a minimum of 2 bytes, one for the backslash + * and one for a letter, so make sure we have enough room + * for that, plus a trailing '\0'. + */ + FMTBUF_EXPAND(2); + FMTBUF_PUTCHAR('\\'); + switch (c) { + + case '\a': + FMTBUF_PUTCHAR('a'); + break; + + case '\b': + FMTBUF_PUTCHAR('b'); /* BS */ + break; + + case '\f': + FMTBUF_PUTCHAR('f'); /* FF */ + break; + + case '\n': + FMTBUF_PUTCHAR('n'); /* NL */ + break; + + case '\r': + FMTBUF_PUTCHAR('r'); /* CR */ + break; + + case '\t': + FMTBUF_PUTCHAR('t'); /* tab */ + break; + + case '\v': + FMTBUF_PUTCHAR('v'); + break; + + default: + /* + * We've already put the backslash, but this + * will put 3 more characters for the octal + * number; make sure we have enough room for + * that, plus the trailing '\0'. + */ + FMTBUF_EXPAND(3); + FMTBUF_PUTBYTE_OCTAL(c); + break; + } + } else { + /* + * We've fetched the first byte of a multi-byte UTF-8 + * sequence into c. + */ + int utf8_len; + unsigned char mask; + gunichar uc; + unsigned char first; + + if ((c & 0xe0) == 0xc0) { + /* Starts a 2-byte UTF-8 sequence; 1 byte left */ + utf8_len = 1; + mask = 0x1f; + } else if ((c & 0xf0) == 0xe0) { + /* Starts a 3-byte UTF-8 sequence; 2 bytes left */ + utf8_len = 2; + mask = 0x0f; + } else if ((c & 0xf8) == 0xf0) { + /* Starts a 4-byte UTF-8 sequence; 3 bytes left */ + utf8_len = 3; + mask = 0x07; + } else if ((c & 0xfc) == 0xf8) { + /* Starts an old-style 5-byte UTF-8 sequence; 4 bytes left */ + utf8_len = 4; + mask = 0x03; + } else if ((c & 0xfe) == 0xfc) { + /* Starts an old-style 6-byte UTF-8 sequence; 5 bytes left */ + utf8_len = 5; + mask = 0x01; + } else { + /* 0xfe or 0xff or a continuation byte - not valid */ + utf8_len = -1; + } + if (utf8_len > 0) { + /* Try to construct the Unicode character */ + uc = c & mask; + for (int i = 0; i < utf8_len; i++) { + if (string >= stringend) { + /* + * Ran out of octets, so the character is + * incomplete. Put in a REPLACEMENT CHARACTER + * instead, and then continue the loop, which + * will terminate. + */ + uc = UNICODE_REPLACEMENT_CHARACTER; + break; + } + c = *string; + if ((c & 0xc0) != 0x80) { + /* + * Not valid UTF-8 continuation character; put in + * a replacement character, and then re-process + * this octet as the beginning of a new character. + */ + uc = UNICODE_REPLACEMENT_CHARACTER; + break; + } + string++; + uc = (uc << 6) | (c & 0x3f); + } + + /* + * If this isn't a valid Unicode character, put in + * a REPLACEMENT CHARACTER. + */ + if (!g_unichar_validate(uc)) + uc = UNICODE_REPLACEMENT_CHARACTER; + } else { + /* 0xfe or 0xff; put it a REPLACEMENT CHARACTER */ + uc = UNICODE_REPLACEMENT_CHARACTER; + } + + /* + * OK, is it a printable Unicode character? + */ + if (g_unichar_isprint(uc)) { + /* + * Yes - put it into the string as UTF-8. + * This means that if it was an overlong + * encoding, this will put out the right + * sized encoding. + */ + if (uc < 0x80) { + first = 0; + utf8_len = 1; + } else if (uc < 0x800) { + first = 0xc0; + utf8_len = 2; + } else if (uc < 0x10000) { + first = 0xe0; + utf8_len = 3; + } else if (uc < 0x200000) { + first = 0xf0; + utf8_len = 4; + } else if (uc < 0x4000000) { + /* + * This should never happen, as Unicode doesn't + * go that high. + */ + first = 0xf8; + utf8_len = 5; + } else { + /* + * This should never happen, as Unicode doesn't + * go that high. + */ + first = 0xfc; + utf8_len = 6; + } + FMTBUF_EXPAND(utf8_len); + for (int i = utf8_len - 1; i > 0; i--) { + fmtbuf[column + i] = (uc & 0x3f) | 0x80; + uc >>= 6; + } + fmtbuf[column] = uc | first; + column += utf8_len; + } else if (replace_space && g_unichar_isspace(uc)) { + /* + * Not printable, but is a space character; show it + * as a blank. + * + * Make sure there's enough room for one more byte, + * and add the blank. + */ + FMTBUF_EXPAND(1); + FMTBUF_PUTCHAR(' '); + } else if (c < 128) { + /* + * ASCII, but not printable. + * Yes, this could happen with an overlong encoding. + * + * That requires a minimum of 2 bytes, one for the + * backslash and one for a letter, so make sure we + * have enough room for that, plus a trailing '\0'. + */ + FMTBUF_EXPAND(2); + FMTBUF_PUTCHAR('\\'); + switch (c) { + + case '\a': + FMTBUF_PUTCHAR('a'); + break; + + case '\b': + FMTBUF_PUTCHAR('b'); /* BS */ + break; + + case '\f': + FMTBUF_PUTCHAR('f'); /* FF */ + break; + + case '\n': + FMTBUF_PUTCHAR('n'); /* NL */ + break; + + case '\r': + FMTBUF_PUTCHAR('r'); /* CR */ + break; + + case '\t': + FMTBUF_PUTCHAR('t'); /* tab */ + break; + + case '\v': + FMTBUF_PUTCHAR('v'); + break; + + default: + /* + * We've already put the backslash, but this + * will put 3 more characters for the octal + * number; make sure we have enough room for + * that, plus the trailing '\0'. + */ + FMTBUF_EXPAND(3); + FMTBUF_PUTBYTE_OCTAL(c); + break; + } + } else { + /* + * Unicode, but not printable, and not ASCII; + * put it out as \uxxxx or \Uxxxxxxxx. + */ + if (uc <= 0xFFFF) { + FMTBUF_EXPAND(6); + FMTBUF_PUTCHAR('\\'); + FMTBUF_PUTCHAR('u'); + FMTBUF_PUTCHAR(hex[(uc >> 12) & 0xF]); + FMTBUF_PUTCHAR(hex[(uc >> 8) & 0xF]); + FMTBUF_PUTCHAR(hex[(uc >> 4) & 0xF]); + FMTBUF_PUTCHAR(hex[(uc >> 0) & 0xF]); + } else { + FMTBUF_EXPAND(10); + FMTBUF_PUTCHAR('\\'); + FMTBUF_PUTCHAR('U'); + FMTBUF_PUTCHAR(hex[(uc >> 28) & 0xF]); + FMTBUF_PUTCHAR(hex[(uc >> 24) & 0xF]); + FMTBUF_PUTCHAR(hex[(uc >> 20) & 0xF]); + FMTBUF_PUTCHAR(hex[(uc >> 16) & 0xF]); + FMTBUF_PUTCHAR(hex[(uc >> 12) & 0xF]); + FMTBUF_PUTCHAR(hex[(uc >> 8) & 0xF]); + FMTBUF_PUTCHAR(hex[(uc >> 4) & 0xF]); + FMTBUF_PUTCHAR(hex[(uc >> 0) & 0xF]); + } + } + } + } + + FMTBUF_ENDSTR; + + return fmtbuf; +} + +/* + * Given a wmem scope, a not-necessarily-null-terminated string, + * expected to be in UTF-8 but possibly containing invalid sequences + * (as it may have come from packet data), and the length of the string, + * generate a valid UTF-8 string from it, allocated in the specified + * wmem scope, that: + * + * shows printable Unicode characters as themselves; + * + * shows non-printable ASCII characters as C-style escapes (octal + * if not one of the standard ones such as LF -> '\n'); + * + * shows non-printable Unicode-but-not-ASCII characters as + * their universal character names; + * + * shows illegal UTF-8 sequences as a sequence of bytes represented + * as C-style hex escapes (XXX: Does not actually do this. Some illegal + * sequences, such as overlong encodings, the sequences reserved for + * UTF-16 surrogate halves (paired or unpaired), and values outside + * Unicode (i.e., the old sequences for code points above U+10FFFF) + * will be decoded in a permissive way. Other illegal sequences, + * such 0xFE and 0xFF and the presence of a continuation byte where + * not expected (or vice versa its absence), are replaced with + * REPLACEMENT CHARACTER.) + * + * and return a pointer to it. + */ +char * +format_text(wmem_allocator_t *allocator, + const char *string, size_t len) +{ + return format_text_internal(allocator, string, len, false); +} + +/** Given a wmem scope and a null-terminated string, expected to be in + * UTF-8 but possibly containing invalid sequences (as it may have come + * from packet data), and the length of the string, generate a valid + * UTF-8 string from it, allocated in the specified wmem scope, that: + * + * shows printable Unicode characters as themselves; + * + * shows non-printable ASCII characters as C-style escapes (octal + * if not one of the standard ones such as LF -> '\n'); + * + * shows non-printable Unicode-but-not-ASCII characters as + * their universal character names; + * + * shows illegal UTF-8 sequences as a sequence of bytes represented + * as C-style hex escapes; + * + * and return a pointer to it. + */ +char * +format_text_string(wmem_allocator_t* allocator, const char *string) +{ + return format_text_internal(allocator, string, strlen(string), false); +} + +/* + * Given a string, generate a string from it that shows non-printable + * characters as C-style escapes except a whitespace character + * (space, tab, carriage return, new line, vertical tab, or formfeed) + * which will be replaced by a space, and return a pointer to it. + */ +char * +format_text_wsp(wmem_allocator_t* allocator, const char *string, size_t len) +{ + return format_text_internal(allocator, string, len, true); +} + +/* + * Given a string, generate a string from it that shows non-printable + * characters as the chr parameter passed, except a whitespace character + * (space, tab, carriage return, new line, vertical tab, or formfeed) + * which will be replaced by a space, and return a pointer to it. + * + * This does *not* treat the input string as UTF-8. + * + * This is useful for displaying binary data that frequently but not always + * contains text; otherwise the number of C escape codes makes it unreadable. + */ +char * +format_text_chr(wmem_allocator_t *allocator, const char *string, size_t len, char chr) +{ + wmem_strbuf_t *buf; + + buf = wmem_strbuf_new_sized(allocator, len + 1); + for (const char *p = string; p < string + len; p++) { + if (g_ascii_isprint(*p)) { + wmem_strbuf_append_c(buf, *p); + } + else if (g_ascii_isspace(*p)) { + wmem_strbuf_append_c(buf, ' '); + } + else { + wmem_strbuf_append_c(buf, chr); + } + } + return wmem_strbuf_finalize(buf); +} + +char * +format_char(wmem_allocator_t *allocator, char c) +{ + char *buf; + char r; + + if (g_ascii_isprint(c)) { + buf = wmem_alloc_array(allocator, char, 2); + buf[0] = c; + buf[1] = '\0'; + return buf; + } + if (escape_char(c, &r)) { + buf = wmem_alloc_array(allocator, char, 3); + buf[0] = '\\'; + buf[1] = r; + buf[2] = '\0'; + return buf; + } + buf = wmem_alloc_array(allocator, char, 5); + buf[0] = '\\'; + buf[1] = 'x'; + buf[2] = hex[((uint8_t)c >> 4) & 0xF]; + buf[3] = hex[((uint8_t)c >> 0) & 0xF]; + buf[4] = '\0'; + return buf; +} + +char* +ws_utf8_truncate(char *string, size_t len) +{ + char* last_char; + + /* Ensure that it is null terminated */ + string[len] = '\0'; + last_char = g_utf8_find_prev_char(string, string + len); + if (last_char != NULL && g_utf8_get_char_validated(last_char, -1) == (gunichar)-2) { + /* The last UTF-8 character was truncated into a partial sequence. */ + *last_char = '\0'; + } + return string; +} + +/* ASCII/EBCDIC conversion tables from + * https://web.archive.org/web/20060813174742/http://www.room42.com/store/computer_center/code_tables.shtml + */ +#if 0 +static const uint8_t ASCII_translate_EBCDIC [ 256 ] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + 0x40, 0x5A, 0x7F, 0x7B, 0x5B, 0x6C, 0x50, 0x7D, 0x4D, + 0x5D, 0x5C, 0x4E, 0x6B, 0x60, 0x4B, 0x61, + 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, + 0xF9, 0x7A, 0x5E, 0x4C, 0x7E, 0x6E, 0x6F, + 0x7C, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, + 0xC9, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, + 0xD7, 0xD8, 0xD9, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, + 0xE8, 0xE9, 0xAD, 0xE0, 0xBD, 0x5F, 0x6D, + 0x7D, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, + 0x89, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, + 0x97, 0x98, 0x99, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, + 0xA8, 0xA9, 0xC0, 0x6A, 0xD0, 0xA1, 0x4B, + 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, + 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, + 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, + 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, + 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, + 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, + 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, + 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, + 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, + 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, + 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, + 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, + 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, + 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, + 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, + 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B +}; + +void +ASCII_to_EBCDIC(uint8_t *buf, unsigned bytes) +{ + unsigned i; + uint8_t *bufptr; + + bufptr = buf; + + for (i = 0; i < bytes; i++, bufptr++) { + *bufptr = ASCII_translate_EBCDIC[*bufptr]; + } +} + +uint8_t +ASCII_to_EBCDIC1(uint8_t c) +{ + return ASCII_translate_EBCDIC[c]; +} +#endif + +static const uint8_t EBCDIC_translate_ASCII [ 256 ] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, + 0x2E, 0x2E, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x2E, 0x3F, + 0x20, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x3C, 0x28, 0x2B, 0x7C, + 0x26, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x21, 0x24, 0x2A, 0x29, 0x3B, 0x5E, + 0x2D, 0x2F, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x7C, 0x2C, 0x25, 0x5F, 0x3E, 0x3F, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x3A, 0x23, 0x40, 0x27, 0x3D, 0x22, + 0x2E, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, + 0x71, 0x72, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x7E, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x79, 0x7A, 0x2E, 0x2E, 0x2E, 0x5B, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x5D, 0x2E, 0x2E, + 0x7B, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x7D, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, + 0x51, 0x52, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x5C, 0x2E, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5A, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E +}; + +void +EBCDIC_to_ASCII(uint8_t *buf, unsigned bytes) +{ + unsigned i; + uint8_t *bufptr; + + bufptr = buf; + + for (i = 0; i < bytes; i++, bufptr++) { + *bufptr = EBCDIC_translate_ASCII[*bufptr]; + } +} + +uint8_t +EBCDIC_to_ASCII1(uint8_t c) +{ + return EBCDIC_translate_ASCII[c]; +} + +/* + * This routine is based on a routine created by Dan Lasley + * <DLASLEY@PROMUS.com>. + * + * It was modified for Wireshark by Gilbert Ramirez and others. + */ + +#define MAX_OFFSET_LEN 8 /* max length of hex offset of bytes */ +#define BYTES_PER_LINE 16 /* max byte values printed on a line */ +#define HEX_DUMP_LEN (BYTES_PER_LINE*3) + /* max number of characters hex dump takes - + 2 digits plus trailing blank */ +#define DATA_DUMP_LEN (HEX_DUMP_LEN + 2 + 2 + BYTES_PER_LINE) + /* number of characters those bytes take; + 3 characters per byte of hex dump, + 2 blanks separating hex from ASCII, + 2 optional ASCII dump delimiters, + 1 character per byte of ASCII dump */ +#define MAX_LINE_LEN (MAX_OFFSET_LEN + 2 + DATA_DUMP_LEN) + /* number of characters per line; + offset, 2 blanks separating offset + from data dump, data dump */ + +bool +hex_dump_buffer(bool (*print_line)(void *, const char *), void *fp, + const unsigned char *cp, unsigned length, + hex_dump_enc encoding, + unsigned ascii_option) +{ + register unsigned int ad, i, j, k, l; + unsigned char c; + char line[MAX_LINE_LEN + 1]; + unsigned int use_digits; + + static char binhex[16] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + /* + * How many of the leading digits of the offset will we supply? + * We always supply at least 4 digits, but if the maximum offset + * won't fit in 4 digits, we use as many digits as will be needed. + */ + if (((length - 1) & 0xF0000000) != 0) + use_digits = 8; /* need all 8 digits */ + else if (((length - 1) & 0x0F000000) != 0) + use_digits = 7; /* need 7 digits */ + else if (((length - 1) & 0x00F00000) != 0) + use_digits = 6; /* need 6 digits */ + else if (((length - 1) & 0x000F0000) != 0) + use_digits = 5; /* need 5 digits */ + else + use_digits = 4; /* we'll supply 4 digits */ + + ad = 0; + i = 0; + j = 0; + k = 0; + while (i < length) { + if ((i & 15) == 0) { + /* + * Start of a new line. + */ + j = 0; + l = use_digits; + do { + l--; + c = (ad >> (l*4)) & 0xF; + line[j++] = binhex[c]; + } while (l != 0); + line[j++] = ' '; + line[j++] = ' '; + memset(line+j, ' ', DATA_DUMP_LEN); + + /* + * Offset in line of ASCII dump. + */ + k = j + HEX_DUMP_LEN + 2; + if (ascii_option == HEXDUMP_ASCII_DELIMIT) + line[k++] = '|'; + } + c = *cp++; + line[j++] = binhex[c>>4]; + line[j++] = binhex[c&0xf]; + j++; + if (ascii_option != HEXDUMP_ASCII_EXCLUDE ) { + if (encoding == HEXDUMP_ENC_EBCDIC) { + c = EBCDIC_to_ASCII1(c); + } + line[k++] = ((c >= ' ') && (c < 0x7f)) ? c : '.'; + } + i++; + if (((i & 15) == 0) || (i == length)) { + /* + * We'll be starting a new line, or + * we're finished printing this buffer; + * dump out the line we've constructed, + * and advance the offset. + */ + if (ascii_option == HEXDUMP_ASCII_DELIMIT) + line[k++] = '|'; + line[k] = '\0'; + if (!print_line(fp, line)) + return false; + ad += 16; + } + } + return true; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/str_util.h b/wsutil/str_util.h new file mode 100644 index 00000000..7f1362f4 --- /dev/null +++ b/wsutil/str_util.h @@ -0,0 +1,392 @@ +/** @file + * String utility definitions + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __STR_UTIL_H__ +#define __STR_UTIL_H__ + +#include <wireshark.h> +#include <wsutil/wmem/wmem.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +WS_DLL_PUBLIC +char * +wmem_strconcat(wmem_allocator_t *allocator, const char *first, ...) +G_GNUC_MALLOC G_GNUC_NULL_TERMINATED; + +WS_DLL_PUBLIC +char * +wmem_strjoin(wmem_allocator_t *allocator, + const char *separator, const char *first, ...) +G_GNUC_MALLOC G_GNUC_NULL_TERMINATED; + +/** + * As g_strjoinv, with the returned string wmem allocated. + * Joins a number of strings together to form one long string, + * with the optional separator inserted between each of them. + * + * @param allocator The wmem scope to use to allocate the returned string + * @param separator A string to insert between each of the strings, or NULL. + * @param str_array A NULL-terminated array of strings to join + * + * @note If str_array has no items, the return value is an empty string. + * str_array should not be NULL (NULL is returned with an warning.) + * NULL as a separator is equivalent to the empty string. + */ +WS_DLL_PUBLIC +char * +wmem_strjoinv(wmem_allocator_t *allocator, + const char *separator, char **str_array) +G_GNUC_MALLOC; + +/** + * Splits a string into a maximum of max_tokens pieces, using the given + * delimiter. If max_tokens is reached, the remainder of string is appended + * to the last token. Successive tokens are not folded and will instead result + * in an empty string as element. + * + * If src or delimiter are NULL, or if delimiter is empty, this will return + * NULL. + * + * Do not use with a NULL allocator, use g_strsplit instead. + */ +WS_DLL_PUBLIC +char ** +wmem_strsplit(wmem_allocator_t *allocator, const char *src, + const char *delimiter, int max_tokens); + +/** + * wmem_ascii_strdown: + * Based on g_ascii_strdown + * @param allocator An enumeration of the different types of available allocators. + * @param str a string. + * @param len length of str in bytes, or -1 if str is nul-terminated. + * + * Converts all upper case ASCII letters to lower case ASCII letters. + * + * Return value: a newly-allocated string, with all the upper case + * characters in str converted to lower case, with + * semantics that exactly match g_ascii_tolower(). (Note + * that this is unlike the old g_strdown(), which modified + * the string in place.) + **/ +WS_DLL_PUBLIC +char* +wmem_ascii_strdown(wmem_allocator_t *allocator, const char *str, ssize_t len); + +/** Convert all upper-case ASCII letters to their ASCII lower-case + * equivalents, in place, with a simple non-locale-dependent + * ASCII mapping (A-Z -> a-z). + * All other characters are left unchanged, as the mapping to + * lower case may be locale-dependent. + * + * The string is assumed to be in a character encoding, such as + * an ISO 8859 or other EUC encoding, or UTF-8, in which all + * bytes in the range 0x00 through 0x7F are ASCII characters and + * non-ASCII characters are constructed from one or more bytes in + * the range 0x80 through 0xFF. + * + * @param str The string to be lower-cased. + * @return ptr to the string + */ +WS_DLL_PUBLIC +char *ascii_strdown_inplace(char *str); + +/** Convert all lower-case ASCII letters to their ASCII upper-case + * equivalents, in place, with a simple non-locale-dependent + * ASCII mapping (a-z -> A-Z). + * All other characters are left unchanged, as the mapping to + * lower case may be locale-dependent. + * + * The string is assumed to be in a character encoding, such as + * an ISO 8859 or other EUC encoding, or UTF-8, in which all + * bytes in the range 0x00 through 0x7F are ASCII characters and + * non-ASCII characters are constructed from one or more bytes in + * the range 0x80 through 0xFF. + * + * @param str The string to be upper-cased. + * @return ptr to the string + */ +WS_DLL_PUBLIC +char *ascii_strup_inplace(char *str); + +/** Check if an entire string consists of printable characters + * + * @param str The string to be checked + * @return true if the entire string is printable, otherwise false + */ +WS_DLL_PUBLIC +bool isprint_string(const char *str); + +/** Given a not-necessarily-null-terminated string, expected to be in + * UTF-8 but possibly containing invalid sequences (as it may have come + * from packet data), and the length of the string, deterimine if the + * string is valid UTF-8 consisting entirely of printable characters. + * + * This means that it: + * + * does not contain an illegal UTF-8 sequence (including overlong encodings, + * the sequences reserved for UTF-16 surrogate halves, and the values for + * code points above U+10FFFF that are no longer in Unicode) + * + * does not contain a non-printable Unicode character such as control + * characters (including internal NULL bytes) + * + * does not end in a partial sequence that could begin a valid character; + * + * does not start with a partial sequence that could end a valid character; + * + * and thus guarantees that the result of format_text() would be the same as + * that of wmem_strndup() with the same parameters. + * + * @param str The string to be checked + * @param length The number of bytes to validate + * @return true if the entire string is valid and printable UTF-8, + * otherwise false + */ +WS_DLL_PUBLIC +bool isprint_utf8_string(const char *str, const unsigned length); + +/** Check if an entire string consists of digits + * + * @param str The string to be checked + * @return true if the entire string is digits, otherwise false + */ +WS_DLL_PUBLIC +bool isdigit_string(const unsigned char *str); + +/** Finds the first occurrence of string 'needle' in string 'haystack'. + * The matching is done ignoring the case of ASCII characters in a + * non-locale-dependent way. + * + * The string is assumed to be in a character encoding, such as + * an ISO 8859 or other EUC encoding, or UTF-8, in which all + * bytes in the range 0x00 through 0x7F are ASCII characters and + * non-ASCII characters are constructed from one or more bytes in + * the range 0x80 through 0xFF. + * + * @param haystack The string possibly containing the substring + * @param needle The substring to be searched + * @return A pointer into 'haystack' where 'needle' is first found. + * Otherwise it returns NULL. + */ +WS_DLL_PUBLIC +const char *ws_ascii_strcasestr(const char *haystack, const char *needle); + +WS_DLL_PUBLIC +char *ws_escape_string(wmem_allocator_t *alloc, const char *string, bool add_quotes); + +WS_DLL_PUBLIC +char *ws_escape_string_len(wmem_allocator_t *alloc, const char *string, ssize_t len, bool add_quotes); + +/* Replace null bytes with "\0". */ +WS_DLL_PUBLIC +char *ws_escape_null(wmem_allocator_t *alloc, const char *string, size_t len, bool add_quotes); + +WS_DLL_PUBLIC +int ws_xton(char ch); + +typedef enum { + FORMAT_SIZE_UNIT_NONE, /**< No unit will be appended. You must supply your own. */ + FORMAT_SIZE_UNIT_BYTES, /**< "bytes" for un-prefixed sizes, "B" otherwise. */ + FORMAT_SIZE_UNIT_BITS, /**< "bits" for un-prefixed sizes, "b" otherwise. */ + FORMAT_SIZE_UNIT_BITS_S, /**< "bits/s" for un-prefixed sizes, "bps" otherwise. */ + FORMAT_SIZE_UNIT_BYTES_S, /**< "bytes/s" for un-prefixed sizes, "Bps" otherwise. */ + FORMAT_SIZE_UNIT_PACKETS, /**< "packets" */ + FORMAT_SIZE_UNIT_PACKETS_S, /**< "packets/s" */ +} format_size_units_e; + +#define FORMAT_SIZE_PREFIX_SI (1 << 0) /**< SI (power of 1000) prefixes will be used. */ +#define FORMAT_SIZE_PREFIX_IEC (1 << 1) /**< IEC (power of 1024) prefixes will be used. */ + +/** Given a size, return its value in a human-readable format + * + * Prefixes up to "T/Ti" (tera, tebi) are currently supported. + * + * @param size The size value + * @param flags Flags to control the output (unit of measurement, + * SI vs IEC, etc). Unit and prefix flags may be ORed together. + * @return A newly-allocated string representing the value. + */ +WS_DLL_PUBLIC +char *format_size_wmem(wmem_allocator_t *allocator, int64_t size, + format_size_units_e unit, uint16_t flags); + +#define format_size(size, unit, flags) \ + format_size_wmem(NULL, size, unit, flags) + +WS_DLL_PUBLIC +char printable_char_or_period(char c); + +WS_DLL_PUBLIC WS_RETNONNULL +const char *ws_strerrorname_r(int errnum, char *buf, size_t buf_size); + +WS_DLL_PUBLIC +char *ws_strdup_underline(wmem_allocator_t *allocator, long offset, size_t len); + +/** Given a wmem scope, a not-necessarily-null-terminated string, + * expected to be in UTF-8 but possibly containing invalid sequences + * (as it may have come from packet data), and the length of the string, + * generate a valid UTF-8 string from it, allocated in the specified + * wmem scope, that: + * + * shows printable Unicode characters as themselves; + * + * shows non-printable ASCII characters as C-style escapes (octal + * if not one of the standard ones such as LF -> '\n'); + * + * shows non-printable Unicode-but-not-ASCII characters as + * their universal character names; + * + * Replaces illegal UTF-8 sequences with U+FFFD (replacement character) ; + * + * and return a pointer to it. + * + * @param allocator The wmem scope + * @param string A pointer to the input string + * @param len The length of the input string + * @return A pointer to the formatted string + * + * @see tvb_format_text() + */ +WS_DLL_PUBLIC +char *format_text(wmem_allocator_t* allocator, const char *string, size_t len); + +/** Same as format_text() but accepts a nul-terminated string. + * + * @param allocator The wmem scope + * @param string A pointer to the input string + * @return A pointer to the formatted string + * + * @see tvb_format_text() + */ +WS_DLL_PUBLIC +char *format_text_string(wmem_allocator_t* allocator, const char *string); + +/** + * Same as format_text() but replaces any whitespace characters + * (space, tab, carriage return, new line, vertical tab, or formfeed) + * with a space. + * + * @param allocator The wmem scope + * @param line A pointer to the input string + * @param len The length of the input string + * @return A pointer to the formatted string + * + */ +WS_DLL_PUBLIC +char *format_text_wsp(wmem_allocator_t* allocator, const char *line, size_t len); + +/** + * Given a string, generate a string from it that shows non-printable + * characters as the chr parameter passed, except a whitespace character + * (space, tab, carriage return, new line, vertical tab, or formfeed) + * which will be replaced by a space, and return a pointer to it. + * + * This does *not* treat the input string as UTF-8. + * + * This is useful for displaying binary data that frequently but not always + * contains text; otherwise the number of C escape codes makes it unreadable. + * + * @param allocator The wmem scope + * @param string A pointer to the input string + * @param len The length of the input string + * @param chr The character to use to replace non-printable characters + * @return A pointer to the formatted string + * + */ +WS_DLL_PUBLIC +char *format_text_chr(wmem_allocator_t *allocator, + const char *string, size_t len, char chr); + +/** Given a wmem scope and an 8-bit character + * generate a valid UTF-8 string from it, allocated in the specified + * wmem scope, that: + * + * shows printable Unicode characters as themselves; + * + * shows non-printable ASCII characters as C-style escapes (hex + * if not one of the standard ones such as LF -> '\n'); + * + * and return a pointer to it. + * + * @param allocator The wmem scope + * @param c A character to format + * @return A pointer to the formatted string + */ +WS_DLL_PUBLIC +char *format_char(wmem_allocator_t *allocator, char c); + +/** + * Truncate a UTF-8 string in place so that it is no larger than len bytes, + * ensuring that the string is null terminated and ends with a complete + * character instead of a partial sequence (e.g., possibly truncating up + * to 3 additional bytes if the terminal character is 4 bytes long). + * + * The buffer holding the string must be large enough (at least len + 1 + * including the null terminator), and the first len bytes of the buffer + * must be a valid UTF-8 string, except for possibly ending in a partial + * sequence or not being null terminated. This is a convenience function + * that for speed does not check either of those conditions. + * + * A common use case is when a valid UTF-8 string has been copied into a + * buffer of length len+1 via snprintf, strlcpy, or strlcat and truncated, + * to ensure that the final UTF-8 character is not a partial sequence. + * + * @param string A pointer to the input string + * @param len The maximum length to truncate to + * @return ptr to the string + */ +WS_DLL_PUBLIC +char* ws_utf8_truncate(char *string, size_t len); + +WS_DLL_PUBLIC +void EBCDIC_to_ASCII(uint8_t *buf, unsigned bytes); + +WS_DLL_PUBLIC +uint8_t EBCDIC_to_ASCII1(uint8_t c); + +/* Types of character encodings */ +typedef enum { + HEXDUMP_ENC_ASCII = 0, /* ASCII */ + HEXDUMP_ENC_EBCDIC = 1 /* EBCDIC */ +} hex_dump_enc; + +/* + * Hexdump options for ASCII: + */ + +#define HEXDUMP_ASCII_MASK (0x0003U) +#define HEXDUMP_ASCII_OPTION(option) ((option) & HEXDUMP_ASCII_MASK) + +#define HEXDUMP_ASCII_INCLUDE (0x0000U) /* include ASCII section no delimiters (legacy tshark behavior) */ +#define HEXDUMP_ASCII_DELIMIT (0x0001U) /* include ASCII section with delimiters, useful for reliable detection of last hexdata */ +#define HEXDUMP_ASCII_EXCLUDE (0x0002U) /* exclude ASCII section from hexdump reports, if we really don't want or need it */ + +WS_DLL_PUBLIC +bool hex_dump_buffer(bool (*print_line)(void *, const char *), void *fp, + const unsigned char *cp, unsigned length, + hex_dump_enc encoding, + unsigned ascii_option); + +/* To pass one of two strings, singular or plural */ +#define plurality(d,s,p) ((d) == 1 ? (s) : (p)) + +#define true_or_false(val) ((val) ? "TRUE" : "FALSE") + +#define string_or_null(val) ((val) ? (val) : "[NULL]") + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __STR_UTIL_H__ */ diff --git a/wsutil/strnatcmp.c b/wsutil/strnatcmp.c new file mode 100644 index 00000000..6a9a6a5f --- /dev/null +++ b/wsutil/strnatcmp.c @@ -0,0 +1,189 @@ +/* strnatcmp.c + * + * Original code downloaded from: http://sourcefrog.net/projects/natsort/ + + strnatcmp.c -- Perform 'natural order' comparisons of strings in C. + Copyright (C) 2000, 2004 by Martin Pool <mbp sourcefrog net> + + SPDX-License-Identifier: Zlib + */ + + +/* partial change history: + * + * 2004-10-10 mbp: Lift out character type dependencies into macros. + * + * Eric Sosman pointed out that ctype functions take a parameter whose + * value must be that of an unsigned int, even on platforms that have + * negative chars in their default char type. + */ + +/* + * Modified 2014-10-29 to use the g_ascii_XXX() routines; this avoids + * locale-dependent behavior. The routine names were changed to + * ws_ascii_XXX() to reflect this. + */ + +#include "strnatcmp.h" + +#include <glib.h> + +/* These are defined as macros to make it easier to adapt this code to + * different characters types or comparison functions. */ +static int +nat_isdigit(nat_char a) +{ + return g_ascii_isdigit(a); +} + + +static int +nat_isspace(nat_char a) +{ + return g_ascii_isspace(a); +} + + +static nat_char +nat_toupper(nat_char a) +{ + return g_ascii_toupper(a); +} + + +static int +compare_right(nat_char const *a, nat_char const *b) +{ + int bias = 0; + + /* The longest run of digits wins. That aside, the greatest + value wins, but we can't know that it will until we've scanned + both numbers to know that they have the same magnitude, so we + remember it in BIAS. */ + for (;; a++, b++) { + if (!nat_isdigit(*a) && !nat_isdigit(*b)) + return bias; + else if (!nat_isdigit(*a)) + return -1; + else if (!nat_isdigit(*b)) + return +1; + else if (*a < *b) { + if (!bias) + bias = -1; + } else if (*a > *b) { + if (!bias) + bias = +1; + } else if (!*a && !*b) + return bias; + } + + return 0; +} + + +static int +compare_left(nat_char const *a, nat_char const *b) +{ + /* Compare two left-aligned numbers: the first to have a + different value wins. */ + for (;; a++, b++) { + if (!nat_isdigit(*a) && !nat_isdigit(*b)) + return 0; + else if (!nat_isdigit(*a)) + return -1; + else if (!nat_isdigit(*b)) + return +1; + else if (*a < *b) + return -1; + else if (*a > *b) + return +1; + } + + return 0; +} + + +static int strnatcmp0(nat_char const *a, nat_char const *b, int fold_case) +{ + int ai, bi; + nat_char ca, cb; + int fractional, result; + + if (!a || !b) { + if (!a && !b) + return 0; + if (!a) + return -1; + return +1; + } + ai = bi = 0; + while (1) { + ca = a[ai]; cb = b[bi]; + + /* skip over leading spaces or zeros */ + while (nat_isspace(ca)) + ca = a[++ai]; + + while (nat_isspace(cb)) + cb = b[++bi]; + + /* process run of digits */ + if (nat_isdigit(ca) && nat_isdigit(cb)) { + fractional = (ca == '0' || cb == '0'); + + if (fractional) { + if ((result = compare_left(a+ai, b+bi)) != 0) + return result; + } else { + if ((result = compare_right(a+ai, b+bi)) != 0) + return result; + } + } + + if (!ca && !cb) { + /* The strings compare the same. Perhaps the caller + will want to call strcmp to break the tie. */ + return 0; + } + + if (fold_case) { + ca = nat_toupper(ca); + cb = nat_toupper(cb); + } + + if (ca < cb) + return -1; + else if (ca > cb) + return +1; + + ++ai; ++bi; + } +} + + +int ws_ascii_strnatcmp(nat_char const *a, nat_char const *b) +{ + return strnatcmp0(a, b, 0); +} + + +/* Compare, recognizing numeric string and ignoring case. */ +int ws_ascii_strnatcasecmp(nat_char const *a, nat_char const *b) +{ + return strnatcmp0(a, b, 1); +} + + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ + diff --git a/wsutil/strnatcmp.h b/wsutil/strnatcmp.h new file mode 100644 index 00000000..0ebe1f6f --- /dev/null +++ b/wsutil/strnatcmp.h @@ -0,0 +1,33 @@ +/** @file + * + * Original code downloaded from: http://sourcefrog.net/projects/natsort/ + + strnatcmp.c -- Perform 'natural order' comparisons of strings in C. + Copyright (C) 2000, 2004 by Martin Pool <mbp sourcefrog net> + + SPDX-License-Identifier: Zlib + */ + +#ifndef STRNATCMP_H +#define STRNATCMP_H + +#include "ws_symbol_export.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* CUSTOMIZATION SECTION + * + * You can change this typedef, but must then also change the inline + * functions in strnatcmp.c */ +typedef char nat_char; + +WS_DLL_PUBLIC int ws_ascii_strnatcmp(nat_char const *a, nat_char const *b); +WS_DLL_PUBLIC int ws_ascii_strnatcasecmp(nat_char const *a, nat_char const *b); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* STRNATCMP_H */ diff --git a/wsutil/strtoi.c b/wsutil/strtoi.c new file mode 100644 index 00000000..b64763a5 --- /dev/null +++ b/wsutil/strtoi.c @@ -0,0 +1,306 @@ +/* strtoi.c + * Utilities to convert strings to integers + * + * Copyright 2016, Dario Lombardo + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" + +#include <errno.h> + +#include <glib.h> + +#include "strtoi.h" +#include <wsutil/ws_assert.h> + +bool ws_strtoi64(const char* str, const char** endptr, int64_t* cint) +{ + char* end; + int64_t val; + + ws_assert(cint); + + if (!str) { + errno = EINVAL; + return false; + } + + errno = 0; + val = g_ascii_strtoll(str, &end, 10); + if ((val == 0 && end == str) || (endptr == NULL && *end != '\0')) { + *cint = 0; + if (endptr != NULL) + *endptr = end; + errno = EINVAL; + return false; + } + if ((val == INT64_MAX || val == INT64_MIN) && errno == ERANGE) { + /* + * Return the value, so our caller knows whether to + * report the value as "too small" or "too large". + */ + *cint = val; + if (endptr != NULL) + *endptr = end; + /* errno is already set */ + return false; + } + if (endptr != NULL) + *endptr = end; + *cint = val; + return true; +} + +#define DEFINE_WS_STRTOI_BITS(bits) \ +bool ws_strtoi##bits(const char* str, const char** endptr, int##bits##_t* cint) \ +{ \ + int64_t val = 0; \ + if (!ws_strtoi64(str, endptr, &val)) { \ + /* \ + * For ERANGE, return either INT##bits##_MIN or \ + * INT##bits##_MAX so our caller knows whether \ + * to report the value as "too small" or "too \ + * large". \ + * \ + * For other errors, return 0, for parallelism \ + * with ws_strtoi64(). \ + */ \ + if (errno == ERANGE) { \ + if (val < 0) \ + *cint = INT##bits##_MIN; \ + else \ + *cint = INT##bits##_MAX; \ + } else \ + *cint = 0; \ + return false; \ + } \ + if (val < INT##bits##_MIN) { \ + /* \ + * Return INT##bits##_MIN so our caller knows whether to \ + * report the value as "too small" or "too large". \ + */ \ + *cint = INT##bits##_MIN; \ + errno = ERANGE; \ + return false; \ + } \ + if (val > INT##bits##_MAX) { \ + /* \ + * Return INT##bits##_MAX so our caller knows whether to \ + * report the value as "too small" or "too large". \ + */ \ + *cint = INT##bits##_MAX; \ + errno = ERANGE; \ + return false; \ + } \ + *cint = (int##bits##_t)val; \ + return true; \ +} + +DEFINE_WS_STRTOI_BITS(32) +DEFINE_WS_STRTOI_BITS(16) +DEFINE_WS_STRTOI_BITS(8) + +bool ws_strtoi(const char* str, const char** endptr, int* cint) +{ + int64_t val = 0; + if (!ws_strtoi64(str, endptr, &val)) { + /* + * For ERANGE, return either INT_MIN or + * INT_MAX so our caller knows whether + * to report the value as "too small" or "too + * large". + * + * For other errors, return 0, for parallelism + * with ws_strtoi64(). + */ + if (errno == ERANGE) { + if (val < 0) + *cint = INT_MIN; + else + *cint = INT_MAX; + } else + *cint = 0; + return false; + } + if (val < INT_MIN) { + /* + * Return INT_MIN so our caller knows whether to + * report the value as "too small" or "too large". + */ + *cint = INT_MIN; + errno = ERANGE; + return false; + } + if (val > INT_MAX) { + /* + * Return INT_MAX so our caller knows whether to + * report the value as "too small" or "too large". + */ + *cint = INT_MAX; + errno = ERANGE; + return false; + } + *cint = (int)val; + return true; +} + +bool ws_basestrtou64(const char* str, const char** endptr, uint64_t* cint, int base) +{ + char* end; + uint64_t val; + + ws_assert(cint); + + if (!str) { + errno = EINVAL; + return false; + } + + if (str[0] == '-' || str[0] == '+') { + /* + * Unsigned numbers don't have a sign. + */ + *cint = 0; + if (endptr != NULL) + *endptr = str; + errno = EINVAL; + return false; + } + errno = 0; + val = g_ascii_strtoull(str, &end, base); + if ((val == 0 && end == str) || (endptr == NULL && *end != '\0')) { + *cint = 0; + if (endptr != NULL) + *endptr = end; + errno = EINVAL; + return false; + } + if (val == UINT64_MAX && errno == ERANGE) { + /* + * Return the value, because ws_strtoi64() does. + */ + *cint = val; + if (endptr != NULL) + *endptr = end; + /* errno is already set */ + return false; + } + if (endptr != NULL) + *endptr = end; + *cint = val; + return true; +} + +bool ws_strtou64(const char* str, const char** endptr, uint64_t* cint) +{ + return ws_basestrtou64(str, endptr, cint, 10); +} + +bool ws_hexstrtou64(const char* str, const char** endptr, uint64_t* cint) +{ + return ws_basestrtou64(str, endptr, cint, 16); +} + +#define DEFINE_WS_STRTOU_BITS(bits) \ +bool ws_basestrtou##bits(const char* str, const char** endptr, uint##bits##_t* cint, int base) \ +{ \ + uint64_t val; \ + if (!ws_basestrtou64(str, endptr, &val, base)) { \ + /* \ + * For ERANGE, return UINT##bits##_MAX for parallelism \ + * with ws_strtoi##bits(). \ + * \ + * For other errors, return 0, for parallelism \ + * with ws_basestrtou64(). \ + */ \ + if (errno == ERANGE) \ + *cint = UINT##bits##_MAX; \ + else \ + *cint = 0; \ + return false; \ + } \ + if (val > UINT##bits##_MAX) { \ + /* \ + * Return UINT##bits##_MAX for parallelism with \ + * ws_strtoi##bits(). \ + */ \ + *cint = UINT##bits##_MAX; \ + errno = ERANGE; \ + return false; \ + } \ + *cint = (uint##bits##_t)val; \ + return true; \ +} \ +\ +bool ws_strtou##bits(const char* str, const char** endptr, uint##bits##_t* cint) \ +{ \ + return ws_basestrtou##bits(str, endptr, cint, 10); \ +} \ +\ +bool ws_hexstrtou##bits(const char* str, const char** endptr, uint##bits##_t* cint) \ +{ \ + return ws_basestrtou##bits(str, endptr, cint, 16); \ +} + +DEFINE_WS_STRTOU_BITS(32) +DEFINE_WS_STRTOU_BITS(16) +DEFINE_WS_STRTOU_BITS(8) + +bool ws_basestrtou(const char* str, const char** endptr, unsigned* cint, int base) +{ + uint64_t val; + if (!ws_basestrtou64(str, endptr, &val, base)) { + /* + * For ERANGE, return UINT_MAX for parallelism + * with ws_strtoi(). + * + * For other errors, return 0, for parallelism + * with ws_basestrtou64(). + */ + if (errno == ERANGE) + *cint = UINT_MAX; + else + *cint = 0; + return false; + } + if (val > UINT_MAX) { + /* + * Return UINT_MAX for parallelism with + * ws_strtoi(). + */ + *cint = UINT_MAX; + errno = ERANGE; + return false; + } + *cint = (unsigned)val; + return true; +} + +bool ws_strtou(const char* str, const char** endptr, unsigned* cint) +{ + return ws_basestrtou(str, endptr, cint, 10); +} +\ +bool ws_hexstrtou(const char* str, const char** endptr, unsigned* cint) +{ + return ws_basestrtou(str, endptr, cint, 16); +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: t + * End: + * + * vi: set shiftwidth=4 tabstop=8 noexpandtab: + * :indentSize=4:tabSize=8:noTabs=false: + */ diff --git a/wsutil/strtoi.h b/wsutil/strtoi.h new file mode 100644 index 00000000..04fe802e --- /dev/null +++ b/wsutil/strtoi.h @@ -0,0 +1,108 @@ +/** @file + * Utilities to convert strings to integers + * + * Copyright 2016, Dario Lombardo + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef _WS_STRTOI_H +#define _WS_STRTOI_H + +#include <stdbool.h> +#include <inttypes.h> + +#include "ws_symbol_export.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * \brief Convert a decimal string to a signed/unsigned int, with error checks. + * \param str The string to convert + * \param endptr A pointer that will store a pointer to the first invalid + * character in str, allowing a number to be parsed even if there is trailing + * whitespace. If NULL, then the string is assumed to contain only valid + * characters (or it will error out). + * \param cint The converted integer + * \return true if the conversion succeeds, false otherwise. + * On error, errno is set to EINVAL for unrecognized input and ERANGE + * if the resulting number does not fit in the type. + */ +WS_DLL_PUBLIC bool ws_strtoi64(const char* str, const char** endptr, int64_t* cint); +WS_DLL_PUBLIC bool ws_strtoi32(const char* str, const char** endptr, int32_t* cint); +WS_DLL_PUBLIC bool ws_strtoi16(const char* str, const char** endptr, int16_t* cint); +WS_DLL_PUBLIC bool ws_strtoi8 (const char* str, const char** endptr, int8_t* cint); +WS_DLL_PUBLIC bool ws_strtoi (const char* str, const char** endptr, int* cint); + +WS_DLL_PUBLIC bool ws_strtou64(const char* str, const char** endptr, uint64_t* cint); +WS_DLL_PUBLIC bool ws_strtou32(const char* str, const char** endptr, uint32_t* cint); +WS_DLL_PUBLIC bool ws_strtou16(const char* str, const char** endptr, uint16_t* cint); +WS_DLL_PUBLIC bool ws_strtou8 (const char* str, const char** endptr, uint8_t* cint); +WS_DLL_PUBLIC bool ws_strtou (const char* str, const char** endptr, unsigned* cint); + +/* + * \brief Convert a hexadecimal string to an unsigned int, with error checks. + * \param str The string to convert + * \param endptr A pointer that will store a pointer to the first invalid + * character in str, allowing a number to be parsed even if there is trailing + * whitespace. If NULL, then the string is assumed to contain only valid + * characters (or it will error out). + * \param cint The converted integer + * \return true if the conversion succeeds, false otherwise. + * On error, errno is set to EINVAL for unrecognized input and ERANGE + * if the resulting number does not fit in the type. + */ + +WS_DLL_PUBLIC bool ws_hexstrtou64(const char* str, const char** endptr, uint64_t* cint); +WS_DLL_PUBLIC bool ws_hexstrtou32(const char* str, const char** endptr, uint32_t* cint); +WS_DLL_PUBLIC bool ws_hexstrtou16(const char* str, const char** endptr, uint16_t* cint); +WS_DLL_PUBLIC bool ws_hexstrtou8 (const char* str, const char** endptr, uint8_t* cint); +WS_DLL_PUBLIC bool ws_hexstrtou (const char* str, const char** endptr, unsigned* cint); + +/* + * \brief Convert a string in the specified base to an unsigned int, with + * error checks. + * \param str The string to convert + * \param endptr A pointer that will store a pointer to the first invalid + * character in str, allowing a number to be parsed even if there is trailing + * whitespace. If NULL, then the string is assumed to contain only valid + * characters (or it will error out). + * \param cint The converted integer + * \param base The base for the integer; 0 means "if it begins with 0x, + * it's hex, otherwise if it begins with 0, it's octal, otherwise it's + * decimal". + * \return true if the conversion succeeds, false otherwise. + * On error, errno is set to EINVAL for unrecognized input and ERANGE + * if the resulting number does not fit in the type. + */ + +WS_DLL_PUBLIC bool ws_basestrtou64(const char* str, const char** endptr, uint64_t* cint, int base); +WS_DLL_PUBLIC bool ws_basestrtou32(const char* str, const char** endptr, uint32_t* cint, int base); +WS_DLL_PUBLIC bool ws_basestrtou16(const char* str, const char** endptr, uint16_t* cint, int base); +WS_DLL_PUBLIC bool ws_basestrtou8 (const char* str, const char** endptr, uint8_t* cint, int base); +WS_DLL_PUBLIC bool ws_basestrtou (const char* str, const char** endptr, unsigned* cint, int base); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: t + * End: + * + * vi: set shiftwidth=4 tabstop=8 noexpandtab: + * :indentSize=4:tabSize=8:noTabs=false: + */ diff --git a/wsutil/tempfile.c b/wsutil/tempfile.c new file mode 100644 index 00000000..531ed914 --- /dev/null +++ b/wsutil/tempfile.c @@ -0,0 +1,153 @@ +/* tempfile.c + * Routines to create temporary files + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" +#include "tempfile.h" + +#include <errno.h> + +#include "file_util.h" + +static char * +sanitize_prefix(const char *prefix) +{ + if (!prefix) { + return NULL; + } + + /* The characters in "delimiters" come from: + * https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions. + * Add to the list as necessary for other OS's. + */ + const char *delimiters = "<>:\"/\\|?*" + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a" + "\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14" + "\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"; + + /* Sanitize the prefix to resolve bug 7877 */ + char *safe_prefx = g_strdup(prefix); + safe_prefx = g_strdelimit(safe_prefx, delimiters, '-'); + return safe_prefx; +} + + /** + * Create a tempfile with the given prefix (e.g. "wireshark"). The path + * is created using g_file_open_tmp. + * + * @param tempdir [in] If not NULL, the directory in which to create the file. + * @param namebuf [in,out] If not NULL, receives the full path of the temp file. + * Must be freed. + * @param pfx [in] A prefix for the temporary file. + * @param sfx [in] A file extension for the temporary file. NULL can be passed + * if no file extension is needed + * @param err [out] Any error returned by g_file_open_tmp. May be NULL + * @return The file descriptor of the new tempfile, from mkstemps(). + */ +int +create_tempfile(const char *tempdir, char **namebuf, const char *pfx, const char *sfx, GError **err) +{ + int fd; + char *safe_pfx = sanitize_prefix(pfx); + + if (tempdir == NULL || tempdir[0] == '\0') { + /* Use OS default tempdir behaviour */ + char* filetmpl = ws_strdup_printf("%sXXXXXX%s", safe_pfx ? safe_pfx : "", sfx ? sfx : ""); + g_free(safe_pfx); + + fd = g_file_open_tmp(filetmpl, namebuf, err); + g_free(filetmpl); + } + else { + /* User-specified tempdir. + * We don't get libc's help generating a random name here. + */ + const char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-"; + const int32_t a_len = 64; + char* filetmpl = NULL; + + while(1) { + g_free(filetmpl); + filetmpl = ws_strdup_printf("%s%c%s%c%c%c%c%c%c%s", + tempdir, + G_DIR_SEPARATOR, + safe_pfx ? safe_pfx : "", + alphabet[g_random_int_range(0, a_len)], + alphabet[g_random_int_range(0, a_len)], + alphabet[g_random_int_range(0, a_len)], + alphabet[g_random_int_range(0, a_len)], + alphabet[g_random_int_range(0, a_len)], + alphabet[g_random_int_range(0, a_len)], + sfx ? sfx : ""); + + fd = ws_open(filetmpl, O_CREAT|O_EXCL|O_BINARY|O_WRONLY, 0600); + if (fd >= 0) { + break; + } + if (errno != EEXIST) { + g_set_error_literal(err, G_FILE_ERROR, + g_file_error_from_errno(errno), g_strerror(errno)); + g_free(filetmpl); + filetmpl = NULL; + break; + } + /* Loop continues if error was EEXIST, meaning the file we tried + * to make already existed at the destination + */ + } + + if (namebuf == NULL) { + g_free(filetmpl); + } + else { + *namebuf = filetmpl; + } + g_free(safe_pfx); + } + + return fd; +} + +char * +create_tempdir(const char *parent_dir, const char *tmpl, GError **err) +{ + if (parent_dir == NULL || parent_dir[0] == '\0') { + parent_dir = g_get_tmp_dir(); + } + + char *safe_pfx = sanitize_prefix(tmpl); + if (safe_pfx == NULL) { + safe_pfx = g_strdup("wireshark_XXXXXX"); + } + + char *temp_subdir = g_build_path(G_DIR_SEPARATOR_S, parent_dir, safe_pfx, NULL); + g_free(safe_pfx); + if (g_mkdtemp(temp_subdir) == NULL) + { + g_free(temp_subdir); + g_set_error_literal(err, G_FILE_ERROR, + g_file_error_from_errno(errno), g_strerror(errno)); + return false; + } + + return temp_subdir; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local Variables: + * c-basic-offset: 2 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=2 tabstop=8 expandtab: + * :indentSize=2:tabSize=8:noTabs=true: + */ diff --git a/wsutil/tempfile.h b/wsutil/tempfile.h new file mode 100644 index 00000000..328324f0 --- /dev/null +++ b/wsutil/tempfile.h @@ -0,0 +1,55 @@ +/* tempfile.h + * Declarations of routines to create temporary files + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __TEMPFILE_H__ +#define __TEMPFILE_H__ + +#include <wireshark.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** @file + * Convenience function for temporary file creation. + */ + +/** + * Create a tempfile with the given prefix (e.g. "wireshark"). The path + * is created using g_file_open_tmp. + * + * @param tempdir [in] If not NULL, the directory in which to create the file. + * @param namebuf [in,out] If not NULL, receives the full path of the temp file. + * Must be g_freed. + * @param pfx [in] A prefix for the temporary file. + * @param sfx [in] A file extension for the temporary file. NULL can be passed + * if no file extension is needed + * @param err [out] Any error returned by g_file_open_tmp. May be NULL. + * @return The file descriptor of the new tempfile, from mkstemps(). + */ +WS_DLL_PUBLIC int create_tempfile(const char *tempdir, char **namebuf, const char *pfx, const char *sfx, GError **err); + +/** + * Create a tempfile with the given parent directory (e.g. "/my/private/tmp"). The path + * is created using g_mkdtemp. + * + * @param parent_dir [in] If not NULL, the parent directory in which to create the subdirectory, + * otherwise the system temporary directory is used. + * @param tmpl [in] A template for the temporary directory. + * @param err [out] Any error returned by g_mkdtemp. May be NULL. + * @return The full path of the temporary directory or NULL on error. Must be g_freed. + */ +WS_DLL_PUBLIC char *create_tempdir(const char *parent_dir, const char *tmpl, GError **err); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __TEMPFILE_H__ */ diff --git a/wsutil/test_wsutil.c b/wsutil/test_wsutil.c new file mode 100644 index 00000000..ff9e2f82 --- /dev/null +++ b/wsutil/test_wsutil.c @@ -0,0 +1,881 @@ +/* + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" + +#include <stdio.h> +#include <glib.h> +#include <wsutil/utf8_entities.h> +#include <wsutil/time_util.h> + +#include "inet_addr.h" + +static void test_inet_pton4_test1(void) +{ + const char *str; + bool ok; + ws_in4_addr result, expect; + + str = "198.51.100.200"; + expect = g_htonl(3325256904); + ok = ws_inet_pton4(str, &result); + g_assert_true(ok); + g_assert_cmpint(result, ==, expect); +} + +static void test_inet_ntop4_test1(void) +{ + char result[WS_INET_ADDRSTRLEN]; + const char *expect, *ptr; + ws_in4_addr addr; + + addr = g_htonl(3325256904); + expect = "198.51.100.200"; + ptr = ws_inet_ntop4(&addr, result, sizeof(result)); + g_assert_true(ptr == result); + g_assert_cmpstr(result, ==, expect); +} + +struct in6_test { + char str[WS_INET6_ADDRSTRLEN]; + ws_in6_addr addr; +}; + +static struct in6_test in6_test1 = { + .str = "2001:db8:ffaa:ddbb:1199:2288:3377:1", + .addr = { { 0x20, 0x01, 0x0d, 0xb8, 0xff, 0xaa, 0xdd, 0xbb, + 0x11, 0x99, 0x22, 0x88, 0x33, 0x77, 0x00, 0x01 } } +}; + +static void test_inet_pton6_test1(void) +{ + bool ok; + ws_in6_addr result; + + ok = ws_inet_pton6(in6_test1.str, &result); + g_assert_true(ok); + g_assert_cmpmem(&result, sizeof(result), &in6_test1.addr, sizeof(in6_test1.addr)); +} + +static void test_inet_ntop6_test1(void) +{ + char result[WS_INET6_ADDRSTRLEN]; + const char *ptr; + + ptr = ws_inet_ntop6(&in6_test1.addr, result, sizeof(result)); + g_assert_true(ptr == result); + g_assert_cmpstr(result, ==, in6_test1.str); +} + +#include "str_util.h" + +static void test_format_size(void) +{ + char *str; + + str = format_size(10000, FORMAT_SIZE_UNIT_BYTES, FORMAT_SIZE_PREFIX_SI); + g_assert_cmpstr(str, ==, "10 kB"); + g_free(str); + + str = format_size(100000, FORMAT_SIZE_UNIT_BYTES, FORMAT_SIZE_PREFIX_IEC); + g_assert_cmpstr(str, ==, "97 KiB"); + g_free(str); + + str = format_size(20971520, FORMAT_SIZE_UNIT_BITS, FORMAT_SIZE_PREFIX_IEC); + g_assert_cmpstr(str, ==, "20 Mib"); + g_free(str); +} + +static void test_escape_string(void) +{ + char *buf; + + buf = ws_escape_string(NULL, "quoted \"\\\" backslash", true); + g_assert_cmpstr(buf, ==, "\"quoted \\\"\\\\\\\" backslash\""); + wmem_free(NULL, buf); + + buf = ws_escape_string(NULL, "whitespace \t \n \r \f \v", true); + g_assert_cmpstr(buf, ==, "\"whitespace \\t \\n \\r \\f \\v""\""); + wmem_free(NULL, buf); + + const char s1[] = { 'a', 'b', 'c', '\0', 'e', 'f', 'g'}; + buf = ws_escape_null(NULL, s1, sizeof(s1), true); + g_assert_cmpstr(buf, ==, "\"abc\\0efg\""); + wmem_free(NULL, buf); +} + +static void test_strconcat(void) +{ + wmem_allocator_t *allocator; + char *new_str; + + allocator = wmem_allocator_new(WMEM_ALLOCATOR_BLOCK); + + new_str = wmem_strconcat(allocator, "ABC", NULL); + g_assert_cmpstr(new_str, ==, "ABC"); + + new_str = wmem_strconcat(allocator, "ABC", "DEF", NULL); + g_assert_cmpstr(new_str, ==, "ABCDEF"); + + new_str = wmem_strconcat(allocator, "", "", "ABCDEF", "", "GH", NULL); + g_assert_cmpstr(new_str, ==, "ABCDEFGH"); + + wmem_destroy_allocator(allocator); +} + +static void test_strsplit(void) +{ + wmem_allocator_t *allocator; + char **split_str; + + allocator = wmem_allocator_new(WMEM_ALLOCATOR_BLOCK); + + split_str = wmem_strsplit(allocator, "A-C", "-", 2); + g_assert_cmpstr(split_str[0], ==, "A"); + g_assert_cmpstr(split_str[1], ==, "C"); + g_assert_null(split_str[2]); + + split_str = wmem_strsplit(allocator, "A-C", "-", 0); + g_assert_cmpstr(split_str[0], ==, "A"); + g_assert_cmpstr(split_str[1], ==, "C"); + g_assert_null(split_str[2]); + + split_str = wmem_strsplit(allocator, "--aslkf-asio--asfj-as--", "-", 10); + g_assert_cmpstr(split_str[0], ==, ""); + g_assert_cmpstr(split_str[1], ==, ""); + g_assert_cmpstr(split_str[2], ==, "aslkf"); + g_assert_cmpstr(split_str[3], ==, "asio"); + g_assert_cmpstr(split_str[4], ==, ""); + g_assert_cmpstr(split_str[5], ==, "asfj"); + g_assert_cmpstr(split_str[6], ==, "as"); + g_assert_cmpstr(split_str[7], ==, ""); + g_assert_cmpstr(split_str[8], ==, ""); + g_assert_null(split_str[9]); + + split_str = wmem_strsplit(allocator, "--aslkf-asio--asfj-as--", "-", 5); + g_assert_cmpstr(split_str[0], ==, ""); + g_assert_cmpstr(split_str[1], ==, ""); + g_assert_cmpstr(split_str[2], ==, "aslkf"); + g_assert_cmpstr(split_str[3], ==, "asio"); + g_assert_cmpstr(split_str[4], ==, "-asfj-as--"); + g_assert_null(split_str[5]); + + split_str = wmem_strsplit(allocator, "", "-", -1); + g_assert_null(split_str[0]); + + wmem_destroy_allocator(allocator); +} + +static void test_str_ascii(void) +{ + wmem_allocator_t *allocator; + const char *orig_str; + char *new_str; + + allocator = wmem_allocator_new(WMEM_ALLOCATOR_BLOCK); + + orig_str = "TeStAsCiIsTrDoWn"; + new_str = wmem_ascii_strdown(allocator, orig_str, -1); + g_assert_cmpstr(new_str, ==, "testasciistrdown"); + + wmem_destroy_allocator(allocator); +} + +static void test_format_text(void) +{ + const char *have, *want; + char *res; + + /* ASCII */ + have = "abcdef"; + want = "abcdef"; + res = format_text_string(NULL, have); + g_assert_cmpstr(res, ==, want); + g_free(res); + + /* ASCII with special escape characters. */ + have = "abc\td\fe\nf"; + want = "abc\\td\\fe\\nf"; + res = format_text_string(NULL, have); + g_assert_cmpstr(res, ==, want); + g_free(res); + + /* ASCII with non-printable characters. */ + have = "abc \004 def"; + want = "abc \\004 def"; + res = format_text_string(NULL, have); + g_assert_cmpstr(res, ==, want); + g_free(res); + + /* UTF-8 */ + have = u8"Γαζέες καὶ μυρτιὲς δὲν θὰ βρῶ πιὰ στὸ χρυσαφὶ ξέφωτο"; + want = u8"Γαζέες καὶ μυρτιὲς δὲν θὰ βρῶ πιὰ στὸ χρυσαφὶ ξέφωτο"; + res = format_text_string(NULL, have); + g_assert_cmpstr(res, ==, want); + g_free(res); + + /* UTF-8 with non-ASCII non-printable characters. */ + have = u8"String with BOM \ufeff"; + want = u8"String with BOM \\uFEFF"; + res = format_text_string(NULL, have); + g_assert_cmpstr(res, ==, want); + g_free(res); + +} + +#define RESOURCE_USAGE_START get_resource_usage(&start_utime, &start_stime) + +#define RESOURCE_USAGE_END \ + get_resource_usage(&end_utime, &end_stime); \ + utime_ms = (end_utime - start_utime) * 1000.0; \ + stime_ms = (end_stime - start_stime) * 1000.0 + +static void test_format_text_perf(void) +{ +#define LOOP_COUNT (1 * 1000 * 1000) + char *str; + int i; + double start_utime, start_stime, end_utime, end_stime, utime_ms, stime_ms; + + const char *text = "The quick brown fox\tjumps over the lazy \001dog"UTF8_HORIZONTAL_ELLIPSIS"\n"; + + RESOURCE_USAGE_START; + for (i = 0; i < LOOP_COUNT; i++) { + str = format_text_string(NULL, text); + g_free(str); + } + RESOURCE_USAGE_END; + g_test_minimized_result(utime_ms + stime_ms, + "format_text_string(): u %.3f ms s %.3f ms", utime_ms, stime_ms); +} + +#include "to_str.h" + +static void test_word_to_hex(void) +{ + static char buf[32]; + char *str; /* String is not NULL terminated. */ + + str = guint8_to_hex(buf, 0x34); + g_assert_true(str == buf + 2); + g_assert_cmpint(str[-1], ==, '4'); + g_assert_cmpint(str[-2], ==, '3'); + + str = word_to_hex(buf, 0x1234); + g_assert_true(str == buf + 4); + g_assert_cmpint(str[-1], ==, '4'); + g_assert_cmpint(str[-2], ==, '3'); + g_assert_cmpint(str[-3], ==, '2'); + g_assert_cmpint(str[-4], ==, '1'); + + str = dword_to_hex(buf, 0x1234); + g_assert_true(str == buf + 8); + g_assert_cmpint(str[-1], ==, '4'); + g_assert_cmpint(str[-2], ==, '3'); + g_assert_cmpint(str[-3], ==, '2'); + g_assert_cmpint(str[-4], ==, '1'); + g_assert_cmpint(str[-5], ==, '0'); + g_assert_cmpint(str[-6], ==, '0'); + g_assert_cmpint(str[-7], ==, '0'); + g_assert_cmpint(str[-8], ==, '0'); + + str = qword_to_hex(buf, G_GUINT64_CONSTANT(0xFEDCBA987654321)); + g_assert_true(str == buf + 16); + g_assert_cmpint(str[-1], ==, '1'); + g_assert_cmpint(str[-2], ==, '2'); + g_assert_cmpint(str[-3], ==, '3'); + g_assert_cmpint(str[-4], ==, '4'); + g_assert_cmpint(str[-5], ==, '5'); + g_assert_cmpint(str[-6], ==, '6'); + g_assert_cmpint(str[-7], ==, '7'); + g_assert_cmpint(str[-8], ==, '8'); + g_assert_cmpint(str[-9], ==, '9'); + g_assert_cmpint(str[-10], ==, 'a'); + g_assert_cmpint(str[-11], ==, 'b'); + g_assert_cmpint(str[-12], ==, 'c'); + g_assert_cmpint(str[-13], ==, 'd'); + g_assert_cmpint(str[-14], ==, 'e'); + g_assert_cmpint(str[-15], ==, 'f'); + g_assert_cmpint(str[-16], ==, '0'); +} + +static void test_bytes_to_str(void) +{ + char *str; + + const uint8_t buf[] = { 1, 2, 3}; + + str = bytes_to_str(NULL, buf, sizeof(buf)); + g_assert_cmpstr(str, ==, "010203"); + g_free(str); +} + +static void test_bytes_to_str_punct(void) +{ + char *str; + + const uint8_t buf[] = { 1, 2, 3}; + + str = bytes_to_str_punct(NULL, buf, sizeof(buf), ':'); + g_assert_cmpstr(str, ==, "01:02:03"); + g_free(str); +} + +static void test_bytes_to_str_punct_maxlen(void) +{ + char *str; + + const uint8_t buf[] = { 1, 2, 3}; + + str = bytes_to_str_punct_maxlen(NULL, buf, sizeof(buf), ':', 4); + g_assert_cmpstr(str, ==, "01:02:03"); + g_free(str); + + str = bytes_to_str_punct_maxlen(NULL, buf, sizeof(buf), ':', 3); + g_assert_cmpstr(str, ==, "01:02:03"); + g_free(str); + + str = bytes_to_str_punct_maxlen(NULL, buf, sizeof(buf), ':', 2); + g_assert_cmpstr(str, ==, "01:02:" UTF8_HORIZONTAL_ELLIPSIS); + g_free(str); + + str = bytes_to_str_punct_maxlen(NULL, buf, sizeof(buf), ':', 1); + g_assert_cmpstr(str, ==, "01:" UTF8_HORIZONTAL_ELLIPSIS); + g_free(str); + + str = bytes_to_str_punct_maxlen(NULL, buf, sizeof(buf), ':', 0); + g_assert_cmpstr(str, ==, "01:02:03"); + g_free(str); +} + +static void test_bytes_to_str_maxlen(void) +{ + char *str; + + const uint8_t buf[] = { 1, 2, 3}; + + str = bytes_to_str_maxlen(NULL, buf, sizeof(buf), 4); + g_assert_cmpstr(str, ==, "010203"); + g_free(str); + + str = bytes_to_str_maxlen(NULL, buf, sizeof(buf), 3); + g_assert_cmpstr(str, ==, "010203"); + g_free(str); + + str = bytes_to_str_maxlen(NULL, buf, sizeof(buf), 2); + g_assert_cmpstr(str, ==, "0102" UTF8_HORIZONTAL_ELLIPSIS); + g_free(str); + + str = bytes_to_str_maxlen(NULL, buf, sizeof(buf), 1); + g_assert_cmpstr(str, ==, "01" UTF8_HORIZONTAL_ELLIPSIS); + g_free(str); + + str = bytes_to_str_maxlen(NULL, buf, sizeof(buf), 0); + g_assert_cmpstr(str, ==, "010203"); + g_free(str); +} + +static void test_bytes_to_string_trunc1(void) +{ + char *str; + + const uint8_t buf[] = { + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA + }; + const char *expect = + "112233445566778899aa" + "112233445566778899aa" + "112233445566778899aa" + "112233445566" UTF8_HORIZONTAL_ELLIPSIS; + + str = bytes_to_str(NULL, buf, sizeof(buf)); + g_assert_cmpstr(str, ==, expect); + g_free(str); +} + +static void test_bytes_to_string_punct_trunc1(void) +{ + char *str; + + const uint8_t buf[] = { + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA + }; + const char *expect = + "11:22:33:44:55:66:77:88:99:aa:" + "11:22:33:44:55:66:77:88:99:aa:" + "11:22:33:44:" UTF8_HORIZONTAL_ELLIPSIS; + + str = bytes_to_str_punct(NULL, buf, sizeof(buf), ':'); + g_assert_cmpstr(str, ==, expect); + g_free(str); +} + +static char to_str_back_buf[32]; +#define BACK_PTR (&to_str_back_buf[31]) /* pointer to NUL string terminator */ + +static void test_oct_to_str_back(void) +{ + char *str; + + str = oct_to_str_back(BACK_PTR, 958769886); + g_assert_cmpstr(str, ==, "07111325336"); + + str = oct_to_str_back(BACK_PTR, 781499127); + g_assert_cmpstr(str, ==, "05645135367"); + + str = oct_to_str_back(BACK_PTR, 1177329882); + g_assert_cmpstr(str, ==, "010613120332"); +} + +static void test_oct64_to_str_back(void) +{ + char *str; + + str = oct64_to_str_back(BACK_PTR, G_GUINT64_CONSTANT(13873797580070999420)); + g_assert_cmpstr(str, ==, "01402115026217563452574"); + + str = oct64_to_str_back(BACK_PTR, G_GUINT64_CONSTANT(7072159458371400691)); + g_assert_cmpstr(str, ==, "0610452670726711271763"); + + str = oct64_to_str_back(BACK_PTR, G_GUINT64_CONSTANT(12453513102400590374)); + g_assert_cmpstr(str, ==, "01263236102754220511046"); +} + +static void test_hex_to_str_back_len(void) +{ + char *str; + + str = hex_to_str_back_len(BACK_PTR, 2481, 8); + g_assert_cmpstr(str, ==, "0x000009b1"); + + str = hex_to_str_back_len(BACK_PTR, 2457, 8); + g_assert_cmpstr(str, ==, "0x00000999"); + + str = hex_to_str_back_len(BACK_PTR, 16230, 8); + g_assert_cmpstr(str, ==, "0x00003f66"); +} + +static void test_hex64_to_str_back_len(void) +{ + char *str; + + str = hex64_to_str_back_len(BACK_PTR, G_GUINT64_CONSTANT(1), 16); + g_assert_cmpstr(str, ==, "0x0000000000000001"); + + str = hex64_to_str_back_len(BACK_PTR, G_GUINT64_CONSTANT(4294967295), 16); + g_assert_cmpstr(str, ==, "0x00000000ffffffff"); + + str = hex64_to_str_back_len(BACK_PTR, G_GUINT64_CONSTANT(18446744073709551615), 16); + g_assert_cmpstr(str, ==, "0xffffffffffffffff"); +} + +static void test_uint_to_str_back(void) +{ + char *str; + + str = uint_to_str_back(BACK_PTR, 873735883); + g_assert_cmpstr(str, ==, "873735883"); + + str = uint_to_str_back(BACK_PTR, 1801148094); + g_assert_cmpstr(str, ==, "1801148094"); + + str = uint_to_str_back(BACK_PTR, 181787997); + g_assert_cmpstr(str, ==, "181787997"); +} + +static void test_uint64_to_str_back(void) +{ + char *str; + + str = uint64_to_str_back(BACK_PTR, G_GUINT64_CONSTANT(585143757104211265)); + g_assert_cmpstr(str, ==, "585143757104211265"); + + str = uint64_to_str_back(BACK_PTR, G_GUINT64_CONSTANT(7191580247919484847)); + g_assert_cmpstr(str, ==, "7191580247919484847"); + + str = uint64_to_str_back(BACK_PTR, G_GUINT64_CONSTANT(95778573911934485)); + g_assert_cmpstr(str, ==, "95778573911934485"); +} + +static void test_uint_to_str_back_len(void) +{ + char *str; + + str = uint_to_str_back_len(BACK_PTR, 26630, 8); + g_assert_cmpstr(str, ==, "00026630"); + + str = uint_to_str_back_len(BACK_PTR, 25313, 8); + g_assert_cmpstr(str, ==, "00025313"); + + str = uint_to_str_back_len(BACK_PTR, 18750000, 8); + g_assert_cmpstr(str, ==, "18750000"); +} + +static void test_uint64_to_str_back_len(void) +{ + char *str; + + str = uint64_to_str_back_len(BACK_PTR, G_GUINT64_CONSTANT(1), 16); + g_assert_cmpstr(str, ==, "0000000000000001"); + + str = uint64_to_str_back_len(BACK_PTR, G_GUINT64_CONSTANT(4294967295), 16); + g_assert_cmpstr(str, ==, "0000004294967295"); + + str = uint64_to_str_back_len(BACK_PTR, G_GUINT64_CONSTANT(18446744073709551615), 16); + g_assert_cmpstr(str, ==, "18446744073709551615"); +} + +static void test_int_to_str_back(void) +{ + char *str; + + str = int_to_str_back(BACK_PTR, -763689611); + g_assert_cmpstr(str, ==, "-763689611"); + + str = int_to_str_back(BACK_PTR, -296015954); + g_assert_cmpstr(str, ==, "-296015954"); + + str = int_to_str_back(BACK_PTR, 898901469); + g_assert_cmpstr(str, ==, "898901469"); +} + +static void test_int64_to_str_back(void) +{ + char *str; + + str = int64_to_str_back(BACK_PTR, G_GINT64_CONSTANT(-9223372036854775807)); + g_assert_cmpstr(str, ==, "-9223372036854775807"); + + str = int64_to_str_back(BACK_PTR, G_GINT64_CONSTANT(1)); + g_assert_cmpstr(str, ==, "1"); + + str = int64_to_str_back(BACK_PTR, G_GINT64_CONSTANT(9223372036854775807)); + g_assert_cmpstr(str, ==, "9223372036854775807"); +} + +#include "nstime.h" +#include "time_util.h" + +void test_nstime_from_iso8601(void) +{ + char *str; + const char *endp; + nstime_t result, expect; + struct tm tm1; + + memset(&tm1, 0, sizeof(tm1)); + tm1.tm_sec = 25; + tm1.tm_min = 45; + tm1.tm_hour = 23; + tm1.tm_mday = 30; + tm1.tm_mon = 4; /* starts at zero */ + tm1.tm_year = 2013 - 1900; + tm1.tm_isdst = -1; + + /* Date and time with local time. */ + str = "2013-05-30T23:45:25.349124"; + expect.secs = mktime(&tm1); + expect.nsecs = 349124 * 1000; + endp = iso8601_to_nstime(&result, str, ISO8601_DATETIME_AUTO); + g_assert_nonnull(endp); + g_assert(*endp == '\0'); + g_assert_cmpint(result.secs, ==, expect.secs); + g_assert_cmpint(result.nsecs, ==, expect.nsecs); + + /* Date and time with UTC timezone. */ + str = "2013-05-30T23:45:25.349124Z"; + expect.secs = mktime_utc(&tm1); + expect.nsecs = 349124 * 1000; + endp = iso8601_to_nstime(&result, str, ISO8601_DATETIME_AUTO); + g_assert_nonnull(endp); + g_assert(*endp == '\0'); + g_assert_cmpint(result.secs, ==, expect.secs); + g_assert_cmpint(result.nsecs, ==, expect.nsecs); + + /* Date and time with timezone offset with separator. */ + str = "2013-05-30T23:45:25.349124+01:00"; + expect.secs = mktime_utc(&tm1) - 1 * 60 * 60; + expect.nsecs = 349124 * 1000; + endp = iso8601_to_nstime(&result, str, ISO8601_DATETIME_AUTO); + g_assert_nonnull(endp); + g_assert(*endp == '\0'); + g_assert_cmpint(result.secs, ==, expect.secs); + g_assert_cmpint(result.nsecs, ==, expect.nsecs); + + /* Date and time with timezone offset without separator. */ + str = "2013-05-30T23:45:25.349124+0100"; + expect.secs = mktime_utc(&tm1) - 1 * 60 * 60; + expect.nsecs = 349124 * 1000; + endp = iso8601_to_nstime(&result, str, ISO8601_DATETIME_AUTO); + g_assert_nonnull(endp); + g_assert(*endp == '\0'); + g_assert_cmpint(result.secs, ==, expect.secs); + g_assert_cmpint(result.nsecs, ==, expect.nsecs); + + /* Date and time with timezone offset with hours only. */ + str = "2013-05-30T23:45:25.349124+01"; + expect.secs = mktime_utc(&tm1) - 1 * 60 * 60; + expect.nsecs = 349124 * 1000; + endp = iso8601_to_nstime(&result, str, ISO8601_DATETIME_AUTO); + g_assert_nonnull(endp); + g_assert(*endp == '\0'); + g_assert_cmpint(result.secs, ==, expect.secs); + g_assert_cmpint(result.nsecs, ==, expect.nsecs); +} + +#include "ws_getopt.h" + +#define ARGV_MAX 31 + +static char **new_argv(int *argc_ptr, const char *args, ...) +{ + char **argv; + int argc = 0; + va_list ap; + + argv = g_malloc((ARGV_MAX + 1) * sizeof(char *)); + + va_start(ap, args); + while (args != NULL) { + /* Increase ARGV_MAX or use a dynamic size if this assertion fails. */ + g_assert_true(argc < ARGV_MAX); + argv[argc++] = g_strdup(args); + args = va_arg(ap, const char *); + } + argv[argc] = NULL; + va_end(ap); + + *argc_ptr = argc; + return argv; +} + +static void free_argv(char **argv) +{ + for (char **p = argv; *p != NULL; p++) { + g_free(*p); + } + g_free(argv); +} + +static void test_getopt_long_basic1(void) +{ + char **argv; + int argc; + + const char *optstring = "ab:c"; + argv = new_argv(&argc, "/bin/ls", "-a", "-b", "arg1", "-c", "path", (char *)NULL); + + ws_optind = 1; + int opt; + + opt = ws_getopt_long(argc, argv, optstring, NULL, NULL); + g_assert_cmpint(opt, ==, 'a'); + g_assert_null(ws_optarg); + + opt = ws_getopt_long(argc, argv, optstring, NULL, NULL); + g_assert_cmpint(opt, ==, 'b'); + g_assert_cmpstr(ws_optarg, ==, "arg1"); + + opt = ws_getopt_long(argc, argv, optstring, NULL, NULL); + g_assert_cmpint(opt, ==, 'c'); + g_assert_null(ws_optarg); + + opt = ws_getopt_long(argc, argv, optstring, NULL, NULL); + g_assert_cmpint(opt, ==, -1); + + free_argv(argv); +} + +static void test_getopt_long_basic2(void) +{ + char **argv; + int argc; + + struct ws_option longopts[] = { + { "opt1", ws_no_argument, NULL, '1' }, + { "opt2", ws_required_argument, NULL, '2' }, + { "opt3", ws_required_argument, NULL, '3' }, + { 0, 0, 0, 0 } + }; + argv = new_argv(&argc, "/bin/ls", "--opt1", "--opt2", "arg1", "--opt3=arg2", "path", (char *)NULL); + + ws_optind = 1; + int opt; + + opt = ws_getopt_long(argc, argv, "", longopts, NULL); + g_assert_cmpint(opt, ==, '1'); + g_assert_null(ws_optarg); + + opt = ws_getopt_long(argc, argv, "", longopts, NULL); + g_assert_cmpint(opt, ==, '2'); + g_assert_cmpstr(ws_optarg, ==, "arg1"); + + opt = ws_getopt_long(argc, argv, "", longopts, NULL); + g_assert_cmpint(opt, ==, '3'); + g_assert_cmpstr(ws_optarg, ==, "arg2"); + + opt = ws_getopt_long(argc, argv, "", longopts, NULL); + g_assert_cmpint(opt, ==, -1); + + free_argv(argv); +} + +static void test_getopt_optional_argument1(void) +{ + char **argv; + int argc; + int opt; + + struct ws_option longopts_optional[] = { + { "optional", ws_optional_argument, NULL, '1' }, + { 0, 0, 0, 0 } + }; + + argv = new_argv(&argc, "/bin/ls", "--optional=arg1", (char *)NULL); + + ws_optreset = 1; + opt = ws_getopt_long(argc, argv, "", longopts_optional, NULL); + g_assert_cmpint(opt, ==, '1'); + g_assert_cmpstr(ws_optarg, ==, "arg1"); + + free_argv(argv); + argv = new_argv(&argc, "/bin/ls", "--optional", "arg1", (char *)NULL); + + ws_optreset = 1; + opt = ws_getopt_long(argc, argv, "", longopts_optional, NULL); + g_assert_cmpint(opt, ==, '1'); + /* Optional argument does not recognize the form "--arg param" (it's ambiguous). */ + g_assert_null(ws_optarg); + + free_argv(argv); + argv = new_argv(&argc, "/bin/ls", "--optional", (char *)NULL); + + ws_optreset = 1; + opt = ws_getopt_long(argc, argv, "", longopts_optional, NULL); + g_assert_cmpint(opt, ==, '1'); + g_assert_null(ws_optarg); + + free_argv(argv); +} + +static void test_getopt_opterr1(void) +{ + char **argv; + int argc; + +#ifdef _WIN32 + g_test_skip("Not supported on Windows"); + return; +#endif + + if (g_test_subprocess()) { + const char *optstring = "ab"; + argv = new_argv(&argc, "/bin/ls", "-a", "-z", "path", (char *)NULL); + + ws_optind = 0; + ws_opterr = 1; + int opt; + + opt = ws_getopt_long(argc, argv, optstring, NULL, NULL); + g_assert_cmpint(opt, ==, 'a'); + + opt = ws_getopt_long(argc, argv, optstring, NULL, NULL); + g_assert_cmpint(opt, ==, '?'); + g_assert_cmpint(ws_optopt, ==, 'z'); + + opt = ws_getopt_long(argc, argv, optstring, NULL, NULL); + g_assert_cmpint(opt, ==, -1); + + free_argv(argv); + + return; + } + + g_test_trap_subprocess(NULL, 0, 0); + g_test_trap_assert_passed(); + g_test_trap_assert_stderr("/bin/ls: unrecognized option: z\n"); +} + +int main(int argc, char **argv) +{ + int ret; + + ws_log_init("test_wsutil", NULL); + + g_test_init(&argc, &argv, NULL); + + g_test_add_func("/inet_addr/inet_pton4", test_inet_pton4_test1); + g_test_add_func("/inet_addr/inet_ntop4", test_inet_ntop4_test1); + g_test_add_func("/inet_addr/inet_pton6", test_inet_pton6_test1); + g_test_add_func("/inet_addr/inet_ntop6", test_inet_ntop6_test1); + + g_test_add_func("/str_util/format_size", test_format_size); + g_test_add_func("/str_util/escape_string", test_escape_string); + g_test_add_func("/str_util/strconcat", test_strconcat); + g_test_add_func("/str_util/strsplit", test_strsplit); + g_test_add_func("/str_util/str_ascii", test_str_ascii); + g_test_add_func("/str_util/format_text", test_format_text); + + if (g_test_perf()) { + g_test_add_func("/str_util/format_text_perf", test_format_text_perf); + } + + g_test_add_func("/to_str/word_to_hex", test_word_to_hex); + g_test_add_func("/to_str/bytes_to_str", test_bytes_to_str); + g_test_add_func("/to_str/bytes_to_str_punct", test_bytes_to_str_punct); + g_test_add_func("/to_str/bytes_to_str_maxlen", test_bytes_to_str_maxlen); + g_test_add_func("/to_str/bytes_to_str_punct_maxlen", test_bytes_to_str_punct_maxlen); + g_test_add_func("/to_str/bytes_to_str_trunc1", test_bytes_to_string_trunc1); + g_test_add_func("/to_str/bytes_to_str_punct_trunc1", test_bytes_to_string_punct_trunc1); + g_test_add_func("/to_str/oct_to_str_back", test_oct_to_str_back); + g_test_add_func("/to_str/oct64_to_str_back", test_oct64_to_str_back); + g_test_add_func("/to_str/hex_to_str_back_len", test_hex_to_str_back_len); + g_test_add_func("/to_str/hex64_to_str_back_len", test_hex64_to_str_back_len); + g_test_add_func("/to_str/uint_to_str_back", test_uint_to_str_back); + g_test_add_func("/to_str/uint64_to_str_back", test_uint64_to_str_back); + g_test_add_func("/to_str/uint_to_str_back_len", test_uint_to_str_back_len); + g_test_add_func("/to_str/uint64_to_str_back_len", test_uint64_to_str_back_len); + g_test_add_func("/to_str/int_to_str_back", test_int_to_str_back); + g_test_add_func("/to_str/int64_to_str_back", test_int64_to_str_back); + + g_test_add_func("/nstime/from_iso8601", test_nstime_from_iso8601); + + g_test_add_func("/ws_getopt/basic1", test_getopt_long_basic1); + g_test_add_func("/ws_getopt/basic2", test_getopt_long_basic2); + g_test_add_func("/ws_getopt/optional1", test_getopt_optional_argument1); + g_test_add_func("/ws_getopt/opterr1", test_getopt_opterr1); + + ret = g_test_run(); + + return ret; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/time_util.c b/wsutil/time_util.c new file mode 100644 index 00000000..6d967953 --- /dev/null +++ b/wsutil/time_util.c @@ -0,0 +1,339 @@ +/* time_util.c + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" +#define WS_LOG_DOMAIN LOG_DOMAIN_WSUTIL +#include "time_util.h" + +#include <errno.h> + +#include <wsutil/epochs.h> + +#ifndef _WIN32 +#include <sys/time.h> +#include <sys/resource.h> +#else +#include <windows.h> +#endif + +/* Test if the given year is a leap year */ +#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0)) + +/* converts a broken down date representation, relative to UTC, + * to a timestamp; it uses timegm() if it's available. + * + * Returns -1 and sets errno to EINVAL on error; returns the timestamp + * and sets errno to 0 on success. + */ +time_t +mktime_utc(struct tm *tm) +{ + time_t retval; +#ifndef HAVE_TIMEGM + /* + * We don't have timegm(), so use code copied from Glib source + * gtimer.c. + */ + static const int days_before[] = + { + 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 + }; + + int yr; + + if (tm->tm_mon < 0 || tm->tm_mon > 11) { + errno = EINVAL; + return (time_t) -1; + } + + retval = (tm->tm_year - 70) * 365; + + /* count number of leap years */ + yr = tm->tm_year + 1900; + if (tm->tm_mon + 1 < 3 && isleap(yr)) + yr--; + retval += (((yr / 4) - (yr / 100) + (yr / 400)) - 477); /* 477 = ((1970 / 4) - (1970 / 100) + (1970 / 400)) */ + + retval += days_before[tm->tm_mon] + tm->tm_mday - 1; + + retval = ((((retval * 24) + tm->tm_hour) * 60) + tm->tm_min) * 60 + tm->tm_sec; + + /* + * Just in case somebody asked for 1969-12-31 23:59:59 UTC, + * which is one second before the Unix epoch. + */ + errno = 0; + return retval; +#else + retval = timegm(tm); + /* + * If passed a struct tm for 2013-03-01 00:00:00, both + * macOS and FreeBSD timegm() return the epoch time + * value for 2013-03-01 00:00:00 UTC, but also set + * errno to EOVERFLOW. This may be true of other + * implementations based on the tzcode reference + * impelementation of timegm(). + * + * The macOS and FreeBSD documentation for timegm() neither + * commit to leaving errno alone nor commit to setting it + * to a particular value. + * + * Force errno to 0, and check for an error and set it to + * EINVAL iff we got an error. + */ + errno = 0; + if (retval == (time_t)-1) { + /* + * Did somebody ask for 1969-12-31 23:59:59 UTC, + * which is one second before the Unix epoch? + * + * If so, timegm() happened to return the correct + * timestamp (whether because it calculated it or + * because it failed in some fashion). + * + * If not, set errno to EINVAL. + */ + if (tm->tm_year != (1969 - 1900) || + tm->tm_mon != (12 - 1) || + tm->tm_mday != 31 || + tm->tm_hour != 23 || + tm->tm_min != 59 || + tm->tm_sec != 59) + errno = EINVAL; + } + return retval; +#endif /* !HAVE_TIMEGM */ +} + +/* Validate the values in a time_t + * Currently checks tm_year, tm_mon, tm_mday, tm_hour, tm_min, and tm_sec; + * disregards tm_wday, tm_yday, and tm_isdst. + * Use this in situations where you wish to return an error rather than + * normalizing invalid dates; otherwise you could specify, for example, + * 2020-10-40 (to quote the macOS and probably *BSD manual + * page for ctime()/localtime()/mktime()/etc., "October 40 + * is changed into November 9"). + */ +bool +tm_is_valid(struct tm *tm) +{ + static const int8_t days_in_month[12] = { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 + }; + + if (tm->tm_mon < 0 || tm->tm_mon > 11) { + return false; + } + if (tm->tm_mday < 0 || tm->tm_mday > + ((tm->tm_mon == 1 && isleap(tm->tm_year)) ? 29 : days_in_month[tm->tm_mon])) { + return false; + } + if (tm->tm_hour < 0 || tm->tm_hour > 23) { + return false; + } + /* XXX: ISO 8601 and others allow 24:00:00 for end of day, perhaps that + * one case should be allowed? + */ + if (tm->tm_min < 0 || tm->tm_min > 59) { + return false; + } + if (tm->tm_sec < 0 || tm->tm_sec > 60) { + /* 60, not 59, to account for leap seconds */ + return false; + } + return true; +} + +void get_resource_usage(double *user_time, double *sys_time) { +#ifndef _WIN32 + struct rusage ru; + + getrusage(RUSAGE_SELF, &ru); + + *user_time = ru.ru_utime.tv_sec + (ru.ru_utime.tv_usec / 1000000.0); + *sys_time = ru.ru_stime.tv_sec + (ru.ru_stime.tv_usec / 1000000.0); +#else /* _WIN32 */ + HANDLE h_proc = GetCurrentProcess(); + FILETIME cft, eft, kft, uft; + ULARGE_INTEGER uli_time; + + GetProcessTimes(h_proc, &cft, &eft, &kft, &uft); + + uli_time.LowPart = uft.dwLowDateTime; + uli_time.HighPart = uft.dwHighDateTime; + *user_time = uli_time.QuadPart / 10000000.0; + uli_time.LowPart = kft.dwLowDateTime; + uli_time.HighPart = kft.dwHighDateTime; + *sys_time = uli_time.QuadPart / 1000000000.0; +#endif /* _WIN32 */ +} + +static double last_user_time = 0.0; +static double last_sys_time = 0.0; + +void log_resource_usage(bool reset_delta, const char *format, ...) { + va_list ap; + GString *log_str = g_string_new(""); + double user_time; + double sys_time; + + get_resource_usage(&user_time, &sys_time); + + if (reset_delta || last_user_time == 0.0) { + last_user_time = user_time; + last_sys_time = sys_time; + } + + g_string_append_printf(log_str, "user %.3f +%.3f sys %.3f +%.3f ", + user_time, user_time - last_user_time, + sys_time, sys_time - last_sys_time); + + va_start(ap, format); + g_string_append_vprintf(log_str, format, ap); + va_end(ap); + + ws_warning("%s", log_str->str); + g_string_free(log_str, true); + +} + +/* Copied from pcapio.c pcapng_write_interface_statistics_block()*/ +uint64_t +create_timestamp(void) { + uint64_t timestamp; +#ifdef _WIN32 + FILETIME now; +#else + struct timeval now; +#endif + +#ifdef _WIN32 + /* + * Current time, represented as 100-nanosecond intervals since + * January 1, 1601, 00:00:00 UTC. + * + * I think DWORD might be signed, so cast both parts of "now" + * to uint32_t so that the sign bit doesn't get treated specially. + * + * Windows 8 provides GetSystemTimePreciseAsFileTime which we + * might want to use instead. + */ + GetSystemTimeAsFileTime(&now); + timestamp = (((uint64_t)(uint32_t)now.dwHighDateTime) << 32) + + (uint32_t)now.dwLowDateTime; + + /* + * Convert to same thing but as 1-microsecond, i.e. 1000-nanosecond, + * intervals. + */ + timestamp /= 10; + + /* + * Subtract difference, in microseconds, between January 1, 1601 + * 00:00:00 UTC and January 1, 1970, 00:00:00 UTC. + */ + timestamp -= EPOCH_DELTA_1601_01_01_00_00_00_UTC*1000000; +#else + /* + * Current time, represented as seconds and microseconds since + * January 1, 1970, 00:00:00 UTC. + */ + gettimeofday(&now, NULL); + + /* + * Convert to delta in microseconds. + */ + timestamp = (uint64_t)(now.tv_sec) * 1000000 + (uint64_t)(now.tv_usec); +#endif + return timestamp; +} + +struct timespec * +ws_clock_get_realtime(struct timespec *ts) +{ +#if defined(HAVE_CLOCK_GETTIME) + if (clock_gettime(CLOCK_REALTIME, ts) == 0) + return ts; +#elif defined(HAVE_TIMESPEC_GET) + if (timespec_get(ts, TIME_UTC) == TIME_UTC) + return ts; +#endif + +#ifndef _WIN32 + /* Fall back on gettimeofday(). */ + struct timeval usectimenow; + gettimeofday(&usectimenow, NULL); + ts->tv_sec = usectimenow.tv_sec; + ts->tv_nsec = usectimenow.tv_usec*1000; + return ts; +#else + /* Fall back on time(). */ + ts->tv_sec = time(NULL); + ts->tv_nsec = 0; + return ts; +#endif +} + +struct tm * +ws_localtime_r(const time_t *timep, struct tm *result) +{ +#if defined(HAVE_LOCALTIME_R) + return localtime_r(timep, result); +#elif defined(_MSC_VER) + errno_t err = localtime_s(result, timep); + if (err == 0) + return result; + return NULL; +#else + struct tm *aux = localtime(timep); + if (aux == NULL) + return NULL; + *result = *aux; + return result; +#endif +} + +void ws_tzset(void) +{ +#ifdef HAVE_TZSET + tzset(); +#endif +} + +struct tm * +ws_gmtime_r(const time_t *timep, struct tm *result) +{ +#if defined(HAVE_GMTIME_R) + return gmtime_r(timep, result); +#elif defined(_MSC_VER) + errno_t err = gmtime_s(result, timep); + if (err == 0) + return result; + return NULL; +#else + struct tm *aux = gmtime(timep); + if (aux == NULL) + return NULL; + *result = *aux; + return result; +#endif +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 8 + * tab-width: 8 + * indent-tabs-mode: t + * End: + * + * vi: set shiftwidth=8 tabstop=8 noexpandtab: + * :indentSize=8:tabSize=8:noTabs=false: + */ diff --git a/wsutil/time_util.h b/wsutil/time_util.h new file mode 100644 index 00000000..6c2f52bf --- /dev/null +++ b/wsutil/time_util.h @@ -0,0 +1,81 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __TIME_UTIL_H__ +#define __TIME_UTIL_H__ + +#include <wireshark.h> +#include <time.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** Converts a broken down date representation, relative to UTC, + * to a timestamp + */ +WS_DLL_PUBLIC +time_t mktime_utc(struct tm *tm); + +/** Validate the values in a time_t. + * Currently checks tm_year, tm_mon, tm_mday, tm_hour, tm_min, and tm_sec; + * disregards tm_wday, tm_yday, and tm_isdst. + * + * @param tm The struct tm to validate. + */ +WS_DLL_PUBLIC +bool tm_is_valid(struct tm *tm); + +/** Fetch the process CPU time. + * + * Fetch the current process user and system CPU times, convert them to + * seconds, and store them in the provided parameters. + * + * @param user_time Seconds spent in user mode. + * @param sys_time Seconds spent in system (kernel) mode. + */ +WS_DLL_PUBLIC +void get_resource_usage(double *user_time, double *sys_time); + +/** Print the process CPU time followed by a log message. + * + * Print the current process user and system CPU times along with the times + * elapsed since the times were last reset. + * + * @param reset_delta Reset the delta times. This will typically be true when + * logging the first measurement and false thereafter. + * @param format Printf-style format string. Passed to g_string_vprintf. + * @param ... Parameters for the format string. + */ +WS_DLL_PUBLIC +void log_resource_usage(bool reset_delta, const char *format, ...); + +/** + * Fetch the number of microseconds since midnight (0 hour), January 1, 1970. + */ +WS_DLL_PUBLIC +uint64_t create_timestamp(void); + +WS_DLL_PUBLIC +void ws_tzset(void); + +WS_DLL_PUBLIC +struct timespec *ws_clock_get_realtime(struct timespec *ts); + +WS_DLL_PUBLIC +struct tm *ws_localtime_r(const time_t *timep, struct tm *result); + +WS_DLL_PUBLIC +struct tm *ws_gmtime_r(const time_t *timep, struct tm *result); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __TIME_UTIL_H__ */ diff --git a/wsutil/to_str.c b/wsutil/to_str.c new file mode 100644 index 00000000..d15576d4 --- /dev/null +++ b/wsutil/to_str.c @@ -0,0 +1,989 @@ +/* wsutil/to_str.c + * Routines for utilities to convert various other types to strings. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" +#include "to_str.h" + +#include <stdio.h> +#include <string.h> +#include <time.h> + +#include <wsutil/utf8_entities.h> +#include <wsutil/wslog.h> +#include <wsutil/inet_addr.h> +#include <wsutil/pint.h> +#include <wsutil/time_util.h> + +/* + * If a user _does_ pass in a too-small buffer, this is probably + * going to be too long to fit. However, even a partial string + * starting with "[Buf" should provide enough of a clue to be + * useful. + */ +#define _return_if_nospace(str_len, buf, buf_len) \ + do { \ + if ((str_len) > (buf_len)) { \ + (void)g_strlcpy(buf, "[Buffer too small]", buf_len); \ + return; \ + } \ + } while (0) + +static const char fast_strings[][4] = { + "0", "1", "2", "3", "4", "5", "6", "7", + "8", "9", "10", "11", "12", "13", "14", "15", + "16", "17", "18", "19", "20", "21", "22", "23", + "24", "25", "26", "27", "28", "29", "30", "31", + "32", "33", "34", "35", "36", "37", "38", "39", + "40", "41", "42", "43", "44", "45", "46", "47", + "48", "49", "50", "51", "52", "53", "54", "55", + "56", "57", "58", "59", "60", "61", "62", "63", + "64", "65", "66", "67", "68", "69", "70", "71", + "72", "73", "74", "75", "76", "77", "78", "79", + "80", "81", "82", "83", "84", "85", "86", "87", + "88", "89", "90", "91", "92", "93", "94", "95", + "96", "97", "98", "99", "100", "101", "102", "103", + "104", "105", "106", "107", "108", "109", "110", "111", + "112", "113", "114", "115", "116", "117", "118", "119", + "120", "121", "122", "123", "124", "125", "126", "127", + "128", "129", "130", "131", "132", "133", "134", "135", + "136", "137", "138", "139", "140", "141", "142", "143", + "144", "145", "146", "147", "148", "149", "150", "151", + "152", "153", "154", "155", "156", "157", "158", "159", + "160", "161", "162", "163", "164", "165", "166", "167", + "168", "169", "170", "171", "172", "173", "174", "175", + "176", "177", "178", "179", "180", "181", "182", "183", + "184", "185", "186", "187", "188", "189", "190", "191", + "192", "193", "194", "195", "196", "197", "198", "199", + "200", "201", "202", "203", "204", "205", "206", "207", + "208", "209", "210", "211", "212", "213", "214", "215", + "216", "217", "218", "219", "220", "221", "222", "223", + "224", "225", "226", "227", "228", "229", "230", "231", + "232", "233", "234", "235", "236", "237", "238", "239", + "240", "241", "242", "243", "244", "245", "246", "247", + "248", "249", "250", "251", "252", "253", "254", "255" +}; + +static inline char +low_nibble_of_octet_to_hex(uint8_t oct) +{ + /* At least one version of Apple's C compiler/linker is buggy, causing + a complaint from the linker about the "literal C string section" + not ending with '\0' if we initialize a 16-element "char" array with + a 16-character string, the fact that initializing such an array with + such a string is perfectly legitimate ANSI C nonwithstanding, the 17th + '\0' byte in the string nonwithstanding. */ + static const char hex_digits[16] = + { '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + + return hex_digits[oct & 0xF]; +} + +static inline char * +byte_to_hex(char *out, uint32_t dword) +{ + *out++ = low_nibble_of_octet_to_hex(dword >> 4); + *out++ = low_nibble_of_octet_to_hex(dword); + return out; +} + +char * +guint8_to_hex(char *out, uint8_t val) +{ + return byte_to_hex(out, val); +} + +char * +word_to_hex(char *out, uint16_t word) +{ + out = byte_to_hex(out, word >> 8); + out = byte_to_hex(out, word); + return out; +} + +char * +word_to_hex_punct(char *out, uint16_t word, char punct) +{ + out = byte_to_hex(out, word >> 8); + *out++ = punct; + out = byte_to_hex(out, word); + return out; +} + +char * +word_to_hex_npad(char *out, uint16_t word) +{ + if (word >= 0x1000) + *out++ = low_nibble_of_octet_to_hex((uint8_t)(word >> 12)); + if (word >= 0x0100) + *out++ = low_nibble_of_octet_to_hex((uint8_t)(word >> 8)); + if (word >= 0x0010) + *out++ = low_nibble_of_octet_to_hex((uint8_t)(word >> 4)); + *out++ = low_nibble_of_octet_to_hex((uint8_t)(word >> 0)); + return out; +} + +char * +dword_to_hex(char *out, uint32_t dword) +{ + out = word_to_hex(out, dword >> 16); + out = word_to_hex(out, dword); + return out; +} + +char * +dword_to_hex_punct(char *out, uint32_t dword, char punct) +{ + out = word_to_hex_punct(out, dword >> 16, punct); + *out++ = punct; + out = word_to_hex_punct(out, dword, punct); + return out; +} + +char * +qword_to_hex(char *out, uint64_t qword) +{ + out = dword_to_hex(out, (uint32_t)(qword >> 32)); + out = dword_to_hex(out, (uint32_t)(qword & 0xffffffff)); + return out; +} + +char * +qword_to_hex_punct(char *out, uint64_t qword, char punct) +{ + out = dword_to_hex_punct(out, (uint32_t)(qword >> 32), punct); + *out++ = punct; + out = dword_to_hex_punct(out, (uint32_t)(qword & 0xffffffff), punct); + return out; +} + +/* + * This does *not* null-terminate the string. It returns a pointer + * to the position in the string following the last character it + * puts there, so that the caller can either put the null terminator + * in or can append more stuff to the buffer. + * + * There needs to be at least len * 2 bytes left in the buffer. + */ +char * +bytes_to_hexstr(char *out, const uint8_t *ad, size_t len) +{ + size_t i; + + ws_return_val_if(!ad, NULL); + + for (i = 0; i < len; i++) + out = byte_to_hex(out, ad[i]); + return out; +} + +/* + * This does *not* null-terminate the string. It returns a pointer + * to the position in the string following the last character it + * puts there, so that the caller can either put the null terminator + * in or can append more stuff to the buffer. + * + * There needs to be at least len * 3 - 1 bytes left in the buffer. + */ +char * +bytes_to_hexstr_punct(char *out, const uint8_t *ad, size_t len, char punct) +{ + size_t i; + + ws_return_val_if(!ad, NULL); + + out = byte_to_hex(out, ad[0]); + for (i = 1; i < len; i++) { + *out++ = punct; + out = byte_to_hex(out, ad[i]); + } + return out; +} + +/* Routine to convert a sequence of bytes to a hex string, one byte/two hex + * digits at a time, with a specified punctuation character between + * the bytes. + * + * If punct is '\0', no punctuation is applied (and thus + * the resulting string is (len-1) bytes shorter) + */ +char * +bytes_to_str_punct_maxlen(wmem_allocator_t *scope, + const uint8_t *src, size_t src_size, + char punct, size_t max_bytes_len) +{ + char *buf; + size_t max_char_size; + char *buf_ptr; + int truncated = 0; + + ws_return_str_if(!src, scope); + ws_return_str_if(!src_size, scope); + + if (!punct) + return bytes_to_str_maxlen(scope, src, src_size, max_bytes_len); + + if (max_bytes_len == 0 || max_bytes_len > src_size) { + max_bytes_len = src_size; + } + else if (max_bytes_len < src_size) { + truncated = 1; + } + + /* Include space for ellipsis and '\0'. Optional extra punct + * at the end is already accounted for. */ + max_char_size = max_bytes_len * 3 + strlen(UTF8_HORIZONTAL_ELLIPSIS) + 1; + + buf = wmem_alloc(scope, max_char_size); + buf_ptr = bytes_to_hexstr_punct(buf, src, max_bytes_len, punct); + + if (truncated) { + *buf_ptr++ = punct; + buf_ptr = g_stpcpy(buf_ptr, UTF8_HORIZONTAL_ELLIPSIS); + } + + *buf_ptr = '\0'; + return buf; +} + +char * +bytes_to_str_maxlen(wmem_allocator_t *scope, + const uint8_t *src, size_t src_size, + size_t max_bytes_len) +{ + char *buf; + size_t max_char_size; + char *buf_ptr; + int truncated = 0; + + ws_return_str_if(!src, scope); + ws_return_str_if(!src_size, scope); + + if (max_bytes_len == 0 || max_bytes_len > src_size) { + max_bytes_len = src_size; + } + else if (max_bytes_len < src_size) { + truncated = 1; + } + + max_char_size = max_bytes_len * 2 + strlen(UTF8_HORIZONTAL_ELLIPSIS) + 1; + + buf = wmem_alloc(scope, max_char_size); + buf_ptr = bytes_to_hexstr(buf, src, max_bytes_len); + + if (truncated) + buf_ptr = g_stpcpy(buf_ptr, UTF8_HORIZONTAL_ELLIPSIS); + + *buf_ptr = '\0'; + return buf; +} + +/* + * The *_to_str_back() functions measured approx. a x7.5 speed-up versus + * snprintf() on my Linux system with GNU libc. + */ + +char * +oct_to_str_back(char *ptr, uint32_t value) +{ + while (value) { + *(--ptr) = '0' + (value & 0x7); + value >>= 3; + } + + *(--ptr) = '0'; + return ptr; +} + +char * +oct64_to_str_back(char *ptr, uint64_t value) +{ + while (value) { + *(--ptr) = '0' + (value & 0x7); + value >>= 3; + } + + *(--ptr) = '0'; + return ptr; +} + +char * +hex_to_str_back_len(char *ptr, uint32_t value, int len) +{ + do { + *(--ptr) = low_nibble_of_octet_to_hex(value); + value >>= 4; + len--; + } while (value); + + /* pad */ + while (len > 0) { + *(--ptr) = '0'; + len--; + } + + *(--ptr) = 'x'; + *(--ptr) = '0'; + + return ptr; +} + +char * +hex64_to_str_back_len(char *ptr, uint64_t value, int len) +{ + do { + *(--ptr) = low_nibble_of_octet_to_hex(value & 0xF); + value >>= 4; + len--; + } while (value); + + /* pad */ + while (len > 0) { + *(--ptr) = '0'; + len--; + } + + *(--ptr) = 'x'; + *(--ptr) = '0'; + + return ptr; +} + +char * +uint_to_str_back(char *ptr, uint32_t value) +{ + char const *p; + + /* special case */ + if (value == 0) + *(--ptr) = '0'; + + while (value >= 10) { + p = fast_strings[100 + (value % 100)]; + + value /= 100; + + *(--ptr) = p[2]; + *(--ptr) = p[1]; + } + + if (value) + *(--ptr) = (value) | '0'; + + return ptr; +} + +char * +uint64_to_str_back(char *ptr, uint64_t value) +{ + char const *p; + + /* special case */ + if (value == 0) + *(--ptr) = '0'; + + while (value >= 10) { + p = fast_strings[100 + (value % 100)]; + + value /= 100; + + *(--ptr) = p[2]; + *(--ptr) = p[1]; + } + + /* value will be 0..9, so using '& 0xF' is safe, and faster than '% 10' */ + if (value) + *(--ptr) = (value & 0xF) | '0'; + + return ptr; +} + +char * +uint_to_str_back_len(char *ptr, uint32_t value, int len) +{ + char *new_ptr; + + new_ptr = uint_to_str_back(ptr, value); + + /* substract from len number of generated characters */ + len -= (int)(ptr - new_ptr); + + /* pad remaining with '0' */ + while (len > 0) + { + *(--new_ptr) = '0'; + len--; + } + + return new_ptr; +} + +char * +uint64_to_str_back_len(char *ptr, uint64_t value, int len) +{ + char *new_ptr; + + new_ptr = uint64_to_str_back(ptr, value); + + /* substract from len number of generated characters */ + len -= (int)(ptr - new_ptr); + + /* pad remaining with '0' */ + while (len > 0) + { + *(--new_ptr) = '0'; + len--; + } + + return new_ptr; +} + +char * +int_to_str_back(char *ptr, int32_t value) +{ + if (value < 0) { + ptr = uint_to_str_back(ptr, -value); + *(--ptr) = '-'; + } else + ptr = uint_to_str_back(ptr, value); + + return ptr; +} + +char * +int64_to_str_back(char *ptr, int64_t value) +{ + if (value < 0) { + ptr = uint64_to_str_back(ptr, -value); + *(--ptr) = '-'; + } else + ptr = uint64_to_str_back(ptr, value); + + return ptr; +} + +static size_t +guint32_to_str_buf_len(const uint32_t u) +{ + /* ((2^32)-1) == 2147483647 */ + if (u >= 1000000000)return 10; + if (u >= 100000000) return 9; + if (u >= 10000000) return 8; + if (u >= 1000000) return 7; + if (u >= 100000) return 6; + if (u >= 10000) return 5; + if (u >= 1000) return 4; + if (u >= 100) return 3; + if (u >= 10) return 2; + + return 1; +} + +void +guint32_to_str_buf(uint32_t u, char *buf, size_t buf_len) +{ + size_t str_len = guint32_to_str_buf_len(u)+1; + + char *bp = &buf[str_len]; + + _return_if_nospace(str_len, buf, buf_len); + + *--bp = '\0'; + + uint_to_str_back(bp, u); +} + +static size_t +guint64_to_str_buf_len(const uint64_t u) +{ + /* ((2^64)-1) == 18446744073709551615 */ + + if (u >= G_GUINT64_CONSTANT(10000000000000000000)) return 20; + if (u >= G_GUINT64_CONSTANT(1000000000000000000)) return 19; + if (u >= G_GUINT64_CONSTANT(100000000000000000)) return 18; + if (u >= G_GUINT64_CONSTANT(10000000000000000)) return 17; + if (u >= G_GUINT64_CONSTANT(1000000000000000)) return 16; + if (u >= G_GUINT64_CONSTANT(100000000000000)) return 15; + if (u >= G_GUINT64_CONSTANT(10000000000000)) return 14; + if (u >= G_GUINT64_CONSTANT(1000000000000)) return 13; + if (u >= G_GUINT64_CONSTANT(100000000000)) return 12; + if (u >= G_GUINT64_CONSTANT(10000000000)) return 11; + if (u >= G_GUINT64_CONSTANT(1000000000)) return 10; + if (u >= G_GUINT64_CONSTANT(100000000)) return 9; + if (u >= G_GUINT64_CONSTANT(10000000)) return 8; + if (u >= G_GUINT64_CONSTANT(1000000)) return 7; + if (u >= G_GUINT64_CONSTANT(100000)) return 6; + if (u >= G_GUINT64_CONSTANT(10000)) return 5; + if (u >= G_GUINT64_CONSTANT(1000)) return 4; + if (u >= G_GUINT64_CONSTANT(100)) return 3; + if (u >= G_GUINT64_CONSTANT(10)) return 2; + + return 1; +} + +void +guint64_to_str_buf(uint64_t u, char *buf, size_t buf_len) +{ + size_t str_len = guint64_to_str_buf_len(u)+1; + + char *bp = &buf[str_len]; + + _return_if_nospace(str_len, buf, buf_len); + + *--bp = '\0'; + + uint64_to_str_back(bp, u); +} + +/* + This function is very fast and this function is called a lot. + XXX update the address_to_str stuff to use this function. + */ +void +ip_to_str_buf(const uint8_t *ad, char *buf, const int buf_len) +{ + register char const *p; + register char *b=buf; + + _return_if_nospace(WS_INET_ADDRSTRLEN, buf, buf_len); + + p=fast_strings[*ad++]; + do { + *b++=*p; + p++; + } while(*p); + *b++='.'; + + p=fast_strings[*ad++]; + do { + *b++=*p; + p++; + } while(*p); + *b++='.'; + + p=fast_strings[*ad++]; + do { + *b++=*p; + p++; + } while(*p); + *b++='.'; + + p=fast_strings[*ad]; + do { + *b++=*p; + p++; + } while(*p); + *b=0; +} + +char *ip_to_str(wmem_allocator_t *scope, const uint8_t *ad) +{ + char *buf = wmem_alloc(scope, WS_INET_ADDRSTRLEN * sizeof(char)); + + ip_to_str_buf(ad, buf, WS_INET_ADDRSTRLEN); + + return buf; +} + +void +ip6_to_str_buf(const ws_in6_addr *addr, char *buf, size_t buf_size) +{ + /* + * If there is not enough space then ws_inet_ntop6() will leave + * an error message in the buffer, we don't need + * to use _return_if_nospace(). + */ + ws_inet_ntop6(addr, buf, (unsigned)buf_size); +} + +char *ip6_to_str(wmem_allocator_t *scope, const ws_in6_addr *ad) +{ + char *buf = wmem_alloc(scope, WS_INET6_ADDRSTRLEN * sizeof(char)); + + ws_inet_ntop6(ad, buf, WS_INET6_ADDRSTRLEN); + + return buf; +} + +char * +ipxnet_to_str_punct(wmem_allocator_t *allocator, const uint32_t ad, const char punct) +{ + char *buf = (char *)wmem_alloc(allocator, 12); + + *dword_to_hex_punct(buf, ad, punct) = '\0'; + return buf; +} + +#define WS_EUI64_STRLEN 24 + +char * +eui64_to_str(wmem_allocator_t *scope, const uint64_t ad) { + char *buf, *tmp; + uint8_t *p_eui64; + + p_eui64=(uint8_t *)wmem_alloc(NULL, 8); + buf=(char *)wmem_alloc(scope, WS_EUI64_STRLEN); + + /* Copy and convert the address to network byte order. */ + *(uint64_t *)(void *)(p_eui64) = pntoh64(&(ad)); + + tmp = bytes_to_hexstr_punct(buf, p_eui64, 8, ':'); + *tmp = '\0'; /* NULL terminate */ + wmem_free(NULL, p_eui64); + return buf; +} + +/* + * Number of characters required by a 64-bit signed number. + */ +#define CHARS_64_BIT_SIGNED 20 /* sign plus 19 digits */ + +/* + * Number of characters required by a fractional part, in nanoseconds, + * not counting the decimal point. + */ +#define CHARS_NANOSECONDS 9 /* 000000001 */ + +/* + * Format the fractional part of a time, with the specified precision. + * Returns the number of bytes formatted. + */ +int +format_fractional_part_nsecs(char *buf, size_t buflen, uint32_t nsecs, const char *decimal_point, int precision) +{ + char *ptr; + size_t remaining; + int num_bytes; + size_t decimal_point_len; + uint32_t frac_part; + int8_t num_buf[CHARS_NANOSECONDS]; + int8_t *num_end = &num_buf[CHARS_NANOSECONDS]; + int8_t *num_ptr; + size_t num_len; + + ws_assert(precision != 0); + + if (buflen == 0) { + /* + * No room in the buffer for anything, including + * a terminating '\0'. + */ + return 0; + } + + /* + * If the fractional part is >= 1, don't show it as a + * fractional part. + */ + if (nsecs >= 1000000000U) { + num_bytes = snprintf(buf, buflen, "%s(%u nanoseconds)", + decimal_point, nsecs); + if ((unsigned int)num_bytes >= buflen) { + /* + * That filled up or would have overflowed + * the buffer. Nothing more to do; return + * the remaining space in the buffer, minus + * one byte for the terminating '\0',* as + * that's the number of bytes we copied. + */ + return (int)(buflen - 1); + } + return num_bytes; + } + + ptr = buf; + remaining = buflen; + num_bytes = 0; + + /* + * Copy the decimal point. + * (We assume here that the locale's decimal point does + * not contain so many characters that its size doesn't + * fit in an int. :-)) + */ + decimal_point_len = g_strlcpy(buf, decimal_point, buflen); + if (decimal_point_len >= buflen) { + /* + * The decimal point didn't fit in the buffer + * and was truncated. Nothing more to do; + * return the remaining space in the buffer, + * minus one byte for the terminating '\0', + * as that's the number of bytes we copied. + */ + return (int)(buflen - 1); + } + ptr += decimal_point_len; + remaining -= decimal_point_len; + num_bytes += (int)decimal_point_len; + + /* + * Fill in num_buf with the nanoseconds value, padded with + * leading zeroes, to the specified precision. + * + * We scale the fractional part in advance, as that just + * takes one division by a constant (which may be + * optimized to a faster multiplication by a constant) + * and gets rid of some divisions and remainders by 100 + * done to generate the digits. + * + * We pass preciions as the last argument to + * uint_to_str_back_len(), as that might mean that + * all of the cases end up using common code to + * do part of the call to uint_to_str_back_len(). + */ + switch (precision) { + + case 1: + /* + * Scale down to units of 1/10 second. + */ + frac_part = nsecs / 100000000U; + break; + + case 2: + /* + * Scale down to units of 1/100 second. + */ + frac_part = nsecs / 10000000U; + break; + + case 3: + /* + * Scale down to units of 1/1000 second. + */ + frac_part = nsecs / 1000000U; + break; + + case 4: + /* + * Scale down to units of 1/10000 second. + */ + frac_part = nsecs / 100000U; + break; + + case 5: + /* + * Scale down to units of 1/100000 second. + */ + frac_part = nsecs / 10000U; + break; + + case 6: + /* + * Scale down to units of 1/1000000 second. + */ + frac_part = nsecs / 1000U; + break; + + case 7: + /* + * Scale down to units of 1/10000000 second. + */ + frac_part = nsecs / 100U; + break; + + case 8: + /* + * Scale down to units of 1/100000000 second. + */ + frac_part = nsecs / 10U; + break; + + case 9: + /* + * We're already in units of 1/1000000000 second. + */ + frac_part = nsecs; + break; + + default: + ws_assert_not_reached(); + break; + } + + num_ptr = uint_to_str_back_len(num_end, frac_part, precision); + + /* + * The length of the string that we want to copy to the buffer + * is the minimum of: + * + * the length of the digit string; + * the remaining space in the buffer, minus 1 for the + * terminating '\0'. + */ + num_len = MIN((size_t)(num_end - num_ptr), remaining - 1); + if (num_len == 0) { + /* + * Not enough room to copy anything. + * Return the number of bytes we've generated. + */ + return num_bytes; + } + + /* + * Copy over the fractional part. + * (We assume here that the fractional part does not contain + * so many characters that its size doesn't fit in an int. :-)) + */ + memcpy(ptr, num_ptr, num_len); + ptr += num_len; + num_bytes += (int)num_len; + + /* + * '\0'-terminate it. + */ + *ptr = '\0'; + return num_bytes; +} + +void +display_epoch_time(char *buf, size_t buflen, const nstime_t *ns, int precision) +{ + display_signed_time(buf, buflen, ns, precision); +} + +void +display_signed_time(char *buf, size_t buflen, const nstime_t *ns, int precision) +{ + int nsecs; + /* this buffer is not NUL terminated */ + int8_t num_buf[CHARS_64_BIT_SIGNED]; + int8_t *num_end = &num_buf[CHARS_64_BIT_SIGNED]; + int8_t *num_ptr; + size_t num_len; + + if (buflen < 1) + return; + + /* If the fractional part of the time stamp is negative, + print its absolute value and, if the seconds part isn't + (the seconds part should be zero in that case), stick + a "-" in front of the entire time stamp. */ + nsecs = ns->nsecs; + if (nsecs < 0) { + nsecs = -nsecs; + if (ns->secs >= 0) { + buf[0] = '-'; + buf++; + buflen--; + } + } + + /* + * Fill in num_buf with the seconds value. + */ + num_ptr = int64_to_str_back(num_end, ns->secs); + + /* + * The length of the string that we want to copy to the buffer + * is the minimum of: + * + * the length of the digit string; + * the size of the buffer, minus 1 for the terminating + * '\0'. + */ + num_len = MIN((size_t)(num_end - num_ptr), buflen - 1); + if (num_len == 0) { + /* + * Not enough room to copy anything. + */ + return; + } + + /* + * Copy over the seconds value. + */ + memcpy(buf, num_ptr, num_len); + buf += num_len; + buflen -= num_len; + + if (precision == 0) { + /* + * Seconds precision, so no nanosecond. + * Nothing more to do other than to + * '\0'-terminate the string. + */ + *buf = '\0'; + return; + } + + /* + * Append the fractional part. + */ + format_fractional_part_nsecs(buf, buflen, (uint32_t)nsecs, ".", precision); +} + +void +format_nstime_as_iso8601(char *buf, size_t buflen, const nstime_t *ns, + char *decimal_point, bool local, int precision) +{ + struct tm tm, *tmp; + char *ptr; + size_t remaining; + int num_bytes; + + if (local) + tmp = ws_localtime_r(&ns->secs, &tm); + else + tmp = ws_gmtime_r(&ns->secs, &tm); + if (tmp == NULL) { + snprintf(buf, buflen, "Not representable"); + return; + } + ptr = buf; + remaining = buflen; + num_bytes = snprintf(ptr, remaining, + "%04d-%02d-%02d %02d:%02d:%02d", + tmp->tm_year + 1900, + tmp->tm_mon + 1, + tmp->tm_mday, + tmp->tm_hour, + tmp->tm_min, + tmp->tm_sec); + if (num_bytes < 0) { + /* + * That got an error. + * Not much else we can do. + */ + snprintf(buf, buflen, "snprintf() failed"); + return; + } + if ((unsigned int)num_bytes >= remaining) { + /* + * That filled up or would have overflowed the buffer. + * Nothing more we can do. + */ + return; + } + ptr += num_bytes; + remaining -= num_bytes; + + if (precision != 0) { + /* + * Append the fractional part. + * Get the nsecs as a 32-bit unsigned value, as it should + * never be negative, so we treat it as unsigned. + */ + format_fractional_part_nsecs(ptr, remaining, (uint32_t)ns->nsecs, decimal_point, precision); + } +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 8 + * tab-width: 8 + * indent-tabs-mode: t + * End: + * + * vi: set shiftwidth=8 tabstop=8 noexpandtab: + * :indentSize=8:tabSize=8:noTabs=false: + */ diff --git a/wsutil/to_str.h b/wsutil/to_str.h new file mode 100644 index 00000000..08bd536b --- /dev/null +++ b/wsutil/to_str.h @@ -0,0 +1,314 @@ +/** @file + * + * Definitions for utilities to convert various other types to strings. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WSUTIL_TO_STR_H__ +#define __WSUTIL_TO_STR_H__ + +#include <wireshark.h> + +#include <wsutil/wmem/wmem.h> +#include <wsutil/inet_ipv6.h> +#include <wsutil/nstime.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * guint8_to_hex() + * + * Output uint8_t hex representation to 'out', and return pointer after last character (out + 2). + * It will always output full representation (padded with 0). + * + * String is not NUL terminated by this routine. + * There needs to be at least 2 bytes in the buffer. + */ +WS_DLL_PUBLIC char *guint8_to_hex(char *out, uint8_t val); + +/** + * word_to_hex() + * + * Output uint16_t hex representation to 'out', and return pointer after last character (out + 4). + * It will always output full representation (padded with 0). + * + * String is not NUL terminated by this routine. + * There needs to be at least 4 bytes in the buffer. + */ +WS_DLL_PUBLIC char *word_to_hex(char *out, uint16_t word); + +/** + * word_to_hex_punct() + * + * Output uint16_t hex representation to 'out', and return pointer after last character. + * Each byte will be separated with punct character (cannot be NUL). + * It will always output full representation (padded with 0). + * + * String is not NUL terminated by this routine. + * There needs to be at least 5 bytes in the buffer. + */ +WS_DLL_PUBLIC char *word_to_hex_punct(char *out, uint16_t word, char punct); + +/** + * word_to_hex_npad() + * + * Output uint16_t hex representation to 'out', and return pointer after last character. + * Value is not padded. + * + * String is not NUL terminated by this routine. + * There needs to be at least 4 bytes in the buffer. + */ +WS_DLL_PUBLIC char *word_to_hex_npad(char *out, uint16_t word); + +/** + * dword_to_hex() + * + * Output uint32_t hex representation to 'out', and return pointer after last character. + * It will always output full representation (padded with 0). + * + * String is not NUL terminated by this routine. + * There needs to be at least 8 bytes in the buffer. + */ +WS_DLL_PUBLIC char *dword_to_hex(char *out, uint32_t dword); + +/** + * dword_to_hex_punct() + * + * Output uint32_t hex representation to 'out', and return pointer after last character. + * Each byte will be separated with punct character (cannot be NUL). + * It will always output full representation (padded with 0). + * + * String is not NUL terminated by this routine. + * There needs to be at least 11 bytes in the buffer. + */ +WS_DLL_PUBLIC char *dword_to_hex_punct(char *out, uint32_t dword, char punct); + +/** + * qword_to_hex() + * + * Output uint64_t hex representation to 'out', and return pointer after last character. + * It will always output full representation (padded with 0). + * + * String is not NUL terminated by this routine. + * There needs to be at least 16 bytes in the buffer. + */ +WS_DLL_PUBLIC char *qword_to_hex(char *out, uint64_t qword); + +/** + * qword_to_hex_punct() + * + * Output uint64_t hex representation to 'out', and return pointer after last character. + * Each byte will be separated with punct character (cannot be NUL). + * It will always output full representation (padded with 0). + * + * String is not NUL terminated by this routine. + * There needs to be at least 22 bytes in the buffer. + */ +WS_DLL_PUBLIC char *qword_to_hex_punct(char *out, uint64_t qword, char punct); + +/** + * bytes_to_hexstr() + * + * Output hex representation of uint8_t array, and return pointer after last character. + * It will always output full representation (padded with 0). + * + * String is not NUL terminated by this routine. + * There needs to be at least len * 2 bytes in the buffer. + */ +WS_DLL_PUBLIC char *bytes_to_hexstr(char *out, const uint8_t *ad, size_t len); + +/** + * bytes_to_hexstr_punct() + * + * Output hex representation of uint8_t array, and return pointer after last character. + * Each byte will be separated with punct character (cannot be NUL). + * It will always output full representation (padded with 0). + * + * String is not NUL terminated by this routine. + * There needs to be at least len * 3 - 1 bytes in the buffer. + */ +WS_DLL_PUBLIC char *bytes_to_hexstr_punct(char *out, const uint8_t *ad, size_t len, char punct); + +/** Turn an array of bytes into a string showing the bytes in hex, + * separated by a punctuation character. + * + * @param scope memory allocation scheme used + * @param buf A pointer to the byte array + * @param buf_size The length of the byte array + * @param punct The punctuation character + * @param max_bytes_len Maximum number of bytes to represent, zero for no limit. + * @return A pointer to the formatted string + */ +WS_DLL_PUBLIC char *bytes_to_str_punct_maxlen(wmem_allocator_t *scope, + const uint8_t *buf, size_t buf_size, + char punct, size_t max_bytes_len); + +#define bytes_to_str_punct(scope, buf, buf_size, punct) \ + bytes_to_str_punct_maxlen(scope, buf, buf_size, punct, 24) + +/** Turn an array of bytes into a string showing the bytes in hex. + * + * @param scope memory allocation scheme used + * @param buf A pointer to the byte array + * @param buf_size The length of the byte array + * @param max_bytes_len Maximum number of bytes to represent, zero for no limit. + * @return A pointer to the formatted string + */ +WS_DLL_PUBLIC char *bytes_to_str_maxlen(wmem_allocator_t *scope, + const uint8_t *buf, size_t buf_size, + size_t max_bytes_len); + +#define bytes_to_str(scope, buf, buf_size) \ + bytes_to_str_maxlen(scope, buf, buf_size, 36) + +/** + * oct_to_str_back() + * + * Output uint32_t octal representation backward (last character will be written on ptr - 1), + * and return pointer to first character. + * + * String is not NUL terminated by this routine. + * There needs to be at least 12 bytes in the buffer. + */ +WS_DLL_PUBLIC char *oct_to_str_back(char *ptr, uint32_t value); + +/** + * oct64_to_str_back() + * + * Output uint64_t octal representation backward (last character will be written on ptr - 1), + * and return pointer to first character. + * + * String is not NUL terminated by this routine. + * There needs to be at least 12 bytes in the buffer. + */ +WS_DLL_PUBLIC char *oct64_to_str_back(char *ptr, uint64_t value); + +/** + * hex_to_str_back() + * + * Output uint32_t hex representation backward (last character will be written on ptr - 1), + * and return pointer to first character. + * This routine will output for sure (can output more) 'len' decimal characters (number padded with '0'). + * + * String is not NUL terminated by this routine. + * There needs to be at least 2 + MAX(8, len) bytes in the buffer. + */ +WS_DLL_PUBLIC char *hex_to_str_back_len(char *ptr, uint32_t value, int len); + +/** + * hex64_to_str_back() + * + * Output uint64_t hex representation backward (last character will be written on ptr - 1), + * and return pointer to first character. + * This routine will output for sure (can output more) 'len' decimal characters (number padded with '0'). + * + * String is not NUL terminated by this routine. + * There needs to be at least 2 + MAX(16, len) bytes in the buffer. + */ +WS_DLL_PUBLIC char *hex64_to_str_back_len(char *ptr, uint64_t value, int len); + +/** + * uint_to_str_back() + * + * Output uint32_t decimal representation backward (last character will be written on ptr - 1), + * and return pointer to first character. + * + * String is not NUL terminated by this routine. + * There needs to be at least 10 bytes in the buffer. + */ +WS_DLL_PUBLIC char *uint_to_str_back(char *ptr, uint32_t value); + +/** + * uint64_str_back() + * + * Output uint64_t decimal representation backward (last character will be written on ptr - 1), + * and return pointer to first character. + * + * String is not NUL terminated by this routine. + * There needs to be at least 20 bytes in the buffer. + */ +WS_DLL_PUBLIC char *uint64_to_str_back(char *ptr, uint64_t value); + +/** + * uint_to_str_back_len() + * + * Output uint32_t decimal representation backward (last character will be written on ptr - 1), + * and return pointer to first character. + * This routine will output for sure (can output more) 'len' decimal characters (number padded with '0'). + * + * String is not NUL terminated by this routine. + * There needs to be at least MAX(10, len) bytes in the buffer. + */ +WS_DLL_PUBLIC char *uint_to_str_back_len(char *ptr, uint32_t value, int len); + +/** + * uint64_to_str_back_len() + * + * Output uint64_t decimal representation backward (last character will be written on ptr - 1), + * and return pointer to first character. + * This routine will output for sure (can output more) 'len' decimal characters (number padded with '0'). + * + * String is not NUL terminated by this routine. + * There needs to be at least MAX(20, len) bytes in the buffer. + */ +WS_DLL_PUBLIC char *uint64_to_str_back_len(char *ptr, uint64_t value, int len); + +/** + * int_to_str_back() + * + * Output int32_t decimal representation backward (last character will be written on ptr - 1), + * and return pointer to first character. + * + * String is not NUL terminated by this routine. + * There needs to be at least 11 bytes in the buffer. + */ +WS_DLL_PUBLIC char *int_to_str_back(char *ptr, int32_t value); + +/** + * int64_to_str_back() + * + * Output int64_t decimal representation backward (last character will be written on ptr - 1), + * and return pointer to first character. + * + * String is not NUL terminated by this routine. + * There needs to be at least 21 bytes in the buffer. + */ +WS_DLL_PUBLIC char *int64_to_str_back(char *ptr, int64_t value); + +WS_DLL_PUBLIC void guint32_to_str_buf(uint32_t u, char *buf, size_t buf_len); + +WS_DLL_PUBLIC void guint64_to_str_buf(uint64_t u, char *buf, size_t buf_len); + +WS_DLL_PUBLIC void ip_to_str_buf(const uint8_t *ad, char *buf, const int buf_len); + +WS_DLL_PUBLIC char *ip_to_str(wmem_allocator_t *scope, const uint8_t *ad); + +/* Returns length of the result. */ +WS_DLL_PUBLIC void ip6_to_str_buf(const ws_in6_addr *ad, char *buf, size_t buf_size); + +WS_DLL_PUBLIC char *ip6_to_str(wmem_allocator_t *scope, const ws_in6_addr *ad); + +WS_DLL_PUBLIC char *ipxnet_to_str_punct(wmem_allocator_t *scope, const uint32_t ad, const char punct); + +WS_DLL_PUBLIC char *eui64_to_str(wmem_allocator_t *scope, const uint64_t ad); + +WS_DLL_PUBLIC int format_fractional_part_nsecs(char *, size_t, uint32_t, const char *, int); + +WS_DLL_PUBLIC void display_epoch_time(char *, size_t, const nstime_t *, int); + +WS_DLL_PUBLIC void display_signed_time(char *, size_t, const nstime_t *, int); + +WS_DLL_PUBLIC void format_nstime_as_iso8601(char *, size_t, const nstime_t *, char *, bool, int); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __TO_STR_H__ */ diff --git a/wsutil/type_util.c b/wsutil/type_util.c new file mode 100644 index 00000000..f98a1b1d --- /dev/null +++ b/wsutil/type_util.c @@ -0,0 +1,68 @@ +/* type_util.c + * Types utility routines + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" + +#include <glib.h> + +#include "type_util.h" + +/* + * uint64_t to double conversions taken from gstutils.c of GStreamer project + * + * GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * 2002 Thomas Vander Stichele <thomas@apestaart.org> + * + * gstutils.h: Header for various utility functions + * + * GNU GPL v2 + * + */ + +/* work around error C2520: conversion from unsigned __int64 to double + * not implemented, use signed __int64 + * + * These are implemented as functions because on some platforms a 64bit int to + * double conversion is not defined/implemented. + */ + +double +type_util_guint64_to_gdouble(uint64_t value) +{ + if (value & G_GUINT64_CONSTANT (0x8000000000000000)) + return (double) ((int64_t) value) + (double) 18446744073709551616.; + else + return (double) ((int64_t) value); +} + +uint64_t +type_util_gdouble_to_guint64(double value) +{ + if (value < (double) 9223372036854775808.) /* 1 << 63 */ + return ((uint64_t) ((int64_t) value)); + + value -= (double) 18446744073709551616.; + return ((uint64_t) ((int64_t) value)); +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local Variables: + * c-basic-offset: 2 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=2 tabstop=8 expandtab: + * :indentSize=2:tabSize=8:noTabs=true: + */ diff --git a/wsutil/type_util.h b/wsutil/type_util.h new file mode 100644 index 00000000..eab9ff9b --- /dev/null +++ b/wsutil/type_util.h @@ -0,0 +1,44 @@ +/** @file + * Types utility definitions + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __TYPE_UTIL_H__ +#define __TYPE_UTIL_H__ + +#include <inttypes.h> +#include "ws_symbol_export.h" + +/* + * uint64_t to double conversions taken from gstutils.h of GStreamer project + * + * GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * 2002 Thomas Vander Stichele <thomas@apestaart.org> + * + * gstutils.h: Header for various utility functions + * + * GNU GPL v2 + * + */ + +WS_DLL_PUBLIC +uint64_t type_util_gdouble_to_guint64(double value); +WS_DLL_PUBLIC +double type_util_guint64_to_gdouble(uint64_t value); + +#ifdef _WIN32 +#define gdouble_to_guint64(value) type_util_gdouble_to_guint64(value) +#define guint64_to_gdouble(value) type_util_guint64_to_gdouble(value) +#else +#define gdouble_to_guint64(value) ((uint64_t)(value)) +#define guint64_to_gdouble(value) ((double)(value)) +#endif + +#endif /* __TYPE_UTIL_H__ */ diff --git a/wsutil/unicode-utils.c b/wsutil/unicode-utils.c new file mode 100644 index 00000000..4ed4b326 --- /dev/null +++ b/wsutil/unicode-utils.c @@ -0,0 +1,368 @@ +/* unicode-utils.c + * Unicode utility routines + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 2006 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" + +#include "unicode-utils.h" + +int ws_utf8_seqlen[256] = { + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 0x00...0x0f */ + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 0x10...0x1f */ + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 0x20...0x2f */ + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 0x30...0x3f */ + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 0x40...0x4f */ + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 0x50...0x5f */ + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 0x60...0x6f */ + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 0x70...0x7f */ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0x80...0x8f */ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0x90...0x9f */ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0xa0...0xaf */ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0xb0...0xbf */ + 0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2, /* 0xc0...0xcf */ + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, /* 0xd0...0xdf */ + 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, /* 0xe0...0xef */ + 4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0, /* 0xf0...0xff */ +}; + +/* Given a pointer and a length, validates a string of bytes as UTF-8. + * Returns the number of valid bytes, and a pointer immediately past + * the checked region. + * + * Differs from Glib's g_utf8_validate_len in that null bytes are + * considered valid UTF-8, and that maximal subparts are replaced as + * a unit. (I.e., given a sequence of 2 or 3 bytes which are a + * truncated version of a 3 or 4 byte UTF-8 character, but the next + * byte does not continue the character, the set of 2 or 3 bytes + * are replaced with one REPLACMENT CHARACTER.) + */ +static inline size_t +utf_8_validate(const uint8_t *start, ssize_t length, const uint8_t **end) +{ + const uint8_t *ptr = start; + uint8_t ch; + size_t unichar_len, valid_bytes = 0; + + while (length > 0) { + + ch = *ptr; + + if (ch < 0x80) { + valid_bytes++; + ptr++; + length--; + continue; + } + + ch = *ptr; + + if (ch < 0xc2 || ch > 0xf4) { + ptr++; + length--; + *end = ptr; + return valid_bytes; + } + + if (ch < 0xe0) { /* 110xxxxx, 2 byte char */ + unichar_len = 2; + } else if (ch < 0xf0) { /* 1110xxxx, 3 byte char */ + unichar_len = 3; + ptr++; + length--; + if (length < 1) { + *end = ptr; + return valid_bytes; + } + switch (ch) { + case 0xe0: + if (*ptr < 0xa0 || *ptr > 0xbf) { + *end = ptr; + return valid_bytes; + } + break; + case 0xed: + if (*ptr < 0x80 || *ptr > 0x9f) { + *end = ptr; + return valid_bytes; + } + break; + default: + if (*ptr < 0x80 || *ptr > 0xbf) { + *end = ptr; + return valid_bytes; + } + } + } else { /* 11110xxx, 4 byte char - > 0xf4 excluded above */ + unichar_len = 4; + ptr++; + length--; + if (length < 1) { + *end = ptr; + return valid_bytes; + } + switch (ch) { + case 0xf0: + if (*ptr < 0x90 || *ptr > 0xbf) { + *end = ptr; + return valid_bytes; + } + break; + case 0xf4: + if (*ptr < 0x80 || *ptr > 0x8f) { + *end = ptr; + return valid_bytes; + } + break; + default: + if (*ptr < 0x80 || *ptr > 0xbf) { + *end = ptr; + return valid_bytes; + } + } + ptr++; + length--; + if (length < 1) { + *end = ptr; + return valid_bytes; + } + if (*ptr < 0x80 || *ptr > 0xbf) { + *end = ptr; + return valid_bytes; + } + } + + ptr++; + length--; + if (length < 1) { + *end = ptr; + return valid_bytes; + } + if (*ptr < 0x80 || *ptr > 0xbf) { + *end = ptr; + return valid_bytes; + } else { + ptr++; + length--; + valid_bytes += unichar_len; + } + + } + *end = ptr; + return valid_bytes; +} + +/* + * Given a wmem scope, a pointer, and a length, treat the string of bytes + * referred to by the pointer and length as a UTF-8 string, and return a + * pointer to a UTF-8 string, allocated using the wmem scope, with all + * ill-formed sequences replaced with the Unicode REPLACEMENT CHARACTER + * according to the recommended "best practices" given in the Unicode + * Standard and specified by W3C/WHATWG. + * + * Note that in conformance with the Unicode Standard, this treats three + * byte sequences corresponding to UTF-16 surrogate halves (paired or unpaired) + * and two byte overlong encodings of 7-bit ASCII characters as invalid and + * substitutes REPLACEMENT CHARACTER for them. Explicit support for nonstandard + * derivative encoding formats (e.g. CESU-8, Java Modified UTF-8, WTF-8) could + * be added later. + * + * Compared with g_utf8_make_valid(), this function does not consider + * internal NUL bytes as invalid and replace them with replacment characters. + * It also replaces maximal subparts as a unit; i.e., a sequence of 2 or 3 + * bytes which are a truncated version of a valid 3 or 4 byte character (but + * the next byte does not continue the character) are replaced with a single + * REPLACEMENT CHARACTER, whereas the Glib function replaces each byte of the + * sequence with its own (3 octet) REPLACEMENT CHARACTER. + * + * XXX: length should probably be a size_t instead of a int in all + * these encoding functions + * XXX: the buffer returned can be of different length than the input, + * and can have internal NULs as well (so that strlen doesn't give its + * length). As with the other encoding functions, we should return the + * length of the output buffer (or a wmem_strbuf_t directly) and an + * indication of whether there was an invalid character (i.e. + * REPLACEMENT CHARACTER was used.) + */ +wmem_strbuf_t * +ws_utf8_make_valid_strbuf(wmem_allocator_t *scope, const uint8_t *ptr, ssize_t length) +{ + wmem_strbuf_t *str; + + str = wmem_strbuf_new_sized(scope, length+1); + + /* See the Unicode Standard conformance chapter at + * https://www.unicode.org/versions/Unicode15.0.0/ch03.pdf especially + * Table 3-7 "Well-Formed UTF-8 Byte Sequences" and + * U+FFFD Substitution of Maximal Subparts. */ + + while (length > 0) { + const uint8_t *prev = ptr; + size_t valid_bytes = utf_8_validate(prev, length, &ptr); + + if (valid_bytes) { + wmem_strbuf_append_len(str, prev, valid_bytes); + } + length -= ptr - prev; + prev += valid_bytes; + if (ptr - prev) { + wmem_strbuf_append_unichar_repl(str); + } + } + + return str; +} + +uint8_t * +ws_utf8_make_valid(wmem_allocator_t *scope, const uint8_t *ptr, ssize_t length) +{ + wmem_strbuf_t *str = ws_utf8_make_valid_strbuf(scope, ptr, length); + return wmem_strbuf_finalize(str); +} + +#ifdef _WIN32 + +#include <strsafe.h> + +/** @file + * Unicode utilities (internal interface) + * + * We define UNICODE and _UNICODE under Windows. This means that + * Windows SDK routines expect UTF-16 strings, in contrast to newer + * versions of Glib and GTK+ which expect UTF-8. This module provides + * convenience routines for converting between UTF-8 and UTF-16. + */ + +#define INITIAL_UTFBUF_SIZE 128 + +/* + * XXX - Should we use g_utf8_to_utf16() and g_utf16_to_utf8() + * instead? The goal of the functions below was to provide simple + * wrappers for UTF-8 <-> UTF-16 conversion without making the + * caller worry about freeing up memory afterward. + */ + +/* Convert from UTF-8 to UTF-16. */ +const wchar_t * +utf_8to16(const char *utf8str) +{ + static wchar_t *utf16buf[3]; + static int utf16buf_len[3]; + static int idx; + + if (utf8str == NULL) + return NULL; + + idx = (idx + 1) % 3; + + /* + * Allocate the buffer if it's not already allocated. + */ + if (utf16buf[idx] == NULL) { + utf16buf_len[idx] = INITIAL_UTFBUF_SIZE; + utf16buf[idx] = g_malloc(utf16buf_len[idx] * sizeof(wchar_t)); + } + + while (MultiByteToWideChar(CP_UTF8, 0, utf8str, -1, NULL, 0) >= utf16buf_len[idx]) { + /* + * Double the buffer's size if it's not big enough. + * The size of the buffer starts at 128, so doubling its size + * adds at least another 128 bytes, which is more than enough + * for one more character plus a terminating '\0'. + */ + utf16buf_len[idx] *= 2; + utf16buf[idx] = g_realloc(utf16buf[idx], utf16buf_len[idx] * sizeof(wchar_t)); + } + + if (MultiByteToWideChar(CP_UTF8, 0, utf8str, -1, utf16buf[idx], utf16buf_len[idx]) == 0) + return NULL; + + return utf16buf[idx]; +} + +void +utf_8to16_snprintf(TCHAR *utf16buf, int utf16buf_len, const char* fmt, ...) +{ + va_list ap; + char* dst; + + va_start(ap,fmt); + dst = ws_strdup_vprintf(fmt, ap); + va_end(ap); + + StringCchPrintf(utf16buf, utf16buf_len, _T("%s"), utf_8to16(dst)); + + g_free(dst); +} + +/* Convert from UTF-16 to UTF-8. */ +char * +utf_16to8(const wchar_t *utf16str) +{ + static char *utf8buf[3]; + static int utf8buf_len[3]; + static int idx; + + if (utf16str == NULL) + return NULL; + + idx = (idx + 1) % 3; + + /* + * Allocate the buffer if it's not already allocated. + */ + if (utf8buf[idx] == NULL) { + utf8buf_len[idx] = INITIAL_UTFBUF_SIZE; + utf8buf[idx] = g_malloc(utf8buf_len[idx]); + } + + while (WideCharToMultiByte(CP_UTF8, 0, utf16str, -1, NULL, 0, NULL, NULL) >= utf8buf_len[idx]) { + /* + * Double the buffer's size if it's not big enough. + * The size of the buffer starts at 128, so doubling its size + * adds at least another 128 bytes, which is more than enough + * for one more character plus a terminating '\0'. + */ + utf8buf_len[idx] *= 2; + utf8buf[idx] = g_realloc(utf8buf[idx], utf8buf_len[idx]); + } + + if (WideCharToMultiByte(CP_UTF8, 0, utf16str, -1, utf8buf[idx], utf8buf_len[idx], NULL, NULL) == 0) + return NULL; + + return utf8buf[idx]; +} + +/* Convert our argument list from UTF-16 to UTF-8. */ +char ** +arg_list_utf_16to8(int argc, wchar_t *wc_argv[]) { + char **argv; + int i; + + argv = (char **)g_malloc((argc + 1) * sizeof(char *)); + for (i = 0; i < argc; i++) { + argv[i] = g_utf16_to_utf8(wc_argv[i], -1, NULL, NULL, NULL); + } + argv[argc] = NULL; + return argv; +} + +#endif + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/unicode-utils.h b/wsutil/unicode-utils.h new file mode 100644 index 00000000..21c50ec0 --- /dev/null +++ b/wsutil/unicode-utils.h @@ -0,0 +1,138 @@ +/* unicode-utils.h + * Unicode utility definitions + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 2006 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __UNICODEUTIL_H__ +#define __UNICODEUTIL_H__ + +#include <wireshark.h> + +#ifdef _WIN32 +#include <windows.h> +#include <tchar.h> +#include <wchar.h> +#endif + +/** + * @file + * Unicode convenience routines. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef WS_DEBUG_UTF_8 +#define DEBUG_UTF_8_ENABLED true +#else +#define DEBUG_UTF_8_ENABLED false +#endif + +#define _CHECK_UTF_8(level, str, len) \ + do { \ + const char *__uni_endptr; \ + if (DEBUG_UTF_8_ENABLED && (str) != NULL && \ + !g_utf8_validate(str, len, &__uni_endptr)) { \ + ws_log_utf8(str, len, __uni_endptr); \ + } \ + } while (0) + +#define WS_UTF_8_CHECK(str, len) \ + _CHECK_UTF_8(LOG_LEVEL_DEBUG, str, len) + +#define WS_UTF_8_DEBUG_HERE(str, len) \ + _CHECK_UTF_8(LOG_LEVEL_ECHO, str, len) + +WSUTIL_EXPORT +int ws_utf8_seqlen[256]; + +/** Given the first byte in an UTF-8 encoded code point, + * return the length of the multibyte sequence, or *ZERO* + * if the byte is invalid as the first byte in a multibyte + * sequence. + */ +#define ws_utf8_char_len(ch) (ws_utf8_seqlen[(ch)]) + +/* + * Given a wmem scope, a pointer, and a length, treat the string of bytes + * referred to by the pointer and length as a UTF-8 string, and return a + * pointer to a UTF-8 string, allocated using the wmem scope, with all + * ill-formed sequences replaced with the Unicode REPLACEMENT CHARACTER + * according to the recommended "best practices" given in the Unicode + * Standard and specified by W3C/WHATWG. + */ +WS_DLL_PUBLIC uint8_t * +ws_utf8_make_valid(wmem_allocator_t *scope, const uint8_t *ptr, ssize_t length); + +/* + * Same as ws_utf8_make_valid() but returns a wmem_strbuf_t. + */ +WS_DLL_PUBLIC wmem_strbuf_t * +ws_utf8_make_valid_strbuf(wmem_allocator_t *scope, const uint8_t *ptr, ssize_t length); + +#ifdef _WIN32 + +/** Given a UTF-8 string, convert it to UTF-16. This is meant to be used + * to convert between GTK+ 2.x (UTF-8) to Windows (UTF-16). + * + * @param utf8str The string to convert. May be NULL. + * @return The string converted to UTF-16. If utf8str is NULL, returns + * NULL. The return value should NOT be freed by the caller. + */ +WS_DLL_PUBLIC +const wchar_t * utf_8to16(const char *utf8str); + +/** Create a UTF-16 string (in place) according to the format string. + * + * @param utf16buf The buffer to return the UTF-16 string in. + * @param utf16buf_len The size of the 'utf16buf' parameter + * @param fmt A standard printf() format string + */ +WS_DLL_PUBLIC +void utf_8to16_snprintf(TCHAR *utf16buf, int utf16buf_len, const char* fmt, ...) +G_GNUC_PRINTF(3, 4); + +/** Given a UTF-16 string, convert it to UTF-8. This is meant to be used + * to convert between GTK+ 2.x (UTF-8) to Windows (UTF-16). + * + * @param utf16str The string to convert. May be NULL. + * @return The string converted to UTF-8. If utf16str is NULL, returns + * NULL. The return value should NOT be freed by the caller. + */ +WS_DLL_PUBLIC +char * utf_16to8(const wchar_t *utf16str); + +/** Convert the supplied program argument list from UTF-16 to UTF-8 + * return a pointer to the array of UTF-8 arguments. This is intended + * to be used to normalize command line arguments at program startup. + * + * @param argc The number of arguments. + * @param argv The argument values (vector). + */ +WS_DLL_PUBLIC +char **arg_list_utf_16to8(int argc, wchar_t *wc_argv[]); + +#endif /* _WIN32 */ + +/* + * defines for helping with UTF-16 surrogate pairs + */ + +#define IS_LEAD_SURROGATE(uchar2) \ + ((uchar2) >= 0xd800 && (uchar2) < 0xdc00) +#define IS_TRAIL_SURROGATE(uchar2) \ + ((uchar2) >= 0xdc00 && (uchar2) < 0xe000) +#define SURROGATE_VALUE(lead, trail) \ + (((((lead) - 0xd800) << 10) | ((trail) - 0xdc00)) + 0x10000) + +#ifdef __cplusplus +} +#endif + +#endif /* __UNICODEUTIL_H__ */ diff --git a/wsutil/utf8_entities.h b/wsutil/utf8_entities.h new file mode 100644 index 00000000..3d2f2533 --- /dev/null +++ b/wsutil/utf8_entities.h @@ -0,0 +1,75 @@ +/** @file + * + * Byte sequences for various UTF-8 entities + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + + +#ifndef __UTF8_ENTITIES_H__ +#define __UTF8_ENTITIES_H__ + +/* + * Sequences can be found at + * http://www.fileformat.info/info/unicode/ + * http://www.utf8-chartable.de/ + * and other places + * + * Please be conservative when adding code points below. While many modern + * systems default to UTF-8 and handle it well, some do not. The Windows + * console is a notable example. As a general rule you probably shouldn't + * stray too far from code page 437 or WGL4: + * https://en.wikipedia.org/wiki/Code_page_437 + * https://en.wikipedia.org/wiki/Windows_Glyph_List_4 + * + * Hopefully we can dispense with the sequences below and simply encode our + * files as UTF 8 at some point. For example gcc has supported UTF 8 since + * at least 3.4. Visual C++ on the other hand is much more problematic. + * 2015 and later support /source-charset:utf-8, but prior versions appear + * to require a UTF 8 BOM. + */ + +#define UTF8_DEGREE_SIGN "\xc2\xb0" /* 176 / 0xb0 */ +#define UTF8_SUPERSCRIPT_TWO "\xc2\xb2" /* 178 / 0xb2 */ +#define UTF8_MICRO_SIGN "\xc2\xb5" /* 181 / 0xb5 */ +#define UTF8_MIDDLE_DOT "\xc2\xb7" /* 183 / 0xb7 */ +#define UTF8_RIGHT_POINTING_DOUBLE_ANGLE_QUOTATION_MARK "\xc2\xbb" /* 187 / 0xbb */ + +#define UTF8_BULLET "\xe2\x80\xa2" /* 8226 / 0x2024 */ +#define UTF8_EM_DASH "\xe2\x80\x94" /* 8212 / 0x2014 */ +#define UTF8_HORIZONTAL_ELLIPSIS "\xe2\x80\xa6" /* 8230 / 0x2026 */ + +#define UTF8_LEFTWARDS_ARROW "\xe2\x86\x90" /* 8592 / 0x2190 */ +#define UTF8_RIGHTWARDS_ARROW "\xe2\x86\x92" /* 8594 / 0x2192 */ +#define UTF8_LEFT_RIGHT_ARROW "\xe2\x86\x94" /* 8596 / 0x2194 */ + +/* macOS command key */ +#define UTF8_PLACE_OF_INTEREST_SIGN "\xe2\x8c\x98" /* 8984 / 0x2318 */ + +#define UTF8_SYMBOL_FOR_NULL "\xe2\x90\x80" /* 9216 / 0x2400 */ + +#define UTF8_CHECK_MARK "\xe2\x9c\x93" /* 10003 / 0x2713 */ +#define UTF8_BALLOT_X "\xe2\x9c\x97" /* 10007 / 0x2717 */ +#define UTF8_LONG_RIGHTWARDS_ARROW "\xe2\x9f\xb6" /* 10230 / 0x27f6 */ + +#define UTF8_ZERO_WIDTH_NO_BREAK_SPACE "\xef\xbb\xbf" /* 65279 / 0xffef */ +#define UTF8_BOM UTF8_ZERO_WIDTH_NO_BREAK_SPACE + +#endif /* __UTF8_ENTITIES_H__ */ + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/version_info.c b/wsutil/version_info.c new file mode 100644 index 00000000..ab450686 --- /dev/null +++ b/wsutil/version_info.c @@ -0,0 +1,612 @@ +/* version_info.c + * Routines to report version information for Wireshark programs + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <config.h> +#include "version_info.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <locale.h> + +#ifdef _WIN32 +#include <windows.h> +#elif __APPLE__ +#include <sys/types.h> +#include <sys/sysctl.h> +#elif __linux__ +#include <sys/sysinfo.h> +#endif + +#include <glib.h> +#include <pcre2.h> + +#ifdef HAVE_ZLIB +#include <zlib.h> +#endif + +#include "vcs_version.h" + +#include <wsutil/cpu_info.h> +#include <wsutil/os_version_info.h> +#include <wsutil/crash_info.h> +#include <wsutil/plugins.h> + +static char *appname_with_version; +static char *copyright_info; +static char *license_info; +static char *comp_info; +static char *runtime_info; + +static void end_string(GString *str); +static void get_compiler_info(GString *str); +static void get_mem_info(GString *str); + +void +ws_init_version_info(const char *appname, + gather_feature_func gather_compile, + gather_feature_func gather_runtime) +{ + GString *comp_info_str, *runtime_info_str; + GString *copyright_info_str; + GString *license_info_str; + + copyright_info_str = g_string_new(get_copyright_info()); + end_string(copyright_info_str); + copyright_info = g_string_free(copyright_info_str, false); + + license_info_str = g_string_new(get_license_info_short()); + end_string(license_info_str); + license_info = g_string_free(license_info_str, false); + + /* + * Combine the supplied application name string with the + * version - including the VCS version, for a build from + * a checkout. + */ + if (strstr(appname, "Wireshark") != NULL) { + appname_with_version = ws_strdup_printf("%s %s", + appname, get_ws_vcs_version_info()); + } + else { + appname_with_version = ws_strdup_printf("%s (Wireshark) %s", + appname, get_ws_vcs_version_info()); + } + + /* Get the compile-time version information string */ + comp_info_str = get_compiled_version_info(gather_compile); + + /* Get the run-time version information string */ + runtime_info_str = get_runtime_version_info(gather_runtime); + + comp_info = g_string_free(comp_info_str, false); + runtime_info = g_string_free(runtime_info_str, false); + + /* Add this information to the information to be reported on a crash. */ + ws_add_crash_info("%s\n" + "\n" + "%s\n" + "%s", + appname_with_version, comp_info, runtime_info); +} + +/* + * Take the gathered list of present/absent features (dependencies) + * and add them to the given string. + * Callback function for g_list_foreach() used in + * get_compiled_version_info() and get_runtime_version_info(). + */ +static void +feature_to_gstring(void * data, void * user_data) +{ + char *feature = (char *)data; + GString *str = (GString *)user_data; + if (str->len > 0) { + g_string_append(str, ", "); + } + g_string_append_printf(str, "%s %s", + (*feature == '+' ? "with" : "without"), feature + 1); +} + +/* + * If the string doesn't end with a newline, append one. + * Then word-wrap it to 80 columns. + */ +static void +end_string(GString *str) +{ + size_t point; + char *p, *q; + + point = str->len; + if (point == 0 || str->str[point - 1] != '\n') + g_string_append(str, "\n"); + p = str->str; + while (*p != '\0') { + q = strchr(p, '\n'); + if (q - p > 80) { + /* + * Break at or before this point. + */ + q = p + 80; + while (q > p && *q != ' ') + q--; + if (q != p) + *q = '\n'; + } + p = q + 1; + } +} + +const char * +get_appname_and_version(void) +{ + return appname_with_version; +} + +void +gather_pcre2_compile_info(feature_list l) +{ + with_feature(l, "PCRE2"); +} + +void +gather_zlib_compile_info(feature_list l) +{ +#ifdef HAVE_ZLIB +#ifdef ZLIB_VERSION + with_feature(l, "zlib "ZLIB_VERSION); +#else + with_feature(l, "zlib (version unknown)"); +#endif /* ZLIB_VERSION */ +#else + without_feature(l, "zlib"); +#endif /* HAVE_ZLIB */ +} + +/* + * Get various library compile-time versions, put them in a GString, + * and return the GString. + */ +GString * +get_compiled_version_info(gather_feature_func gather_compile) +{ + GString *str; + GList *l = NULL; + + str = g_string_new("Compiled "); + g_string_append_printf(str, "(%d-bit) ", (int)sizeof(str) * 8); + + /* Compiler info */ + g_string_append(str, "using "); + get_compiler_info(str); + +#ifdef GLIB_MAJOR_VERSION + with_feature(&l, + "GLib %d.%d.%d", GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION, + GLIB_MICRO_VERSION); +#else + with_feature(&l, + "GLib (version unknown)"); +#endif + + if (gather_compile != NULL) { + gather_compile(&l); + } + + l = g_list_reverse(l); + g_list_foreach(l, feature_to_gstring, str); + +#ifdef HAVE_PLUGINS + g_string_append(str, ", with binary plugins"); +#else + g_string_append(str, ", without binary plugins"); +#endif + +#ifdef WS_DEBUG + g_string_append(str, ", debug build"); +#endif + + g_string_append(str, "."); + end_string(str); + free_features(&l); + + return str; +} + +static void +get_mem_info(GString *str) +{ + int64_t memsize = 0; + +#ifdef _WIN32 + MEMORYSTATUSEX statex; + + statex.dwLength = sizeof (statex); + + if (GlobalMemoryStatusEx(&statex)) + memsize = statex.ullTotalPhys; +#elif __APPLE__ + size_t len = sizeof(memsize); + sysctlbyname("hw.memsize", &memsize, &len, NULL, 0); +#elif __linux__ + struct sysinfo info; + if (sysinfo(&info) == 0) + memsize = info.totalram * info.mem_unit; +#endif + + if (memsize > 0) + g_string_append_printf(str, ", with %" PRId64 " MB of physical memory", memsize/(1024*1024)); +} + +/* + * Get compiler information, and append it to the GString. + */ +static void +get_compiler_info(GString *str) +{ + /* + * See https://sourceforge.net/apps/mediawiki/predef/index.php?title=Compilers + * information on various defined strings. + * + * GCC's __VERSION__ is a nice text string for humans to + * read. The page at sourceforge.net largely describes + * numeric #defines that encode the version; if the compiler + * doesn't also offer a nice printable string, we try prettifying + * the number somehow. + */ + #if defined(_MSC_FULL_VER) + /* + * We check for this first, as Microsoft have a version of their + * compiler that has Clang as the front end and their code generator + * as the back end. + * + * My head asplode. + */ + + /* As of Wireshark 3.0, we support only Visual Studio 2015 (14.x) + * or later. + * + * https://dev.to/yumetodo/list-of-mscver-and-mscfullver-8nd + * has a *large* table of Microsoft product names, VC++ versions, + * _MSC_VER values, and _MSC_FULL_VER values. All the versions + * we support define _MSC_FULL_VER. We don't bother trying to + * get the SP/update/version number from the build number, as + * we'd have to keep updating that with every update; there's no + * way to get that information directly from a predefine, and in + * some cases multiple updates/versions have the *same* build + * number (because they didn't update the toolchain). + * + * https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros?view=vs-2017 + * defines the format of _MSC_VER and _MSC_FULL_VER. _MSC_FULL_VER + * is a decimal number of the form MMmmBBBBB, where MM is the compiler/ + * toolchain major version, mm is the minor version, and BBBBB is the + * build. We break it down based on that. + */ + #define COMPILER_MAJOR_VERSION (_MSC_FULL_VER / 10000000) + #define COMPILER_MINOR_VERSION ((_MSC_FULL_VER % 10000000) / 100000) + #define COMPILER_BUILD_NUMBER (_MSC_FULL_VER % 100000) + + /* + * From https://web.archive.org/web/20190125151548/https://blogs.msdn.microsoft.com/vcblog/2014/11/17/c111417-features-in-vs-2015-preview/ + * Bakersfield: DevDiv's upper management determines the scheduling + * of new major versions. They also decided to increment the product + * version from 12 (for VS 2013) to 14 (for VS 2015). However, the + * C++ compiler's version incremented normally, from 18 to 19. + * (It's larger because the C++ compiler predates the "Visual" in + * Visual C++.) + * + * So the product version number is 5 less than the compiler version + * number. + */ + #define VCPP_MAJOR_VERSION (COMPILER_MAJOR_VERSION - 5) + + #if VCPP_MAJOR_VERSION == 14 + /* + * From https://devblogs.microsoft.com/cppblog/side-by-side-minor-version-msvc-toolsets-in-visual-studio-2017/ + * + * We've been delivering improvements to Visual Studio 2017 more + * frequently than ever before. Since its first release in March + * we've released four major updates to VS2017 and are currently + * previewing the fifth update, VS2017 version 15.5. + * + * The MSVC toolset in VS2017 is built as a minor version update to + * the VS2015 compiler toolset. This minor version bump indicates + * that the VS2017 MSVC toolset is binary compatible with the VS2015 + * MSVC toolset, enabling an easier upgrade for VS2015 users. Even + * though the MSVC compiler toolset in VS2017 delivers many new + * features and conformance improvements it is a minor version, + * compatible update from 14.00 in VS2015 to 14.10 in VS2017. + */ + #if COMPILER_MINOR_VERSION < 10 + #define VS_VERSION "2015" + #elif COMPILER_MINOR_VERSION < 20 + #define VS_VERSION "2017" + #elif COMPILER_MINOR_VERSION < 30 + #define VS_VERSION "2019" + #else + #define VS_VERSION "2022" + #endif + #else + /* + * Add additional checks here, before the #else. + */ + #define VS_VERSION "(unknown)" + #endif /* VCPP_MAJOR_VERSION */ + + /* + * XXX - should we show the raw compiler version number, as is + * shown by "cl /?", which would be %d.%d.%d.%d with + * COMPILER_MAJOR_VERSION, COMPILER_MINOR_VERSION, + * COMPILER_BUILD_NUMBER, and _MSC_BUILD, the last of which is + * "the revision number element of the compiler's version number", + * which I guess is not to be confused with the build number, + * the _BUILD in the name nonwithstanding. + */ + g_string_append_printf(str, "Microsoft Visual Studio " VS_VERSION " (VC++ %d.%d, build %d)", + VCPP_MAJOR_VERSION, COMPILER_MINOR_VERSION, COMPILER_BUILD_NUMBER); + #if defined(__clang__) + /* + * See above. + */ + g_string_append_printf(str, " clang/C2 %s and -fno-ms-compatibility", + __VERSION__); + #endif + #elif defined(__GNUC__) && defined(__VERSION__) + /* + * Clang and llvm-gcc also define __GNUC__ and __VERSION__; + * distinguish between them. + */ + #if defined(__clang__) + /* clang */ + char *version; /* clang's version string has a trailing space. */ + #if defined(__clang_version__) + version = g_strdup(__clang_version__); + g_string_append_printf(str, "Clang %s", g_strstrip(version)); + #else + version = g_strdup(__VERSION__); + g_string_append_printf(str, "%s", g_strstrip(version)); + #endif /* __clang_version__ */ + g_free(version); + #elif defined(__llvm__) + /* llvm-gcc */ + g_string_append_printf(str, "llvm-gcc %s", __VERSION__); + #else + /* boring old GCC */ + g_string_append_printf(str, "GCC %s", __VERSION__); + #endif + #elif defined(__HP_aCC) + g_string_append_printf(str, "HP aCC %d", __HP_aCC); + #elif defined(__xlC__) + g_string_append_printf(str, "IBM XL C %d.%d", + (__xlC__ >> 8) & 0xFF, __xlC__ & 0xFF); + #ifdef __IBMC__ + if ((__IBMC__ % 10) != 0) + g_string_append_printf(str, " patch %d", __IBMC__ % 10); + #endif /* __IBMC__ */ + #elif defined(__INTEL_COMPILER) + g_string_append_printf(str, "Intel C %d.%d", + __INTEL_COMPILER / 100, (__INTEL_COMPILER / 10) % 10); + if ((__INTEL_COMPILER % 10) != 0) + g_string_append_printf(str, " patch %d", __INTEL_COMPILER % 10); + #ifdef __INTEL_COMPILER_BUILD_DATE + g_string_sprinta(str, ", compiler built %04d-%02d-%02d", + __INTEL_COMPILER_BUILD_DATE / 10000, + (__INTEL_COMPILER_BUILD_DATE / 100) % 100, + __INTEL_COMPILER_BUILD_DATE % 100); + #endif /* __INTEL_COMPILER_BUILD_DATE */ + #elif defined(__SUNPRO_C) + g_string_append_printf(str, "Sun C %d.%d", + (__SUNPRO_C >> 8) & 0xF, (__SUNPRO_C >> 4) & 0xF); + if ((__SUNPRO_C & 0xF) != 0) + g_string_append_printf(str, " patch %d", __SUNPRO_C & 0xF); + #else + g_string_append(str, "unknown compiler"); + #endif +} + +void +gather_pcre2_runtime_info(feature_list l) +{ + /* From pcre2_api(3): + * The where argument should point to a buffer that is at least 24 code + * units long. (The exact length required can be found by calling + * pcre2_config() with where set to NULL.) + * + * The API should accept a buffer size as additional input. We could opt for a + * stack buffer size greater than 24 but let's just go with the weirdness... + */ + int size; + char *buf_pcre2; + + size = pcre2_config(PCRE2_CONFIG_VERSION, NULL); + if (size < 0 || size > 255) { + without_feature(l, "PCRE2 (error querying)"); + return; + } + buf_pcre2 = g_malloc(size + 1); + pcre2_config(PCRE2_CONFIG_VERSION, buf_pcre2); + buf_pcre2[size] = '\0'; + with_feature(l, "PCRE2 %s", buf_pcre2); + g_free(buf_pcre2); +} + +void +gather_zlib_runtime_info(feature_list l) +{ + (void)l; +#if defined(HAVE_ZLIB) && !defined(_WIN32) + with_feature(l, "zlib %s", zlibVersion()); +#endif +} + +/* + * Get various library run-time versions, and the OS version, and append + * them to the specified GString. + * + * "additional_info" is called at the end to append any additional + * information; this is required in order to, for example, put the + * libcap information at the end of the string, as we currently + * don't use libcap in TShark. + */ +GString * +get_runtime_version_info(gather_feature_func gather_runtime) +{ + GString *str; + char *lc; + GList *l = NULL; + + str = g_string_new("Running on "); + + get_os_version_info(str); + + /* CPU Info */ + get_cpu_info(str); + + /* Get info about installed memory */ + get_mem_info(str); + + with_feature(&l, "GLib %u.%u.%u", + glib_major_version, glib_minor_version, glib_micro_version); + + if (gather_runtime != NULL) { + gather_runtime(&l); + } + + l = g_list_reverse(l); + g_list_foreach(l, feature_to_gstring, str); + + /* + * Display LC_CTYPE as a relevant, portable and sort of representative + * locale configuration without being exceedingly verbose and including + * the whole shebang of categories using LC_ALL. + */ + if ((lc = setlocale(LC_CTYPE, NULL)) != NULL) { + g_string_append_printf(str, ", with LC_TYPE=%s", lc); + } + +#ifdef HAVE_PLUGINS + if (plugins_supported()) { + g_string_append(str, ", binary plugins supported"); + } +#endif + + g_string_append_c(str, '.'); + end_string(str); + free_features(&l); + + return str; +} + +/* + * Return a version number string for Wireshark, including, for builds + * from a tree checked out from Wireshark's version control system, + * something identifying what version was checked out. + */ +const char * +get_ws_vcs_version_info(void) +{ +#ifdef VCSVERSION + return VERSION " (" VCSVERSION ")"; +#else + return VERSION; +#endif +} + +const char * +get_ws_vcs_version_info_short(void) +{ +#ifdef VCSVERSION + return VCSVERSION; +#else + return VERSION; +#endif +} + +void +get_ws_version_number(int *major, int *minor, int *micro) +{ + if (major) + *major = VERSION_MAJOR; + if (minor) + *minor = VERSION_MINOR; + if (micro) + *micro = VERSION_MICRO; +} + +void +show_version(void) +{ + printf("%s.\n\n" + "%s" + "%s\n" + "%s\n" + "%s", + appname_with_version, + copyright_info, + license_info, + comp_info, + runtime_info); +} + +void +show_help_header(const char *description) +{ + printf("%s\n", appname_with_version); + if (description) { + printf("%s\n", description); + printf("See https://www.wireshark.org for more information.\n"); + } +} + +/* + * Get copyright information. + */ +const char * +get_copyright_info(void) +{ + return + "Copyright 1998-2024 Gerald Combs <gerald@wireshark.org> and contributors."; +} + +const char * +get_license_info_short(void) +{ + return + "Licensed under the terms of the GNU General Public License (version 2 or later). " + "This is free software; see the file named COPYING in the distribution. " + "There is NO WARRANTY; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."; +} + +const char * +get_license_info(void) +{ + return + "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."; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 8 + * tab-width: 8 + * indent-tabs-mode: t + * End: + * + * vi: set shiftwidth=8 tabstop=8 noexpandtab: + * :indentSize=8:tabSize=8:noTabs=false: + */ diff --git a/wsutil/version_info.h b/wsutil/version_info.h new file mode 100644 index 00000000..1285cd2c --- /dev/null +++ b/wsutil/version_info.h @@ -0,0 +1,141 @@ +/** @file + * + * Declarations of routines to report version information for Wireshark + * programs + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WS_VERSION_INFO_H__ +#define __WS_VERSION_INFO_H__ + +#include <glib.h> +#include <wsutil/feature_list.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * Initialize information about the program for various purposes, including + * reporting the version and build information for the program, putting + * that information into crash dumps if possible, and giving the program + * name and version information into capture files written by the program + * if possible. + * + * "appname" is a string that appears at the beginning of the information; + * it should be the application name. "(Wireshark)" will be added if + * the program isn't Wireshark. + * + * "gather_compile" is called (if non-null) to add any additional build-time + * information. + * + * "gather_runtime" is called (if non-null) to add any additional + * run-time information; this is required in order to, for example, + * put the libcap information into the string, as we currently + * don't use libcap in TShark. + */ +WS_DLL_PUBLIC +void ws_init_version_info(const char *appname, + gather_feature_func gather_compile, + gather_feature_func gather_runtime); + +/* + * Get a string giving the application name, as provided to + * ws_init_version_info(), followed by a string giving the + * application version. + */ +WS_DLL_PUBLIC +const char *get_appname_and_version(void); + +WS_DLL_PUBLIC +void +gather_pcre2_compile_info(feature_list l); + +WS_DLL_PUBLIC +void +gather_zlib_compile_info(feature_list l); + +/* + * Get various library compile-time versions, put them in a GString, + * and return the GString. + * + * "gather_compile" is called (if non-null) to add any additional build-time + * information. + */ +WS_DLL_PUBLIC +GString *get_compiled_version_info(gather_feature_func gather_compile); + +WS_DLL_PUBLIC +void +gather_pcre2_runtime_info(feature_list l); + +WS_DLL_PUBLIC +void +gather_zlib_runtime_info(feature_list l); + +/* + * Get various library run-time versions, and the OS version, put them in + * a GString, and return the GString. + * + * "gather_runtime" is called (if non-null) to add any additional + * run-time information; this is required in order to, for example, + * put the libcap information into the string, as we currently + * don't use libcap in TShark. + */ +WS_DLL_PUBLIC +GString *get_runtime_version_info(gather_feature_func gather_runtime); + +/* + * Return a version number string for Wireshark, including, for builds + * from a tree checked out from Wireshark's version control system, + * something identifying what version was checked out. + */ +WS_DLL_PUBLIC +const char *get_ws_vcs_version_info(void); + +/* + * Shorter version of get_ws_vcs_version_info(). + */ +WS_DLL_PUBLIC +const char *get_ws_vcs_version_info_short(void); + +/* + * Return version number as integers. + */ +WS_DLL_PUBLIC +void get_ws_version_number(int *major, int *minor, int *micro); + +/* + * Show the program name and version number information on the standard + * output; this is used for command-line "show the version" options. + */ +WS_DLL_PUBLIC +void show_version(void); + +/* + * Show the program name and version number information, a supplied + * description string, and a "See {URL} for more information" message. + * This is used for command-line "help" options. + */ +WS_DLL_PUBLIC +void show_help_header(const char *description); + +WS_DLL_PUBLIC +const char *get_copyright_info(void); + +WS_DLL_PUBLIC +const char *get_license_info(void); + +WS_DLL_PUBLIC +const char *get_license_info_short(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __WS_VERSION_INFO_H__ */ diff --git a/wsutil/win32-utils.c b/wsutil/win32-utils.c new file mode 100644 index 00000000..b1cef5cd --- /dev/null +++ b/wsutil/win32-utils.c @@ -0,0 +1,313 @@ +/* win32-utils.c + * Win32 utility routines + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 2006 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <config.h> + +#include "win32-utils.h" + +#include <tchar.h> +#include <versionhelpers.h> + +/* Quote the argument element if necessary, so that it will get + * reconstructed correctly in the C runtime startup code. Note that + * the unquoting algorithm in the C runtime is really weird, and + * rather different than what Unix shells do. See stdargv.c in the C + * runtime sources (in the Platform SDK, in src/crt). + * + * Stolen from GLib's protect_argv(), an internal routine that quotes + * string in an argument list so that they arguments will be handled + * correctly in the command-line string passed to CreateProcess() + * if that string is constructed by gluing those strings together. + */ +char * +protect_arg (const char *argv) +{ + char *new_arg; + const char *p = argv; + char *q; + int len = 0; + bool need_dblquotes = false; + + while (*p) { + if (*p == ' ' || *p == '\t') + need_dblquotes = true; + else if (*p == '"') + len++; + else if (*p == '\\') { + const char *pp = p; + + while (*pp && *pp == '\\') + pp++; + if (*pp == '"') + len++; + } + len++; + p++; + } + + q = new_arg = g_malloc (len + need_dblquotes*2 + 1); + p = argv; + + if (need_dblquotes) + *q++ = '"'; + + while (*p) { + if (*p == '"') + *q++ = '\\'; + else if (*p == '\\') { + const char *pp = p; + + while (*pp && *pp == '\\') + pp++; + if (*pp == '"') + *q++ = '\\'; + } + *q++ = *p; + p++; + } + + if (need_dblquotes) + *q++ = '"'; + *q++ = '\0'; + + return new_arg; +} + +/* + * Generate a UTF-8 string for a Windows error. + */ + +/* + * We make the buffer at least this big, under the assumption that doing + * so will reduce the number of reallocations to do. (Otherwise, why + * did Microsoft bother supporting a minimum buffer size?) + */ +#define ERRBUF_SIZE 128 +const char * +win32strerror(DWORD error) +{ + DWORD retval; + WCHAR *utf16_message; + char *utf8_message; + char *tempmsg; + const char *msg; + + /* + * XXX - what language ID to use? + * + * For UN*Xes, g_strerror() may or may not return localized strings. + * + * We currently don't have localized strings, except for GUI items, + * but we might want to do so. On the other hand, if most of these + * messages are going to be read by Wireshark developers, English + * might be a better choice, so the developer doesn't have to get + * the message translated if it's in a language they don't happen + * to understand. Then again, we're including the error number, + * so the developer can just look that up. + */ + retval = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_MAX_WIDTH_MASK, + NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR)&utf16_message, ERRBUF_SIZE, NULL); + if (retval == 0) { + /* Failed. */ + tempmsg = ws_strdup_printf("Couldn't get error message for error (%lu) (because %lu)", + error, GetLastError()); + msg = g_intern_string(tempmsg); + g_free(tempmsg); + return msg; + } + + utf8_message = g_utf16_to_utf8(utf16_message, -1, NULL, NULL, NULL); + LocalFree(utf16_message); + if (utf8_message == NULL) { + /* Conversion failed. */ + tempmsg = ws_strdup_printf("Couldn't convert error message for error to UTF-8 (%lu) (because %lu)", + error, GetLastError()); + msg = g_intern_string(tempmsg); + g_free(tempmsg); + return msg; + } + tempmsg = ws_strdup_printf("%s (%lu)", utf8_message, error); + g_free(utf8_message); + msg = g_intern_string(tempmsg); + g_free(tempmsg); + return msg; +} + +/* + * Generate a string for a Win32 exception code. + */ +const char * +win32strexception(DWORD exception) +{ + static char errbuf[ERRBUF_SIZE+1]; + static const struct exception_msg { + DWORD code; + char *msg; + } exceptions[] = { + { EXCEPTION_ACCESS_VIOLATION, "Access violation" }, + { EXCEPTION_ARRAY_BOUNDS_EXCEEDED, "Array bounds exceeded" }, + { EXCEPTION_BREAKPOINT, "Breakpoint" }, + { EXCEPTION_DATATYPE_MISALIGNMENT, "Data type misalignment" }, + { EXCEPTION_FLT_DENORMAL_OPERAND, "Denormal floating-point operand" }, + { EXCEPTION_FLT_DIVIDE_BY_ZERO, "Floating-point divide by zero" }, + { EXCEPTION_FLT_INEXACT_RESULT, "Floating-point inexact result" }, + { EXCEPTION_FLT_INVALID_OPERATION, "Invalid floating-point operation" }, + { EXCEPTION_FLT_OVERFLOW, "Floating-point overflow" }, + { EXCEPTION_FLT_STACK_CHECK, "Floating-point stack check" }, + { EXCEPTION_FLT_UNDERFLOW, "Floating-point underflow" }, + { EXCEPTION_GUARD_PAGE, "Guard page violation" }, + { EXCEPTION_ILLEGAL_INSTRUCTION, "Illegal instruction" }, + { EXCEPTION_IN_PAGE_ERROR, "Page-in error" }, + { EXCEPTION_INT_DIVIDE_BY_ZERO, "Integer divide by zero" }, + { EXCEPTION_INT_OVERFLOW, "Integer overflow" }, + { EXCEPTION_INVALID_DISPOSITION, "Invalid disposition" }, + { EXCEPTION_INVALID_HANDLE, "Invalid handle" }, + { EXCEPTION_NONCONTINUABLE_EXCEPTION, "Non-continuable exception" }, + { EXCEPTION_PRIV_INSTRUCTION, "Privileged instruction" }, + { EXCEPTION_SINGLE_STEP, "Single-step complete" }, + { EXCEPTION_STACK_OVERFLOW, "Stack overflow" }, + { 0, NULL } + }; +#define N_EXCEPTIONS (sizeof exceptions / sizeof exceptions[0]) + + for (size_t i = 0; i < N_EXCEPTIONS; i++) { + if (exceptions[i].code == exception) + return exceptions[i].msg; + } + snprintf(errbuf, sizeof errbuf, "Exception 0x%08lx", exception); + return errbuf; +} + +// This appears to be the closest equivalent to SIGPIPE on Windows. +// https://devblogs.microsoft.com/oldnewthing/?p=2433 +// https://stackoverflow.com/a/53214/82195 + +static void win32_kill_child_on_exit(HANDLE child_handle) { + static HANDLE cjo_handle = NULL; + if (!cjo_handle) { + cjo_handle = CreateJobObject(NULL, NULL); + + if (!cjo_handle) { + ws_log(LOG_DOMAIN_CAPTURE, LOG_LEVEL_DEBUG, "Could not create child cleanup job object: %s", + win32strerror(GetLastError())); + return; + } + + JOBOBJECT_EXTENDED_LIMIT_INFORMATION cjo_jel_info = { 0 }; + cjo_jel_info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + BOOL sijo_ret = SetInformationJobObject(cjo_handle, JobObjectExtendedLimitInformation, + &cjo_jel_info, sizeof(cjo_jel_info)); + if (!sijo_ret) { + ws_log(LOG_DOMAIN_CAPTURE, LOG_LEVEL_DEBUG, "Could not set child cleanup limits: %s", + win32strerror(GetLastError())); + } + } + + BOOL aptjo_ret = AssignProcessToJobObject(cjo_handle, child_handle); + if (!aptjo_ret) { + ws_log(LOG_DOMAIN_CAPTURE, LOG_LEVEL_DEBUG, "Could not assign child cleanup process: %s", + win32strerror(GetLastError())); + } +} + +BOOL win32_create_process(const char *application_name, const char *command_line, LPSECURITY_ATTRIBUTES process_attributes, LPSECURITY_ATTRIBUTES thread_attributes, size_t n_inherit_handles, HANDLE *inherit_handles, DWORD creation_flags, LPVOID environment, const char *current_directory, LPSTARTUPINFO startup_info, LPPROCESS_INFORMATION process_information) +{ + gunichar2 *wappname = NULL, *wcurrentdirectory = NULL; + gunichar2 *wcommandline = g_utf8_to_utf16(command_line, -1, NULL, NULL, NULL); + LPPROC_THREAD_ATTRIBUTE_LIST attribute_list = NULL; + STARTUPINFOEX startup_infoex; + size_t i; + // CREATE_SUSPENDED: Suspend the child so that we can cleanly call + // AssignProcessToJobObject. + DWORD wcreationflags = creation_flags|CREATE_SUSPENDED; + // CREATE_BREAKAWAY_FROM_JOB: The main application might be associated with a job, + // e.g. if we're running under "Run As", ConEmu, or Visual Studio. On Windows + // <= 7 our child process needs to break away from it so that we can cleanly + // call AssignProcessToJobObject on *our* job. + // Windows >= 8 supports nested jobs so this isn't necessary there. + // https://blogs.msdn.microsoft.com/winsdk/2014/09/22/job-object-insanity/ + // + if (! IsWindowsVersionOrGreater(6, 2, 0)) { // Windows 8 + wcreationflags |= CREATE_BREAKAWAY_FROM_JOB; + } + + if (application_name) { + wappname = g_utf8_to_utf16(application_name, -1, NULL, NULL, NULL); + } + if (current_directory) { + wcurrentdirectory = g_utf8_to_utf16(current_directory, -1, NULL, NULL, NULL); + } + if (n_inherit_handles > 0) { + size_t attr_size = 0; + BOOL success; + success = InitializeProcThreadAttributeList(NULL, 1, 0, &attr_size); + if (success || (GetLastError() == ERROR_INSUFFICIENT_BUFFER)) { + attribute_list = g_malloc(attr_size); + success = InitializeProcThreadAttributeList(attribute_list, 1, 0, &attr_size); + } + if (success && (attribute_list != NULL)) { + success = UpdateProcThreadAttribute(attribute_list, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, + inherit_handles, n_inherit_handles * sizeof(HANDLE), NULL, NULL); + } + if (!success && (attribute_list != NULL)) { + DeleteProcThreadAttributeList(attribute_list); + g_free(attribute_list); + attribute_list = NULL; + } + } + memset(&startup_infoex, 0, sizeof(startup_infoex)); + startup_infoex.StartupInfo = *startup_info; + startup_infoex.StartupInfo.cb = sizeof(startup_infoex); + startup_infoex.lpAttributeList = attribute_list; + wcreationflags |= EXTENDED_STARTUPINFO_PRESENT; + for (i = 0; i < n_inherit_handles; i++) { + SetHandleInformation(inherit_handles[i], HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); + } + BOOL cp_res = CreateProcess(wappname, wcommandline, process_attributes, thread_attributes, + (n_inherit_handles > 0) ? true : false, wcreationflags, environment, wcurrentdirectory, + &startup_infoex.StartupInfo, process_information); + /* While this function makes the created process inherit only the explicitly + * listed handles, there can be other functions (in 3rd party libraries) + * that create processes inheriting all inheritable handles. To minimize + * number of unwanted handle duplicates (handle duplicate can extend object + * lifetime, e.g. pipe write end) created that way clear the inherit flag. + */ + for (i = 0; i < n_inherit_handles; i++) { + SetHandleInformation(inherit_handles[i], HANDLE_FLAG_INHERIT, 0); + } + if (cp_res) { + win32_kill_child_on_exit(process_information->hProcess); + ResumeThread(process_information->hThread); + } + // XXX Else try again if CREATE_BREAKAWAY_FROM_JOB and GetLastError() == ERROR_ACCESS_DENIED? + + if (attribute_list) { + DeleteProcThreadAttributeList(attribute_list); + g_free(attribute_list); + } + g_free(wappname); + g_free(wcommandline); + g_free(wcurrentdirectory); + return cp_res; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/win32-utils.h b/wsutil/win32-utils.h new file mode 100644 index 00000000..44471c59 --- /dev/null +++ b/wsutil/win32-utils.h @@ -0,0 +1,95 @@ +/* win32-utils.h + * Windows utility definitions + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 2006 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WIN32UTIL_H__ +#define __WIN32UTIL_H__ + +#include <wireshark.h> + +#include <windows.h> + +/** + * @file + * Unicode convenience routines. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** Quote the argument element if necessary, so that it will get + * reconstructed correctly in the C runtime startup code. Note that + * the unquoting algorithm in the C runtime is really weird, and + * rather different than what Unix shells do. See stdargv.c in the C + * runtime sources (in the Platform SDK, in src/crt). + * + * Stolen from GLib's protect_argv(), an internal routine that quotes + * string in an argument list so that they arguments will be handled + * correctly in the command-line string passed to CreateProcess() + * if that string is constructed by gluing those strings together. + * + * @param argv The string to be quoted. May be NULL. + * @return The string quoted to be used by CreateProcess + */ +WS_DLL_PUBLIC +char * protect_arg (const char *argv); + +/** Generate a string for a Windows error. + * + * @param error The Windows error code + * @return a localized UTF-8 string containing the corresponding error message + */ +WS_DLL_PUBLIC +const char * win32strerror(DWORD error); + +/** Generate a string for a Win32 exception code. + * + * @param exception The exception code + * @return a non-localized string containing the error message + */ +WS_DLL_PUBLIC +const char * win32strexception(DWORD exception); + +/** + * @brief ws_pipe_create_process Create a process and assign it to the main application + * job object so that it will be killed when the main application exits. + * + * In order to limit unwanted handle duplicates in subprocesses all handles should be + * created as not inheritable and passed in the inherit_handles array. This function + * marks the handles as inheritable for as short time as possible. Note that handles + * passed to this function will have the inheritable flag cleared on exit. Processes + * created with this function inherit only the provided handles. + * + * @param application_name Application name. Will be converted to its UTF-16 equivalent or NULL. + * @param command_line Command line. Will be converted to its UTF-16 equivalent. + * @param process_attributes Same as CreateProcess. + * @param thread_attributes Same as CreateProcess. + * @param n_inherit_handles Number of handles the child process will inherit. + * @param inherit_handles Handles the child process will inherit. + * @param creation_flags Will be ORed with CREATE_SUSPENDED|CREATE_BREAKAWAY_FROM_JOB. + * @param environment Same as CreateProcess. + * @param current_directory Current directory. Will be converted to its UTF-16 equivalent or NULL. + * @param startup_info Same as CreateProcess. + * @param process_information Same as CreateProcess. + * @return + */ +WS_DLL_PUBLIC +BOOL win32_create_process(const char *application_name, const char *command_line, + LPSECURITY_ATTRIBUTES process_attributes, LPSECURITY_ATTRIBUTES thread_attributes, + size_t n_inherit_handles, HANDLE *inherit_handles, + DWORD creation_flags, LPVOID environment, + const char *current_directory, LPSTARTUPINFO startup_info, LPPROCESS_INFORMATION process_information +); + +#ifdef __cplusplus +} +#endif + +#endif /* __WIN32UTIL_H__ */ diff --git a/wsutil/wmem/wmem-int.h b/wsutil/wmem/wmem-int.h new file mode 100644 index 00000000..6c4e4985 --- /dev/null +++ b/wsutil/wmem/wmem-int.h @@ -0,0 +1,32 @@ +/** @file + * + * Internal definitions for the Wireshark Memory Manager + * Copyright 2012, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WMEM_INT_H__ +#define __WMEM_INT_H__ + +#include <glib.h> +#include <wsutil/ws_assert.h> + +#endif /* __WMEM_INT_H__ */ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem.h b/wsutil/wmem/wmem.h new file mode 100644 index 00000000..81429151 --- /dev/null +++ b/wsutil/wmem/wmem.h @@ -0,0 +1,43 @@ +/** @file + * + * Definitions for the Wireshark Memory Manager + * Copyright 2012, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WMEM_H__ +#define __WMEM_H__ + +#include "wmem_array.h" +#include "wmem_core.h" +#include "wmem_list.h" +#include "wmem_map.h" +#include "wmem_miscutl.h" +#include "wmem_multimap.h" +#include "wmem_queue.h" +#include "wmem_stack.h" +#include "wmem_strbuf.h" +#include "wmem_strutl.h" +#include "wmem_tree.h" +#include "wmem_interval_tree.h" +#include "wmem_user_cb.h" + +#endif /* __WMEM_H__ */ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_allocator.h b/wsutil/wmem/wmem_allocator.h new file mode 100644 index 00000000..8890054a --- /dev/null +++ b/wsutil/wmem/wmem_allocator.h @@ -0,0 +1,64 @@ +/** @file + * + * Definitions for the Wireshark Memory Manager Allocator + * Copyright 2012, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WMEM_ALLOCATOR_H__ +#define __WMEM_ALLOCATOR_H__ + +#include <glib.h> +#include <string.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +struct _wmem_user_cb_container_t; + +/* See section "4. Internal Design" of doc/README.wmem for details + * on this structure */ +struct _wmem_allocator_t { + /* Consumer functions */ + void *(*walloc)(void *private_data, const size_t size); + void (*wfree)(void *private_data, void *ptr); + void *(*wrealloc)(void *private_data, void *ptr, const size_t size); + + /* Producer/Manager functions */ + void (*free_all)(void *private_data); + void (*gc)(void *private_data); + void (*cleanup)(void *private_data); + + /* Callback List */ + struct _wmem_user_cb_container_t *callbacks; + + /* Implementation details */ + void *private_data; + enum _wmem_allocator_type_t type; + bool in_scope; +}; + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __WMEM_ALLOCATOR_H__ */ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_allocator_block.c b/wsutil/wmem/wmem_allocator_block.c new file mode 100644 index 00000000..b8a0f598 --- /dev/null +++ b/wsutil/wmem/wmem_allocator_block.c @@ -0,0 +1,1125 @@ +/* wmem_allocator_block.c + * Wireshark Memory Manager Large-Block Allocator (version 3) + * Copyright 2013, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <stdio.h> +#include <string.h> + +#include <glib.h> + +#include "wmem_core.h" +#include "wmem_allocator.h" +#include "wmem_allocator_block.h" + +/* This has turned into a very interesting excercise in algorithms and data + * structures. + * + * HISTORY + * + * Version 1 of this allocator was embedded in the original emem framework. It + * didn't have to handle realloc or free, so it was very simple: it just grabbed + * a block from the OS and served allocations sequentially out of that until it + * ran out, then allocated a new block. The old block was never revisited, so + * it generally had a bit of wasted space at the end, but the waste was + * small enough that it was simply ignored. This allocator provided very fast + * constant-time allocation for any request that didn't require a new block from + * the OS, and that cost could be amortized away. + * + * Version 2 of this allocator was prompted by the need to support realloc and + * free in wmem. The original version simply didn't save enough metadata to do + * this, so I added a layer on top to make it possible. The primary principle + * was the same (allocate sequentially out of big blocks) with a bit of extra + * magic. Allocations were still fast constant-time, and frees were as well. + * Large parts of that design are still present in this one, but for more + * details see older versions of this file from git or svn. + * + * Version 3 of this allocator was written to address some issues that + * eventually showed up with version 2 under real-world usage. Specifically, + * version 2 dealt very poorly with memory fragmentation, almost never reusing + * freed blocks and choosing to just keep allocating from the master block + * instead. This led to particularly poor behaviour under the tick-tock loads + * (alloc/free/alloc/free or alloc/alloc/free/alloc/alloc/free/ or ...) that + * showed up in a couple of different protocol dissectors (TCP, Kafka). + * + * BLOCKS AND CHUNKS + * + * As in previous versions, allocations typically happen sequentially out of + * large OS-level blocks. Each block has a short embedded header used to + * maintain a doubly-linked list of all blocks (used or not) currently owned by + * the allocator. Each block is divided into chunks, which represent allocations + * and free sections (a block is initialized with one large, free, chunk). Each + * chunk is prefixed with a wmem_block_chunk_t structure, which is a short + * metadata header (8 bytes, regardless of 32 or 64-bit architecture unless + * alignment requires it to be padded) that contains the length of the chunk, + * the length of the previous chunk, a flag marking the chunk as free or used, + * and a flag marking the last chunk in a block. This serves to implement an + * inline sequential doubly-linked list of all the chunks in each block. A block + * with three chunks might look something like this: + * + * 0 _________________________ + * ^ ___________ / ______________ \ __________ + * ||---||--|-----/-----------||--------/--------------||--\-----/----------|| + * ||hdr|| prv | len | body || prv | len | body || prv | len | body || + * ||---||--------------------||--/--------------------||-------------------|| + * \______________________/ + * + * + * When allocating, a free chunk is found (more on that later) and split into + * two chunks: the first of the requested size and the second containing any + * remaining free. The first is marked used and returned to the caller. + * + * When freeing, the chunk in question is marked as free. Its neighbouring + * chunks are then checked; if either of them are free, the consecutive free + * chunks are merged into a single larger free chunk. Induction can show that + * applying this operation consistently prevents us ever having consecutive + * free chunks. + * + * Free chunks (because they are not being used for anything else) each store an + * additional pair of pointers (see the wmem_block_free_t structure) that form + * the backbone of the data structures used to track free chunks. + * + * MASTER AND RECYCLER + * + * The extra pair of pointers in free chunks are used to build two doubly-linked + * lists: the master and the recycler. The recycler is circular, the master is + * a stack. + * + * The master stack is only populated by chunks from new OS-level blocks, + * so every chunk in this list is guaranteed to be able to serve any allocation + * request (the allocator will not serve requests larger than its block size). + * The chunk at the head of the master list shrinks as it serves requests. When + * it is too small to serve the current request, it is popped and inserted into + * the recycler. If the master list is empty, a new OS-level block is allocated, + * and its chunk is pushed onto the master stack. + * + * The recycler is populated by 'leftovers' from the master, as well as any + * chunks that were returned to the allocator via a call to free(). Although the + * recycler is circular, we will refer to the element referenced from the + * allocator as the 'head' of the list for convenience. The primary operation on + * the recycler is called cycling it. In this operation, the head is compared + * with its clockwise neighbour. If the neighbour is as large or larger, it + * becomes the head (the list rotates counter-clockwise). If the neighbour is + * smaller, then it is removed from its location and inserted as the counter- + * clockwise neighbour of the head (the list still rotates counter-clockwise, + * but the head element is held fixed while the rest of the list spins). This + * operation has the following properties: + * - fast constant time + * - once the largest chunk is at the head, it remains at the head + * - more iterations increases the probability that the largest chunk will be + * the head (for a list with n items, n iterations guarantees that the + * largest chunk will be the head). + * + * ALLOCATING + * + * When an allocation request is received, the allocator first attempts to + * satisfy it with the chunk at the head of the recycler. If that does not + * succeed, the request is satisfied by the master list instead. Regardless of + * which chunk satisfied the request, the recycler is always cycled. + */ + +/* https://mail.gnome.org/archives/gtk-devel-list/2004-December/msg00091.html + * The 2*sizeof(size_t) alignment here is borrowed from GNU libc, so it should + * be good most everywhere. It is more conservative than is needed on some + * 64-bit platforms, but ia64 does require a 16-byte alignment. The SIMD + * extensions for x86 and ppc32 would want a larger alignment than this, but + * we don't need to do better than malloc. + */ +#define WMEM_ALIGN_AMOUNT (2 * sizeof (size_t)) +#define WMEM_ALIGN_SIZE(SIZE) ((~(WMEM_ALIGN_AMOUNT-1)) & \ + ((SIZE) + (WMEM_ALIGN_AMOUNT-1))) + +/* When required, allocate more memory from the OS in chunks of this size. + * 8MB is a pretty arbitrary value - it's big enough that it should last a while + * and small enough that a mostly-unused one doesn't waste *too* much. It's + * also a nice power of two, of course. */ +#define WMEM_BLOCK_SIZE (8 * 1024 * 1024) + +/* The header for an entire OS-level 'block' of memory */ +typedef struct _wmem_block_hdr_t { + struct _wmem_block_hdr_t *prev, *next; +} wmem_block_hdr_t; + +/* The header for a single 'chunk' of memory as returned from alloc/realloc. + * The 'jumbo' flag indicates an allocation larger than a normal-sized block + * would be capable of serving. If this is set, it is the only chunk in the + * block and the other chunk header fields are irrelevant. + */ +typedef struct _wmem_block_chunk_t { + uint32_t prev; + + /* flags */ + uint32_t last:1; + uint32_t used:1; + uint32_t jumbo:1; + + uint32_t len:29; +} wmem_block_chunk_t; + +/* Handy macros for navigating the chunks in a block as if they were a + * doubly-linked list. */ +#define WMEM_CHUNK_PREV(CHUNK) ((CHUNK)->prev \ + ? ((wmem_block_chunk_t*)(((uint8_t*)(CHUNK)) - (CHUNK)->prev)) \ + : NULL) + +#define WMEM_CHUNK_NEXT(CHUNK) ((CHUNK)->last \ + ? NULL \ + : ((wmem_block_chunk_t*)(((uint8_t*)(CHUNK)) + (CHUNK)->len))) + +#define WMEM_CHUNK_HEADER_SIZE WMEM_ALIGN_SIZE(sizeof(wmem_block_chunk_t)) + +#define WMEM_BLOCK_MAX_ALLOC_SIZE (WMEM_BLOCK_SIZE - \ + (WMEM_BLOCK_HEADER_SIZE + WMEM_CHUNK_HEADER_SIZE)) + +/* other handy chunk macros */ +#define WMEM_CHUNK_TO_DATA(CHUNK) ((void*)((uint8_t*)(CHUNK) + WMEM_CHUNK_HEADER_SIZE)) +#define WMEM_DATA_TO_CHUNK(DATA) ((wmem_block_chunk_t*)((uint8_t*)(DATA) - WMEM_CHUNK_HEADER_SIZE)) +#define WMEM_CHUNK_DATA_LEN(CHUNK) ((CHUNK)->len - WMEM_CHUNK_HEADER_SIZE) + +/* some handy block macros */ +#define WMEM_BLOCK_HEADER_SIZE WMEM_ALIGN_SIZE(sizeof(wmem_block_hdr_t)) +#define WMEM_BLOCK_TO_CHUNK(BLOCK) ((wmem_block_chunk_t*)((uint8_t*)(BLOCK) + WMEM_BLOCK_HEADER_SIZE)) +#define WMEM_CHUNK_TO_BLOCK(CHUNK) ((wmem_block_hdr_t*)((uint8_t*)(CHUNK) - WMEM_BLOCK_HEADER_SIZE)) + +/* This is what the 'data' section of a chunk contains if it is free. */ +typedef struct _wmem_block_free_t { + wmem_block_chunk_t *prev, *next; +} wmem_block_free_t; + +/* Handy macro for accessing the free-header of a chunk */ +#define WMEM_GET_FREE(CHUNK) ((wmem_block_free_t*)WMEM_CHUNK_TO_DATA(CHUNK)) + +typedef struct _wmem_block_allocator_t { + wmem_block_hdr_t *block_list; + wmem_block_chunk_t *master_head; + wmem_block_chunk_t *recycler_head; +} wmem_block_allocator_t; + +/* DEBUG AND TEST */ +static int +wmem_block_verify_block(wmem_block_hdr_t *block) +{ + int total_free_space = 0; + uint32_t total_len; + wmem_block_chunk_t *chunk; + + chunk = WMEM_BLOCK_TO_CHUNK(block); + total_len = WMEM_BLOCK_HEADER_SIZE; + + if (chunk->jumbo) { + /* We can tell nothing else about jumbo chunks except that they are + * always used. */ + return 0; + } + + g_assert_true(chunk->prev == 0); + + do { + total_len += chunk->len; + + g_assert_true(chunk->len >= WMEM_CHUNK_HEADER_SIZE); + g_assert_true(!chunk->jumbo); + + if (WMEM_CHUNK_NEXT(chunk)) { + g_assert_true(chunk->len == WMEM_CHUNK_NEXT(chunk)->prev); + } + + if (!chunk->used && + WMEM_CHUNK_DATA_LEN(chunk) >= sizeof(wmem_block_free_t)) { + + total_free_space += chunk->len; + + if (!chunk->last) { + g_assert_true(WMEM_GET_FREE(chunk)->next); + g_assert_true(WMEM_GET_FREE(chunk)->prev); + } + } + + chunk = WMEM_CHUNK_NEXT(chunk); + } while (chunk); + + g_assert_true(total_len == WMEM_BLOCK_SIZE); + + return total_free_space; +} + +static int +wmem_block_verify_master_list(wmem_block_allocator_t *allocator) +{ + wmem_block_chunk_t *cur; + wmem_block_free_t *cur_free; + int free_space = 0; + + cur = allocator->master_head; + if (!cur) { + return 0; + } + + g_assert_true(WMEM_GET_FREE(cur)->prev == NULL); + + while (cur) { + free_space += cur->len; + + cur_free = WMEM_GET_FREE(cur); + + g_assert_true(! cur->used); + + if (cur_free->next) { + g_assert_true(WMEM_GET_FREE(cur_free->next)->prev == cur); + } + + if (cur != allocator->master_head) { + g_assert_true(cur->len == WMEM_BLOCK_SIZE); + } + + cur = cur_free->next; + } + + return free_space; +} + +static int +wmem_block_verify_recycler(wmem_block_allocator_t *allocator) +{ + wmem_block_chunk_t *cur; + wmem_block_free_t *cur_free; + int free_space = 0; + + cur = allocator->recycler_head; + if (!cur) { + return 0; + } + + do { + free_space += cur->len; + + cur_free = WMEM_GET_FREE(cur); + + g_assert_true(! cur->used); + + g_assert_true(cur_free->prev); + g_assert_true(cur_free->next); + + g_assert_true(WMEM_GET_FREE(cur_free->prev)->next == cur); + g_assert_true(WMEM_GET_FREE(cur_free->next)->prev == cur); + + cur = cur_free->next; + } while (cur != allocator->recycler_head); + + return free_space; +} + +void +wmem_block_verify(wmem_allocator_t *allocator) +{ + wmem_block_hdr_t *cur; + wmem_block_allocator_t *private_allocator; + int master_free, recycler_free, chunk_free = 0; + + /* Normally it would be bad for an allocator helper function to depend + * on receiving the right type of allocator, but this is for testing only + * and is not part of any real API. */ + g_assert_true(allocator->type == WMEM_ALLOCATOR_BLOCK); + + private_allocator = (wmem_block_allocator_t*) allocator->private_data; + + if (private_allocator->block_list == NULL) { + g_assert_true(! private_allocator->master_head); + g_assert_true(! private_allocator->recycler_head); + return; + } + + master_free = wmem_block_verify_master_list(private_allocator); + recycler_free = wmem_block_verify_recycler(private_allocator); + + cur = private_allocator->block_list; + g_assert_true(cur->prev == NULL); + while (cur) { + if (cur->next) { + g_assert_true(cur->next->prev == cur); + } + chunk_free += wmem_block_verify_block(cur); + cur = cur->next; + } + + g_assert_true(chunk_free == master_free + recycler_free); +} + +/* MASTER/RECYCLER HELPERS */ + +/* Cycles the recycler. See the design notes at the top of this file for more + * details. */ +static void +wmem_block_cycle_recycler(wmem_block_allocator_t *allocator) +{ + wmem_block_chunk_t *chunk; + wmem_block_free_t *free_chunk; + + chunk = allocator->recycler_head; + + if (chunk == NULL) { + return; + } + + free_chunk = WMEM_GET_FREE(chunk); + + if (free_chunk->next->len < chunk->len) { + /* Hold the current head fixed during rotation. */ + WMEM_GET_FREE(free_chunk->next)->prev = free_chunk->prev; + WMEM_GET_FREE(free_chunk->prev)->next = free_chunk->next; + + free_chunk->prev = free_chunk->next; + free_chunk->next = WMEM_GET_FREE(free_chunk->next)->next; + + WMEM_GET_FREE(free_chunk->next)->prev = chunk; + WMEM_GET_FREE(free_chunk->prev)->next = chunk; + } + else { + /* Just rotate everything. */ + allocator->recycler_head = free_chunk->next; + } +} + +/* Adds a chunk from the recycler. */ +static void +wmem_block_add_to_recycler(wmem_block_allocator_t *allocator, + wmem_block_chunk_t *chunk) +{ + wmem_block_free_t *free_chunk; + + if (WMEM_CHUNK_DATA_LEN(chunk) < sizeof(wmem_block_free_t)) { + return; + } + + free_chunk = WMEM_GET_FREE(chunk); + + if (! allocator->recycler_head) { + /* First one */ + free_chunk->next = chunk; + free_chunk->prev = chunk; + allocator->recycler_head = chunk; + } + else { + free_chunk->next = allocator->recycler_head; + free_chunk->prev = WMEM_GET_FREE(allocator->recycler_head)->prev; + + WMEM_GET_FREE(free_chunk->next)->prev = chunk; + WMEM_GET_FREE(free_chunk->prev)->next = chunk; + + if (chunk->len > allocator->recycler_head->len) { + allocator->recycler_head = chunk; + } + } +} + +/* Removes a chunk from the recycler. */ +static void +wmem_block_remove_from_recycler(wmem_block_allocator_t *allocator, + wmem_block_chunk_t *chunk) +{ + wmem_block_free_t *free_chunk; + + free_chunk = WMEM_GET_FREE(chunk); + + if (free_chunk->prev == chunk && free_chunk->next == chunk) { + /* Only one item in recycler, just empty it. */ + allocator->recycler_head = NULL; + } + else { + /* Two or more items, usual doubly-linked-list removal. It's circular + * so we don't need to worry about null-checking anything, which is + * nice. */ + WMEM_GET_FREE(free_chunk->prev)->next = free_chunk->next; + WMEM_GET_FREE(free_chunk->next)->prev = free_chunk->prev; + if (allocator->recycler_head == chunk) { + allocator->recycler_head = free_chunk->next; + } + } +} + +/* Pushes a chunk onto the master stack. */ +static void +wmem_block_push_master(wmem_block_allocator_t *allocator, + wmem_block_chunk_t *chunk) +{ + wmem_block_free_t *free_chunk; + + free_chunk = WMEM_GET_FREE(chunk); + free_chunk->prev = NULL; + free_chunk->next = allocator->master_head; + if (free_chunk->next) { + WMEM_GET_FREE(free_chunk->next)->prev = chunk; + } + allocator->master_head = chunk; +} + +/* Removes the top chunk from the master stack. */ +static void +wmem_block_pop_master(wmem_block_allocator_t *allocator) +{ + wmem_block_chunk_t *chunk; + wmem_block_free_t *free_chunk; + + chunk = allocator->master_head; + + free_chunk = WMEM_GET_FREE(chunk); + + allocator->master_head = free_chunk->next; + if (free_chunk->next) { + WMEM_GET_FREE(free_chunk->next)->prev = NULL; + } +} + +/* CHUNK HELPERS */ + +/* Takes a free chunk and checks the chunks to its immediate right and left in + * the block. If they are also free, the contigous free chunks are merged into + * a single free chunk. The resulting chunk ends up in either the master list or + * the recycler, depending on where the merged chunks were originally. + */ +static void +wmem_block_merge_free(wmem_block_allocator_t *allocator, + wmem_block_chunk_t *chunk) +{ + wmem_block_chunk_t *tmp; + wmem_block_chunk_t *left_free = NULL; + wmem_block_chunk_t *right_free = NULL; + + /* Check the chunk to our right. If it is free, merge it into our current + * chunk. If it is big enough to hold a free-header, save it for later (we + * need to know about the left chunk before we decide what goes where). */ + tmp = WMEM_CHUNK_NEXT(chunk); + if (tmp && !tmp->used) { + if (WMEM_CHUNK_DATA_LEN(tmp) >= sizeof(wmem_block_free_t)) { + right_free = tmp; + } + chunk->len += tmp->len; + chunk->last = tmp->last; + } + + /* Check the chunk to our left. If it is free, merge our current chunk into + * it (thus chunk = tmp). As before, save it if it has enough space to + * hold a free-header. */ + tmp = WMEM_CHUNK_PREV(chunk); + if (tmp && !tmp->used) { + if (WMEM_CHUNK_DATA_LEN(tmp) >= sizeof(wmem_block_free_t)) { + left_free = tmp; + } + tmp->len += chunk->len; + tmp->last = chunk->last; + chunk = tmp; + } + + /* The length of our chunk may have changed. If we have a chunk following, + * update its 'prev' count. */ + if (!chunk->last) { + WMEM_CHUNK_NEXT(chunk)->prev = chunk->len; + } + + /* Now that the chunk headers are merged and consistent, we need to figure + * out what goes where in which free list. */ + if (right_free && right_free == allocator->master_head) { + /* If we merged right, and that chunk was the head of the master list, + * then we leave the resulting chunk at the head of the master list. */ + wmem_block_free_t *moved; + if (left_free) { + wmem_block_remove_from_recycler(allocator, left_free); + } + moved = WMEM_GET_FREE(chunk); + moved->prev = NULL; + moved->next = WMEM_GET_FREE(right_free)->next; + allocator->master_head = chunk; + if (moved->next) { + WMEM_GET_FREE(moved->next)->prev = chunk; + } + } + else { + /* Otherwise, we remove the right-merged chunk (if there was one) from + * the recycler. Then, if we merged left we have nothing to do, since + * that recycler entry is still valid. If not, we add the chunk. */ + if (right_free) { + wmem_block_remove_from_recycler(allocator, right_free); + } + if (!left_free) { + wmem_block_add_to_recycler(allocator, chunk); + } + } +} + +/* Takes an unused chunk and a size, and splits it into two chunks if possible. + * The first chunk (at the same address as the input chunk) is guaranteed to + * hold at least `size` bytes of data, and to not be in either the master or + * recycler lists. + * + * The second chunk gets whatever data is left over. It is marked unused and + * replaces the input chunk in whichever list it originally inhabited. */ +static void +wmem_block_split_free_chunk(wmem_block_allocator_t *allocator, + wmem_block_chunk_t *chunk, + const size_t size) +{ + wmem_block_chunk_t *extra; + wmem_block_free_t *old_blk, *new_blk; + size_t aligned_size, available; + bool last; + + aligned_size = WMEM_ALIGN_SIZE(size) + WMEM_CHUNK_HEADER_SIZE; + + if (WMEM_CHUNK_DATA_LEN(chunk) < aligned_size + sizeof(wmem_block_free_t)) { + /* If the available space is not enought to store all of + * (hdr + requested size + alignment padding + hdr + free-header) then + * just remove the current chunk from the free list and return, since we + * can't usefully split it. */ + if (chunk == allocator->master_head) { + wmem_block_pop_master(allocator); + } + else if (WMEM_CHUNK_DATA_LEN(chunk) >= sizeof(wmem_block_free_t)) { + wmem_block_remove_from_recycler(allocator, chunk); + } + return; + } + + /* preserve a few values from chunk that we'll need to manipulate */ + last = chunk->last; + available = chunk->len - aligned_size; + + /* set new values for chunk */ + chunk->len = (uint32_t) aligned_size; + chunk->last = false; + + /* with chunk's values set, we can use the standard macro to calculate + * the location and size of the new free chunk */ + extra = WMEM_CHUNK_NEXT(chunk); + + /* Now we move the free chunk's address without changing its location + * in whichever list it is in. + * + * Note that the new chunk header 'extra' may overlap the old free header, + * so we have to copy the free header before we write anything to extra. + */ + old_blk = WMEM_GET_FREE(chunk); + new_blk = WMEM_GET_FREE(extra); + + if (allocator->master_head == chunk) { + new_blk->prev = old_blk->prev; + new_blk->next = old_blk->next; + + if (old_blk->next) { + WMEM_GET_FREE(old_blk->next)->prev = extra; + } + + allocator->master_head = extra; + } + else { + if (old_blk->prev == chunk) { + new_blk->prev = extra; + new_blk->next = extra; + } + else { + new_blk->prev = old_blk->prev; + new_blk->next = old_blk->next; + + WMEM_GET_FREE(old_blk->prev)->next = extra; + WMEM_GET_FREE(old_blk->next)->prev = extra; + } + + if (allocator->recycler_head == chunk) { + allocator->recycler_head = extra; + } + } + + /* Now that we've copied over the free-list stuff (which may have overlapped + * with our new chunk header) we can safely write our new chunk header. */ + extra->len = (uint32_t) available; + extra->last = last; + extra->prev = chunk->len; + extra->used = false; + extra->jumbo = false; + + /* Correctly update the following chunk's back-pointer */ + if (!last) { + WMEM_CHUNK_NEXT(extra)->prev = extra->len; + } +} + +/* Takes a used chunk and a size, and splits it into two chunks if possible. + * The first chunk can hold at least `size` bytes of data, while the second gets + * whatever's left over. The second is marked as unused and is added to the + * recycler. */ +static void +wmem_block_split_used_chunk(wmem_block_allocator_t *allocator, + wmem_block_chunk_t *chunk, + const size_t size) +{ + wmem_block_chunk_t *extra; + size_t aligned_size, available; + bool last; + + aligned_size = WMEM_ALIGN_SIZE(size) + WMEM_CHUNK_HEADER_SIZE; + + if (aligned_size > WMEM_CHUNK_DATA_LEN(chunk)) { + /* in this case we don't have enough space to really split it, so + * it's basically a no-op */ + return; + } + /* otherwise, we have room to split it, though the remaining free chunk + * may still not be usefully large */ + + /* preserve a few values from chunk that we'll need to manipulate */ + last = chunk->last; + available = chunk->len - aligned_size; + + /* set new values for chunk */ + chunk->len = (uint32_t) aligned_size; + chunk->last = false; + + /* with chunk's values set, we can use the standard macro to calculate + * the location and size of the new free chunk */ + extra = WMEM_CHUNK_NEXT(chunk); + + /* set the new values for the chunk */ + extra->len = (uint32_t) available; + extra->last = last; + extra->prev = chunk->len; + extra->used = false; + extra->jumbo = false; + + /* Correctly update the following chunk's back-pointer */ + if (!last) { + WMEM_CHUNK_NEXT(extra)->prev = extra->len; + } + + /* Merge it to its right if possible (it can't be merged left, obviously). + * This also adds it to the recycler. */ + wmem_block_merge_free(allocator, extra); +} + +/* BLOCK HELPERS */ + +/* Add a block to the allocator's embedded doubly-linked list of OS-level blocks + * that it owns. */ +static void +wmem_block_add_to_block_list(wmem_block_allocator_t *allocator, + wmem_block_hdr_t *block) +{ + block->prev = NULL; + block->next = allocator->block_list; + if (block->next) { + block->next->prev = block; + } + allocator->block_list = block; +} + +/* Remove a block from the allocator's embedded doubly-linked list of OS-level + * blocks that it owns. */ +static void +wmem_block_remove_from_block_list(wmem_block_allocator_t *allocator, + wmem_block_hdr_t *block) +{ + if (block->prev) { + block->prev->next = block->next; + } + else { + allocator->block_list = block->next; + } + + if (block->next) { + block->next->prev = block->prev; + } +} + +/* Initializes a single unused chunk at the beginning of the block, and + * adds that chunk to the free list. */ +static void +wmem_block_init_block(wmem_block_allocator_t *allocator, + wmem_block_hdr_t *block) +{ + wmem_block_chunk_t *chunk; + + /* a new block contains one chunk, right at the beginning */ + chunk = WMEM_BLOCK_TO_CHUNK(block); + + chunk->used = false; + chunk->jumbo = false; + chunk->last = true; + chunk->prev = 0; + chunk->len = WMEM_BLOCK_SIZE - WMEM_BLOCK_HEADER_SIZE; + + /* now push that chunk onto the master list */ + wmem_block_push_master(allocator, chunk); +} + +/* Creates a new block, and initializes it. */ +static void +wmem_block_new_block(wmem_block_allocator_t *allocator) +{ + wmem_block_hdr_t *block; + + /* allocate the new block and add it to the block list */ + block = (wmem_block_hdr_t *)wmem_alloc(NULL, WMEM_BLOCK_SIZE); + wmem_block_add_to_block_list(allocator, block); + + /* initialize it */ + wmem_block_init_block(allocator, block); +} + +/* JUMBO ALLOCATIONS */ + +/* Allocates special 'jumbo' blocks for sizes that won't fit normally. */ +static void * +wmem_block_alloc_jumbo(wmem_block_allocator_t *allocator, const size_t size) +{ + wmem_block_hdr_t *block; + wmem_block_chunk_t *chunk; + + /* allocate a new block of exactly the right size */ + block = (wmem_block_hdr_t *) wmem_alloc(NULL, size + + WMEM_BLOCK_HEADER_SIZE + + WMEM_CHUNK_HEADER_SIZE); + + /* add it to the block list */ + wmem_block_add_to_block_list(allocator, block); + + /* the new block contains a single jumbo chunk */ + chunk = WMEM_BLOCK_TO_CHUNK(block); + chunk->last = true; + chunk->used = true; + chunk->jumbo = true; + chunk->len = 0; + chunk->prev = 0; + + /* and return the data pointer */ + return WMEM_CHUNK_TO_DATA(chunk); +} + +/* Frees special 'jumbo' blocks of sizes that won't fit normally. */ +static void +wmem_block_free_jumbo(wmem_block_allocator_t *allocator, + wmem_block_chunk_t *chunk) +{ + wmem_block_hdr_t *block; + + block = WMEM_CHUNK_TO_BLOCK(chunk); + + wmem_block_remove_from_block_list(allocator, block); + + wmem_free(NULL, block); +} + +/* Reallocs special 'jumbo' blocks of sizes that won't fit normally. */ +static void * +wmem_block_realloc_jumbo(wmem_block_allocator_t *allocator, + wmem_block_chunk_t *chunk, + const size_t size) +{ + wmem_block_hdr_t *block; + + block = WMEM_CHUNK_TO_BLOCK(chunk); + + block = (wmem_block_hdr_t *) wmem_realloc(NULL, block, size + + WMEM_BLOCK_HEADER_SIZE + + WMEM_CHUNK_HEADER_SIZE); + + if (block->next) { + block->next->prev = block; + } + + if (block->prev) { + block->prev->next = block; + } + else { + allocator->block_list = block; + } + + return WMEM_CHUNK_TO_DATA(WMEM_BLOCK_TO_CHUNK(block)); +} + +/* API */ + +static void * +wmem_block_alloc(void *private_data, const size_t size) +{ + wmem_block_allocator_t *allocator = (wmem_block_allocator_t*) private_data; + wmem_block_chunk_t *chunk; + + if (size > WMEM_BLOCK_MAX_ALLOC_SIZE) { + return wmem_block_alloc_jumbo(allocator, size); + } + + if (allocator->recycler_head && + WMEM_CHUNK_DATA_LEN(allocator->recycler_head) >= size) { + + /* If we can serve it from the recycler, do so. */ + chunk = allocator->recycler_head; + } + else { + if (allocator->master_head && + WMEM_CHUNK_DATA_LEN(allocator->master_head) < size) { + + /* Recycle the head of the master list if necessary. */ + chunk = allocator->master_head; + wmem_block_pop_master(allocator); + wmem_block_add_to_recycler(allocator, chunk); + } + + if (!allocator->master_head) { + /* Allocate a new block if necessary. */ + wmem_block_new_block(allocator); + } + + chunk = allocator->master_head; + } + + /* Split our chunk into two to preserve any trailing free space */ + wmem_block_split_free_chunk(allocator, chunk, size); + + /* Now cycle the recycler */ + wmem_block_cycle_recycler(allocator); + + /* mark it as used */ + chunk->used = true; + + /* and return the user's pointer */ + return WMEM_CHUNK_TO_DATA(chunk); +} + +static void +wmem_block_free(void *private_data, void *ptr) +{ + wmem_block_allocator_t *allocator = (wmem_block_allocator_t*) private_data; + wmem_block_chunk_t *chunk; + + chunk = WMEM_DATA_TO_CHUNK(ptr); + + if (chunk->jumbo) { + wmem_block_free_jumbo(allocator, chunk); + return; + } + + /* mark it as unused */ + chunk->used = false; + + /* merge it with any other free chunks adjacent to it, so that contiguous + * free space doesn't get fragmented */ + wmem_block_merge_free(allocator, chunk); + + /* Now cycle the recycler */ + wmem_block_cycle_recycler(allocator); +} + +static void * +wmem_block_realloc(void *private_data, void *ptr, const size_t size) +{ + wmem_block_allocator_t *allocator = (wmem_block_allocator_t*) private_data; + wmem_block_chunk_t *chunk; + + chunk = WMEM_DATA_TO_CHUNK(ptr); + + if (chunk->jumbo) { + return wmem_block_realloc_jumbo(allocator, chunk, size); + } + + if (size > WMEM_CHUNK_DATA_LEN(chunk)) { + /* grow */ + wmem_block_chunk_t *tmp; + + tmp = WMEM_CHUNK_NEXT(chunk); + + if (tmp && (!tmp->used) && + (size < WMEM_CHUNK_DATA_LEN(chunk) + tmp->len)) { + /* the next chunk is free and has enough extra, so just grab + * from that */ + size_t split_size; + + /* we ask for the next chunk to be split, but we don't end up + * using the split chunk header (it just gets merged into this one), + * so we want the split to be of (size - curdatalen - header_size). + * However, this can underflow by header_size, so we do a quick + * check here and floor the value to 0. */ + split_size = size - WMEM_CHUNK_DATA_LEN(chunk); + + if (split_size < WMEM_CHUNK_HEADER_SIZE) { + split_size = 0; + } + else { + split_size -= WMEM_CHUNK_HEADER_SIZE; + } + + wmem_block_split_free_chunk(allocator, tmp, split_size); + + /* Now do a 'quickie' merge between the current block and the left- + * hand side of the split. Simply calling wmem_block_merge_free + * might confuse things, since we may temporarily have two blocks + * to our right that are both free (and it isn't guaranteed to + * handle that case). Update our 'next' count and last flag, and + * our (new) successor's 'prev' count */ + chunk->len += tmp->len; + chunk->last = tmp->last; + tmp = WMEM_CHUNK_NEXT(chunk); + if (tmp) { + tmp->prev = chunk->len; + } + + /* Now cycle the recycler */ + wmem_block_cycle_recycler(allocator); + + /* And return the same old pointer */ + return ptr; + } + else { + /* no room to grow, need to alloc, copy, free */ + void *newptr; + + newptr = wmem_block_alloc(private_data, size); + memcpy(newptr, ptr, WMEM_CHUNK_DATA_LEN(chunk)); + wmem_block_free(private_data, ptr); + + /* No need to cycle the recycler, alloc and free both did that + * already */ + + return newptr; + } + } + else if (size < WMEM_CHUNK_DATA_LEN(chunk)) { + /* shrink */ + wmem_block_split_used_chunk(allocator, chunk, size); + + /* Now cycle the recycler */ + wmem_block_cycle_recycler(allocator); + + return ptr; + } + + /* no-op */ + return ptr; +} + +static void +wmem_block_free_all(void *private_data) +{ + wmem_block_allocator_t *allocator = (wmem_block_allocator_t*) private_data; + wmem_block_hdr_t *cur; + wmem_block_chunk_t *chunk; + + /* the existing free lists are entirely irrelevant */ + allocator->master_head = NULL; + allocator->recycler_head = NULL; + + /* iterate through the blocks, reinitializing each one */ + cur = allocator->block_list; + + while (cur) { + chunk = WMEM_BLOCK_TO_CHUNK(cur); + if (chunk->jumbo) { + wmem_block_remove_from_block_list(allocator, cur); + cur = cur->next; + wmem_free(NULL, WMEM_CHUNK_TO_BLOCK(chunk)); + } + else { + wmem_block_init_block(allocator, cur); + cur = cur->next; + } + } +} + +static void +wmem_block_gc(void *private_data) +{ + wmem_block_allocator_t *allocator = (wmem_block_allocator_t*) private_data; + wmem_block_hdr_t *cur, *next; + wmem_block_chunk_t *chunk; + wmem_block_free_t *free_chunk; + + /* Walk through the blocks, adding used blocks to the new list and + * completely destroying unused blocks. */ + cur = allocator->block_list; + allocator->block_list = NULL; + + while (cur) { + chunk = WMEM_BLOCK_TO_CHUNK(cur); + next = cur->next; + + if (!chunk->jumbo && !chunk->used && chunk->last) { + /* If the first chunk is also the last, and is unused, then + * the block as a whole is entirely unused, so return it to + * the OS and remove it from whatever lists it is in. */ + free_chunk = WMEM_GET_FREE(chunk); + if (free_chunk->next) { + WMEM_GET_FREE(free_chunk->next)->prev = free_chunk->prev; + } + if (free_chunk->prev) { + WMEM_GET_FREE(free_chunk->prev)->next = free_chunk->next; + } + if (allocator->recycler_head == chunk) { + if (free_chunk->next == chunk) { + allocator->recycler_head = NULL; + } + else { + allocator->recycler_head = free_chunk->next; + } + } + else if (allocator->master_head == chunk) { + allocator->master_head = free_chunk->next; + } + wmem_free(NULL, cur); + } + else { + /* part of this block is used, so add it to the new block list */ + wmem_block_add_to_block_list(allocator, cur); + } + + cur = next; + } +} + +static void +wmem_block_allocator_cleanup(void *private_data) +{ + /* wmem guarantees that free_all() is called directly before this, so + * calling gc will return all our blocks to the OS automatically */ + wmem_block_gc(private_data); + + /* then just free the allocator structs */ + wmem_free(NULL, private_data); +} + +void +wmem_block_allocator_init(wmem_allocator_t *allocator) +{ + wmem_block_allocator_t *block_allocator; + + block_allocator = wmem_new(NULL, wmem_block_allocator_t); + + allocator->walloc = &wmem_block_alloc; + allocator->wrealloc = &wmem_block_realloc; + allocator->wfree = &wmem_block_free; + + allocator->free_all = &wmem_block_free_all; + allocator->gc = &wmem_block_gc; + allocator->cleanup = &wmem_block_allocator_cleanup; + + allocator->private_data = (void*) block_allocator; + + block_allocator->block_list = NULL; + block_allocator->master_head = NULL; + block_allocator->recycler_head = NULL; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_allocator_block.h b/wsutil/wmem/wmem_allocator_block.h new file mode 100644 index 00000000..e7018dfb --- /dev/null +++ b/wsutil/wmem/wmem_allocator_block.h @@ -0,0 +1,46 @@ +/** @file + * + * Definitions for the Wireshark Memory Manager Large-Block Allocator + * Copyright 2012, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WMEM_ALLOCATOR_BLOCK_H__ +#define __WMEM_ALLOCATOR_BLOCK_H__ + +#include "wmem_core.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void +wmem_block_allocator_init(wmem_allocator_t *allocator); + +/* Exposed only for testing purposes */ +void +wmem_block_verify(wmem_allocator_t *allocator); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __WMEM_ALLOCATOR_BLOCK_H__ */ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_allocator_block_fast.c b/wsutil/wmem/wmem_allocator_block_fast.c new file mode 100644 index 00000000..538f5187 --- /dev/null +++ b/wsutil/wmem/wmem_allocator_block_fast.c @@ -0,0 +1,261 @@ +/* wmem_allocator_block.c + * Wireshark Memory Manager Fast Large-Block Allocator + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" + +#include <stdio.h> +#include <string.h> + +#include <glib.h> + +#include "wmem_core.h" +#include "wmem_allocator.h" +#include "wmem_allocator_block_fast.h" + +/* https://mail.gnome.org/archives/gtk-devel-list/2004-December/msg00091.html + * The 2*sizeof(size_t) alignment here is borrowed from GNU libc, so it should + * be good most everywhere. It is more conservative than is needed on some + * 64-bit platforms, but ia64 does require a 16-byte alignment. The SIMD + * extensions for x86 and ppc32 would want a larger alignment than this, but + * we don't need to do better than malloc. + */ +#define WMEM_ALIGN_AMOUNT (2 * sizeof (size_t)) +#define WMEM_ALIGN_SIZE(SIZE) ((~(WMEM_ALIGN_AMOUNT-1)) & \ + ((SIZE) + (WMEM_ALIGN_AMOUNT-1))) + +#define WMEM_CHUNK_TO_DATA(CHUNK) ((void*)((uint8_t*)(CHUNK) + WMEM_CHUNK_HEADER_SIZE)) +#define WMEM_DATA_TO_CHUNK(DATA) ((wmem_block_fast_chunk_t*)((uint8_t*)(DATA) - WMEM_CHUNK_HEADER_SIZE)) + +#define WMEM_BLOCK_MAX_ALLOC_SIZE (WMEM_BLOCK_SIZE - (WMEM_BLOCK_HEADER_SIZE + WMEM_CHUNK_HEADER_SIZE)) + +/* When required, allocate more memory from the OS in chunks of this size. + * 2MB is a pretty arbitrary value - it's big enough that it should last a while + * and small enough that a mostly-unused one doesn't waste *too* much. It's + * also a nice power of two, of course. */ +#define WMEM_BLOCK_SIZE (2 * 1024 * 1024) + +/* The header for an entire OS-level 'block' of memory */ +typedef struct _wmem_block_fast_hdr { + struct _wmem_block_fast_hdr *next; + + int32_t pos; +} wmem_block_fast_hdr_t; +#define WMEM_BLOCK_HEADER_SIZE WMEM_ALIGN_SIZE(sizeof(wmem_block_fast_hdr_t)) + +typedef struct { + uint32_t len; +} wmem_block_fast_chunk_t; +#define WMEM_CHUNK_HEADER_SIZE WMEM_ALIGN_SIZE(sizeof(wmem_block_fast_chunk_t)) + +#define JUMBO_MAGIC 0xFFFFFFFF +typedef struct _wmem_block_fast_jumbo { + struct _wmem_block_fast_jumbo *prev, *next; +} wmem_block_fast_jumbo_t; +#define WMEM_JUMBO_HEADER_SIZE WMEM_ALIGN_SIZE(sizeof(wmem_block_fast_jumbo_t)) + +typedef struct { + wmem_block_fast_hdr_t *block_list; + wmem_block_fast_jumbo_t *jumbo_list; +} wmem_block_fast_allocator_t; + +/* Creates a new block, and initializes it. */ +static inline void +wmem_block_fast_new_block(wmem_block_fast_allocator_t *allocator) +{ + wmem_block_fast_hdr_t *block; + + /* allocate/initialize the new block and add it to the block list */ + block = (wmem_block_fast_hdr_t *)wmem_alloc(NULL, WMEM_BLOCK_SIZE); + + block->pos = WMEM_BLOCK_HEADER_SIZE; + block->next = allocator->block_list; + + allocator->block_list = block; +} + +/* API */ + +static void * +wmem_block_fast_alloc(void *private_data, const size_t size) +{ + wmem_block_fast_allocator_t *allocator = (wmem_block_fast_allocator_t*) private_data; + wmem_block_fast_chunk_t *chunk; + int32_t real_size; + + if (size > WMEM_BLOCK_MAX_ALLOC_SIZE) { + wmem_block_fast_jumbo_t *block; + + /* allocate/initialize a new block of the necessary size */ + block = (wmem_block_fast_jumbo_t *)wmem_alloc(NULL, + size + WMEM_JUMBO_HEADER_SIZE + WMEM_CHUNK_HEADER_SIZE); + + block->next = allocator->jumbo_list; + if (block->next) { + block->next->prev = block; + } + block->prev = NULL; + allocator->jumbo_list = block; + + chunk = ((wmem_block_fast_chunk_t*)((uint8_t*)(block) + WMEM_JUMBO_HEADER_SIZE)); + chunk->len = JUMBO_MAGIC; + + return WMEM_CHUNK_TO_DATA(chunk); + } + + real_size = (int32_t)(WMEM_ALIGN_SIZE(size) + WMEM_CHUNK_HEADER_SIZE); + + /* Allocate a new block if necessary. */ + if (!allocator->block_list || + (WMEM_BLOCK_SIZE - allocator->block_list->pos) < real_size) { + wmem_block_fast_new_block(allocator); + } + + chunk = (wmem_block_fast_chunk_t *) ((uint8_t *) allocator->block_list + allocator->block_list->pos); + /* safe to cast, size smaller than WMEM_BLOCK_MAX_ALLOC_SIZE */ + chunk->len = (uint32_t) size; + + allocator->block_list->pos += real_size; + + /* and return the user's pointer */ + return WMEM_CHUNK_TO_DATA(chunk); +} + +static void +wmem_block_fast_free(void *private_data _U_, void *ptr _U_) +{ + /* free is NOP */ +} + +static void * +wmem_block_fast_realloc(void *private_data, void *ptr, const size_t size) +{ + wmem_block_fast_chunk_t *chunk; + + chunk = WMEM_DATA_TO_CHUNK(ptr); + + if (chunk->len == JUMBO_MAGIC) { + wmem_block_fast_jumbo_t *block; + + block = ((wmem_block_fast_jumbo_t*)((uint8_t*)(chunk) - WMEM_JUMBO_HEADER_SIZE)); + block = (wmem_block_fast_jumbo_t*)wmem_realloc(NULL, block, + size + WMEM_JUMBO_HEADER_SIZE + WMEM_CHUNK_HEADER_SIZE); + if (block->prev) { + block->prev->next = block; + } + else { + wmem_block_fast_allocator_t *allocator = (wmem_block_fast_allocator_t*) private_data; + allocator->jumbo_list = block; + } + if (block->next) { + block->next->prev = block; + } + return ((void*)((uint8_t*)(block) + WMEM_JUMBO_HEADER_SIZE + WMEM_CHUNK_HEADER_SIZE)); + } + else if (chunk->len < size) { + /* grow */ + void *newptr; + + /* need to alloc and copy; free is no-op, so don't call it */ + newptr = wmem_block_fast_alloc(private_data, size); + memcpy(newptr, ptr, chunk->len); + + return newptr; + } + + /* shrink or same space - great we can do nothing */ + return ptr; +} + +static void +wmem_block_fast_free_all(void *private_data) +{ + wmem_block_fast_allocator_t *allocator = (wmem_block_fast_allocator_t*) private_data; + wmem_block_fast_hdr_t *cur, *nxt; + wmem_block_fast_jumbo_t *cur_jum, *nxt_jum; + + /* iterate through the blocks, freeing all but the first and reinitializing + * that one */ + cur = allocator->block_list; + + if (cur) { + cur->pos = WMEM_BLOCK_HEADER_SIZE; + nxt = cur->next; + cur->next = NULL; + cur = nxt; + } + + while (cur) { + nxt = cur->next; + wmem_free(NULL, cur); + cur = nxt; + } + + /* now do the jumbo blocks, freeing all of them */ + cur_jum = allocator->jumbo_list; + while (cur_jum) { + nxt_jum = cur_jum->next; + wmem_free(NULL, cur_jum); + cur_jum = nxt_jum; + } + allocator->jumbo_list = NULL; +} + +static void +wmem_block_fast_gc(void *private_data _U_) +{ + /* No-op */ +} + +static void +wmem_block_fast_allocator_cleanup(void *private_data) +{ + wmem_block_fast_allocator_t *allocator = (wmem_block_fast_allocator_t*) private_data; + + /* wmem guarantees that free_all() is called directly before this, so + * simply free the first block */ + wmem_free(NULL, allocator->block_list); + + /* then just free the allocator structs */ + wmem_free(NULL, private_data); +} + +void +wmem_block_fast_allocator_init(wmem_allocator_t *allocator) +{ + wmem_block_fast_allocator_t *block_allocator; + + block_allocator = wmem_new(NULL, wmem_block_fast_allocator_t); + + allocator->walloc = &wmem_block_fast_alloc; + allocator->wrealloc = &wmem_block_fast_realloc; + allocator->wfree = &wmem_block_fast_free; + + allocator->free_all = &wmem_block_fast_free_all; + allocator->gc = &wmem_block_fast_gc; + allocator->cleanup = &wmem_block_fast_allocator_cleanup; + + allocator->private_data = (void*) block_allocator; + + block_allocator->block_list = NULL; + block_allocator->jumbo_list = NULL; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_allocator_block_fast.h b/wsutil/wmem/wmem_allocator_block_fast.h new file mode 100644 index 00000000..ff5f90cd --- /dev/null +++ b/wsutil/wmem/wmem_allocator_block_fast.h @@ -0,0 +1,41 @@ +/** @file + * + * Definitions for the Wireshark Memory Manager Fast Large-Block Allocator + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WMEM_ALLOCATOR_BLOCK_FAST_H__ +#define __WMEM_ALLOCATOR_BLOCK_FAST_H__ + +#include "wmem_core.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void +wmem_block_fast_allocator_init(wmem_allocator_t *allocator); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __WMEM_ALLOCATOR_BLOCK_FAST_H__ */ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_allocator_simple.c b/wsutil/wmem/wmem_allocator_simple.c new file mode 100644 index 00000000..0c7d9b0b --- /dev/null +++ b/wsutil/wmem/wmem_allocator_simple.c @@ -0,0 +1,152 @@ +/* wmem_allocator_simple.c + * Wireshark Memory Manager Simple Allocator + * Copyright 2012, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" + +#include <string.h> + +#include <glib.h> + +#include "wmem_core.h" +#include "wmem_allocator.h" +#include "wmem_allocator_simple.h" + +#define DEFAULT_ALLOCS 8192 + +typedef struct _wmem_simple_allocator_t { + int size; + int count; + void **ptrs; +} wmem_simple_allocator_t; + +static void * +wmem_simple_alloc(void *private_data, const size_t size) +{ + wmem_simple_allocator_t *allocator; + + allocator = (wmem_simple_allocator_t*) private_data; + + if (G_UNLIKELY(allocator->count == allocator->size)) { + allocator->size *= 2; + allocator->ptrs = (void**)wmem_realloc(NULL, allocator->ptrs, + sizeof(void*) * allocator->size); + } + + return allocator->ptrs[allocator->count++] = wmem_alloc(NULL, size); +} + +static void +wmem_simple_free(void *private_data, void *ptr) +{ + int i; + wmem_simple_allocator_t *allocator; + + allocator = (wmem_simple_allocator_t*) private_data; + + wmem_free(NULL, ptr); + allocator->count--; + + for (i=allocator->count; i>=0; i--) { + if (ptr == allocator->ptrs[i]) { + if (i < allocator->count) { + allocator->ptrs[i] = allocator->ptrs[allocator->count]; + } + return; + } + } + + g_assert_not_reached(); +} + +static void * +wmem_simple_realloc(void *private_data, void *ptr, const size_t size) +{ + int i; + wmem_simple_allocator_t *allocator; + + allocator = (wmem_simple_allocator_t*) private_data; + + for (i=allocator->count-1; i>=0; i--) { + if (ptr == allocator->ptrs[i]) { + return allocator->ptrs[i] = wmem_realloc(NULL, allocator->ptrs[i], size); + } + } + + g_assert_not_reached(); + /* not reached */ + return NULL; +} + +static void +wmem_simple_free_all(void *private_data) +{ + wmem_simple_allocator_t *allocator; + int i; + + allocator = (wmem_simple_allocator_t*) private_data; + + for (i = 0; i<allocator->count; i++) { + wmem_free(NULL, allocator->ptrs[i]); + } + allocator->count = 0; +} + +static void +wmem_simple_gc(void *private_data _U_) +{ + /* In this simple allocator, there is nothing to garbage-collect */ +} + +static void +wmem_simple_allocator_cleanup(void *private_data) +{ + wmem_simple_allocator_t *allocator; + + allocator = (wmem_simple_allocator_t*) private_data; + + wmem_free(NULL, allocator->ptrs); + wmem_free(NULL, allocator); +} + +void +wmem_simple_allocator_init(wmem_allocator_t *allocator) +{ + wmem_simple_allocator_t *simple_allocator; + + simple_allocator = wmem_new(NULL, wmem_simple_allocator_t); + + allocator->walloc = &wmem_simple_alloc; + allocator->wrealloc = &wmem_simple_realloc; + allocator->wfree = &wmem_simple_free; + + allocator->free_all = &wmem_simple_free_all; + allocator->gc = &wmem_simple_gc; + allocator->cleanup = &wmem_simple_allocator_cleanup; + + allocator->private_data = (void*) simple_allocator; + + simple_allocator->count = 0; + simple_allocator->size = DEFAULT_ALLOCS; + simple_allocator->ptrs = wmem_alloc_array(NULL, void*, DEFAULT_ALLOCS); +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_allocator_simple.h b/wsutil/wmem/wmem_allocator_simple.h new file mode 100644 index 00000000..a843525e --- /dev/null +++ b/wsutil/wmem/wmem_allocator_simple.h @@ -0,0 +1,42 @@ +/** @file + * + * Definitions for the Wireshark Memory Manager Simple Allocator + * Copyright 2012, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WMEM_ALLOCATOR_SIMPLE_H__ +#define __WMEM_ALLOCATOR_SIMPLE_H__ + +#include "wmem_core.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void +wmem_simple_allocator_init(wmem_allocator_t *allocator); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __WMEM_ALLOCATOR_SIMPLE_H__ */ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_allocator_strict.c b/wsutil/wmem/wmem_allocator_strict.c new file mode 100644 index 00000000..87ef01e2 --- /dev/null +++ b/wsutil/wmem/wmem_allocator_strict.c @@ -0,0 +1,230 @@ +/* wmem_allocator_strict.c + * Wireshark Memory Manager Strict Allocator + * Copyright 2012, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" + +#include <string.h> + +#include <glib.h> + +#include "wmem_core.h" +#include "wmem_allocator.h" +#include "wmem_allocator_strict.h" + +/* In this allocator, we do everything we can to catch invalid memory accesses. + * This includes using canaries (what Valgrind calls redzones) and + * filling allocated and freed memory with garbage. Valgrind is still the + * better tool on the platforms where it is available - use it instead if + * possible. + */ + +#define WMEM_CANARY_SIZE 8 /* in bytes */ +#define WMEM_CANARY_VALUE 0x9E + +#define WMEM_PREFILL 0xA1 +#define WMEM_POSTFILL 0x1A + +typedef struct _wmem_strict_allocator_block_t { + struct _wmem_strict_allocator_block_t *prev, *next; + + /* Just the length of real_data, not counting the canaries */ + size_t data_len; +} wmem_strict_allocator_block_t; + +#define WMEM_DATA_TO_BLOCK(DATA) ((wmem_strict_allocator_block_t*)((uint8_t*)(DATA) - WMEM_CANARY_SIZE - sizeof(wmem_strict_allocator_block_t))) +#define WMEM_BLOCK_TO_DATA(BLOCK) ((void*)((uint8_t*)(BLOCK) + WMEM_CANARY_SIZE + sizeof(wmem_strict_allocator_block_t))) +#define WMEM_BLOCK_TO_PRE_CANARY(BLOCK) ((uint8_t*)(BLOCK) + sizeof(wmem_strict_allocator_block_t)) +#define WMEM_BLOCK_TO_POST_CANARY(BLOCK) ((uint8_t*)(BLOCK) + WMEM_CANARY_SIZE + sizeof(wmem_strict_allocator_block_t) + (BLOCK)->data_len) +#define WMEM_FULL_SIZE(SIZE) ((SIZE) + sizeof(wmem_strict_allocator_block_t) + (2*WMEM_CANARY_SIZE)) + +typedef struct _wmem_strict_allocator_t { + wmem_strict_allocator_block_t *blocks; +} wmem_strict_allocator_t; + +/* + * some internal helper functions + */ +static inline void +wmem_strict_block_check_canaries(wmem_strict_allocator_block_t *block) +{ + unsigned i; + uint8_t *canary; + + canary = WMEM_BLOCK_TO_PRE_CANARY(block); + for (i=0; i<WMEM_CANARY_SIZE; i++) g_assert_true(canary[i] == WMEM_CANARY_VALUE); + + canary = WMEM_BLOCK_TO_POST_CANARY(block); + for (i=0; i<WMEM_CANARY_SIZE; i++) g_assert_true(canary[i] == WMEM_CANARY_VALUE); +} + +/* + * public API functions + */ +static void * +wmem_strict_alloc(void *private_data, const size_t size) +{ + wmem_strict_allocator_t *allocator; + wmem_strict_allocator_block_t *block; + unsigned i; + uint8_t *canary; + + allocator = (wmem_strict_allocator_t*) private_data; + + block = (wmem_strict_allocator_block_t *)wmem_alloc(NULL, WMEM_FULL_SIZE(size)); + block->data_len = size; + + memset(WMEM_BLOCK_TO_DATA(block), WMEM_PREFILL, block->data_len); + + canary = WMEM_BLOCK_TO_PRE_CANARY(block); + for (i=0; i<WMEM_CANARY_SIZE; i++) canary[i] = WMEM_CANARY_VALUE; + + canary = WMEM_BLOCK_TO_POST_CANARY(block); + for (i=0; i<WMEM_CANARY_SIZE; i++) canary[i] = WMEM_CANARY_VALUE; + + if (allocator->blocks) { + allocator->blocks->prev = block; + } + block->next = allocator->blocks; + block->prev = NULL; + allocator->blocks = block; + + return WMEM_BLOCK_TO_DATA(block); +} + +static void +wmem_strict_free(void *private_data, void *ptr) +{ + wmem_strict_allocator_t *allocator; + wmem_strict_allocator_block_t *block; + + allocator = (wmem_strict_allocator_t*) private_data; + + block = WMEM_DATA_TO_BLOCK(ptr); + + wmem_strict_block_check_canaries(block); + + if (block->next) { + block->next->prev = block->prev; + } + + if (block->prev) { + block->prev->next = block->next; + } + else { + allocator->blocks = block->next; + } + + memset(block, WMEM_POSTFILL, WMEM_FULL_SIZE(block->data_len)); + + wmem_free(NULL, block); +} + +static void * +wmem_strict_realloc(void *private_data, void *ptr, const size_t size) +{ + wmem_strict_allocator_block_t *block; + void *new_ptr; + + block = WMEM_DATA_TO_BLOCK(ptr); + + /* create a new block */ + new_ptr = wmem_strict_alloc(private_data, size); + + /* copy from the old block to the new */ + if (block->data_len > size) { + memcpy(new_ptr, ptr, size); + } + else { + memcpy(new_ptr, ptr, block->data_len); + } + + /* free the old block */ + wmem_strict_free(private_data, ptr); + + return new_ptr; +} + +void +wmem_strict_check_canaries(wmem_allocator_t *allocator) +{ + wmem_strict_allocator_t *private_allocator; + wmem_strict_allocator_block_t *block; + + if (allocator->type != WMEM_ALLOCATOR_STRICT) { + return; + } + + private_allocator = (wmem_strict_allocator_t*) allocator->private_data; + + block = private_allocator->blocks; + while (block) { + wmem_strict_block_check_canaries(block); + block = block->next; + } +} + +static void +wmem_strict_free_all(void *private_data) +{ + wmem_strict_allocator_t *allocator; + + allocator = (wmem_strict_allocator_t*) private_data; + + while (allocator->blocks) { + wmem_strict_free(private_data, WMEM_BLOCK_TO_DATA(allocator->blocks)); + } +} + +static void +wmem_strict_gc(void *private_data _U_) +{ + /* We don't really have anything to garbage-collect, but it might be worth + * checking our canaries at this point? */ +} + +static void +wmem_strict_allocator_cleanup(void *private_data) +{ + wmem_free(NULL, private_data); +} + +void +wmem_strict_allocator_init(wmem_allocator_t *allocator) +{ + wmem_strict_allocator_t *strict_allocator; + + strict_allocator = wmem_new(NULL, wmem_strict_allocator_t); + + allocator->walloc = &wmem_strict_alloc; + allocator->wrealloc = &wmem_strict_realloc; + allocator->wfree = &wmem_strict_free; + + allocator->free_all = &wmem_strict_free_all; + allocator->gc = &wmem_strict_gc; + allocator->cleanup = &wmem_strict_allocator_cleanup; + + allocator->private_data = (void*) strict_allocator; + + strict_allocator->blocks = NULL; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_allocator_strict.h b/wsutil/wmem/wmem_allocator_strict.h new file mode 100644 index 00000000..014f76fb --- /dev/null +++ b/wsutil/wmem/wmem_allocator_strict.h @@ -0,0 +1,45 @@ +/** @file + * + * Definitions for the Wireshark Memory Manager Strict Allocator + * Copyright 2012, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WMEM_ALLOCATOR_STRICT_H__ +#define __WMEM_ALLOCATOR_STRICT_H__ + +#include "wmem_core.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void +wmem_strict_allocator_init(wmem_allocator_t *allocator); + +void +wmem_strict_check_canaries(wmem_allocator_t *allocator); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __WMEM_ALLOCATOR_STRICT_H__ */ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_array.c b/wsutil/wmem/wmem_array.c new file mode 100644 index 00000000..47f6e8e0 --- /dev/null +++ b/wsutil/wmem/wmem_array.c @@ -0,0 +1,196 @@ +/* wmem_array.c + * Wireshark Memory Manager Array + * Copyright 2013, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" + +#include <string.h> +#include <stdlib.h> +#include <glib.h> + +#include "wmem_core.h" +#include "wmem_array.h" + +/* Holds a wmem-allocated array. + * elem_len is the size of each element + * elem_count is the number of used elements + * alloc_count is the length (in elems) of the raw buffer pointed to by buf, + * regardless of how many elems are used (the contents) + */ +struct _wmem_array_t { + wmem_allocator_t *allocator; + + uint8_t *buf; + + size_t elem_size; + + unsigned elem_count; + unsigned alloc_count; + + bool null_terminated; +}; + +wmem_array_t * +wmem_array_sized_new(wmem_allocator_t *allocator, size_t elem_size, + unsigned alloc_count) +{ + wmem_array_t *array; + + array = wmem_new(allocator, wmem_array_t); + + array->allocator = allocator; + array->elem_size = elem_size; + array->elem_count = 0; + array->alloc_count = alloc_count ? alloc_count : 1; + array->null_terminated = false; + + array->buf = (uint8_t *)wmem_alloc(array->allocator, + array->elem_size * array->alloc_count); + + return array; +} + +wmem_array_t * +wmem_array_new(wmem_allocator_t *allocator, const size_t elem_size) +{ + wmem_array_t *array; + + array = wmem_array_sized_new(allocator, elem_size, 1); + + return array; +} + +void +wmem_array_grow(wmem_array_t *array, const unsigned to_add) +{ + unsigned new_alloc_count, new_count; + + new_alloc_count = array->alloc_count; + new_count = array->elem_count + to_add; + + while (new_alloc_count < new_count) { + new_alloc_count *= 2; + } + + if (new_alloc_count == array->alloc_count) { + return; + } + + array->buf = (uint8_t *)wmem_realloc(array->allocator, array->buf, + new_alloc_count * array->elem_size); + + array->alloc_count = new_alloc_count; +} + +static void +wmem_array_write_null_terminator(wmem_array_t *array) +{ + if (array->null_terminated) { + wmem_array_grow(array, 1); + memset(&array->buf[array->elem_count * array->elem_size], 0x0, array->elem_size); + } +} + +void +wmem_array_set_null_terminator(wmem_array_t *array) +{ + array->null_terminated = true; + wmem_array_write_null_terminator(array); +} + +void +wmem_array_bzero(wmem_array_t *array) +{ + memset(array->buf, 0x0, array->elem_size * array->elem_count); +} + +void +wmem_array_append(wmem_array_t *array, const void *in, unsigned count) +{ + wmem_array_grow(array, count); + + memcpy(&array->buf[array->elem_count * array->elem_size], in, + count * array->elem_size); + + array->elem_count += count; + + wmem_array_write_null_terminator(array); +} + +void * +wmem_array_index(wmem_array_t *array, unsigned array_index) +{ + g_assert(array_index < array->elem_count); + return &array->buf[array_index * array->elem_size]; +} + +int +wmem_array_try_index(wmem_array_t *array, unsigned array_index, void *val) +{ + if (array_index >= array->elem_count) + return -1; + memcpy(val, &array->buf[array_index * array->elem_size], array->elem_size); + return 0; +} + +void +wmem_array_sort(wmem_array_t *array, int (*compar)(const void*,const void*)) +{ + qsort(array->buf, array->elem_count, array->elem_size, compar); +} + +void * +wmem_array_get_raw(wmem_array_t *array) +{ + return array->buf; +} + +unsigned +wmem_array_get_count(wmem_array_t *array) +{ + if (array == NULL) + return 0; + + return array->elem_count; +} + +void * +wmem_array_finalize(wmem_array_t *array) +{ + if (array == NULL) + return NULL; + + size_t used_size = array->null_terminated ? (array->elem_count + 1) * array->elem_size : array->elem_count * array->elem_size; + void *ret = wmem_realloc(array->allocator, array->buf, used_size); + + wmem_free(array->allocator, array); + + return ret; +} + +void +wmem_destroy_array(wmem_array_t *array) +{ + wmem_free(array->allocator, array->buf); + wmem_free(array->allocator, array); +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_array.h b/wsutil/wmem/wmem_array.h new file mode 100644 index 00000000..e813232d --- /dev/null +++ b/wsutil/wmem/wmem_array.h @@ -0,0 +1,121 @@ +/** @file + * Definitions for the Wireshark Memory Manager Array + * Copyright 2013, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WMEM_ARRAY_H__ +#define __WMEM_ARRAY_H__ + +#include <string.h> +#include <glib.h> + +#include "wmem_core.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** @addtogroup wmem + * @{ + * @defgroup wmem-array Array + * + * A resizable array implementation on top of wmem. + * + * @{ + */ + +struct _wmem_array_t; + +typedef struct _wmem_array_t wmem_array_t; + +WS_DLL_PUBLIC +wmem_array_t * +wmem_array_sized_new(wmem_allocator_t *allocator, size_t elem_size, + unsigned alloc_count) +G_GNUC_MALLOC; + +WS_DLL_PUBLIC +wmem_array_t * +wmem_array_new(wmem_allocator_t *allocator, const size_t elem_size) +G_GNUC_MALLOC; + +WS_DLL_PUBLIC +void +wmem_array_grow(wmem_array_t *array, const unsigned to_add); + +WS_DLL_PUBLIC +void +wmem_array_set_null_terminator(wmem_array_t *array); + +WS_DLL_PUBLIC +void +wmem_array_bzero(wmem_array_t *array); + +WS_DLL_PUBLIC +void +wmem_array_append(wmem_array_t *array, const void *in, unsigned count); + +#define wmem_array_append_one(ARRAY, VAL) \ + wmem_array_append((ARRAY), &(VAL), 1) + +WS_DLL_PUBLIC +void * +wmem_array_index(wmem_array_t *array, unsigned array_index); + +WS_DLL_PUBLIC +int +wmem_array_try_index(wmem_array_t *array, unsigned array_index, void *val); + +WS_DLL_PUBLIC +void +wmem_array_sort(wmem_array_t *array, int (*compar)(const void*,const void*)); + +WS_DLL_PUBLIC +void * +wmem_array_get_raw(wmem_array_t *array); + +WS_DLL_PUBLIC +unsigned +wmem_array_get_count(wmem_array_t *array); + +/* Truncates the underlying array to the elements contained within + * (including null terminator if set), frees the wmem_array_t + * structure, and returns a pointer to the raw array. The wmem_array_t + * struct cannot be used after this is called. This is for when you are + * done adding elements to the array but still need the underlying array. + */ +WS_DLL_PUBLIC +void * +wmem_array_finalize(wmem_array_t *array); + +WS_DLL_PUBLIC +void +wmem_destroy_array(wmem_array_t *array); + +/** @} + * @} */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __WMEM_ARRAY_H__ */ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_core.c b/wsutil/wmem/wmem_core.c new file mode 100644 index 00000000..a166bf84 --- /dev/null +++ b/wsutil/wmem/wmem_core.c @@ -0,0 +1,240 @@ +/* wmem_core.c + * Wireshark Memory Manager Core + * Copyright 2012, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <stdlib.h> +#include <string.h> +#include <glib.h> + +#include "wmem-int.h" +#include "wmem_core.h" +#include "wmem_map_int.h" +#include "wmem_user_cb_int.h" +#include "wmem_allocator.h" +#include "wmem_allocator_simple.h" +#include "wmem_allocator_block.h" +#include "wmem_allocator_block_fast.h" +#include "wmem_allocator_strict.h" + +/* Set according to the WIRESHARK_DEBUG_WMEM_OVERRIDE environment variable in + * wmem_init. Should not be set again. */ +static bool do_override = false; +static wmem_allocator_type_t override_type; + +void * +wmem_alloc(wmem_allocator_t *allocator, const size_t size) +{ + if (allocator == NULL) { + return g_malloc(size); + } + + ws_assert(allocator->in_scope); + + if (size == 0) { + return NULL; + } + + return allocator->walloc(allocator->private_data, size); +} + +void * +wmem_alloc0(wmem_allocator_t *allocator, const size_t size) +{ + void *buf; + + buf = wmem_alloc(allocator, size); + + if (buf) { + memset(buf, 0, size); + } + + return buf; +} + +void +wmem_free(wmem_allocator_t *allocator, void *ptr) +{ + if (allocator == NULL) { + g_free(ptr); + return; + } + + ws_assert(allocator->in_scope); + + if (ptr == NULL) { + return; + } + + allocator->wfree(allocator->private_data, ptr); +} + +void * +wmem_realloc(wmem_allocator_t *allocator, void *ptr, const size_t size) +{ + if (allocator == NULL) { + return g_realloc(ptr, size); + } + + if (ptr == NULL) { + return wmem_alloc(allocator, size); + } + + if (size == 0) { + wmem_free(allocator, ptr); + return NULL; + } + + ws_assert(allocator->in_scope); + + return allocator->wrealloc(allocator->private_data, ptr, size); +} + +static void +wmem_free_all_real(wmem_allocator_t *allocator, bool final) +{ + wmem_call_callbacks(allocator, + final ? WMEM_CB_DESTROY_EVENT : WMEM_CB_FREE_EVENT); + allocator->free_all(allocator->private_data); +} + +void +wmem_free_all(wmem_allocator_t *allocator) +{ + wmem_free_all_real(allocator, false); +} + +void +wmem_gc(wmem_allocator_t *allocator) +{ + allocator->gc(allocator->private_data); +} + +void +wmem_destroy_allocator(wmem_allocator_t *allocator) +{ + + wmem_free_all_real(allocator, true); + allocator->cleanup(allocator->private_data); + wmem_free(NULL, allocator); +} + +wmem_allocator_t * +wmem_allocator_new(const wmem_allocator_type_t type) +{ + wmem_allocator_t *allocator; + wmem_allocator_type_t real_type; + + if (do_override) { + real_type = override_type; + } + else { + real_type = type; + } + + allocator = wmem_new(NULL, wmem_allocator_t); + allocator->type = real_type; + allocator->callbacks = NULL; + allocator->in_scope = true; + + switch (real_type) { + case WMEM_ALLOCATOR_SIMPLE: + wmem_simple_allocator_init(allocator); + break; + case WMEM_ALLOCATOR_BLOCK: + wmem_block_allocator_init(allocator); + break; + case WMEM_ALLOCATOR_BLOCK_FAST: + wmem_block_fast_allocator_init(allocator); + break; + case WMEM_ALLOCATOR_STRICT: + wmem_strict_allocator_init(allocator); + break; + default: + g_assert_not_reached(); + break; + }; + + return allocator; +} + +void +wmem_init(void) +{ + const char *override_env; + + /* Our valgrind script uses this environment variable to override the + * usual allocator choice so that everything goes through system-level + * allocations that it understands and can track. Otherwise it will get + * confused by the block allocator etc. */ + override_env = getenv("WIRESHARK_DEBUG_WMEM_OVERRIDE"); + + if (override_env == NULL) { + do_override = false; + } + else { + do_override = true; + if (strncmp(override_env, "simple", strlen("simple")) == 0) { + override_type = WMEM_ALLOCATOR_SIMPLE; + } + else if (strncmp(override_env, "block", strlen("block")) == 0) { + override_type = WMEM_ALLOCATOR_BLOCK; + } + else if (strncmp(override_env, "strict", strlen("strict")) == 0) { + override_type = WMEM_ALLOCATOR_STRICT; + } + else if (strncmp(override_env, "block_fast", strlen("block_fast")) == 0) { + override_type = WMEM_ALLOCATOR_BLOCK_FAST; + } + else { + g_warning("Unrecognized wmem override"); + do_override = false; + } + } + + wmem_init_hashing(); +} + +void +wmem_cleanup(void) +{ +} + +void +wmem_enter_scope(wmem_allocator_t *allocator) +{ + allocator->in_scope = true; +} + +void +wmem_leave_scope(wmem_allocator_t *allocator) +{ + wmem_free_all(allocator); + allocator->in_scope = false; +} + +bool +wmem_in_scope(wmem_allocator_t *allocator) +{ + return allocator->in_scope; +} + + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_core.h b/wsutil/wmem/wmem_core.h new file mode 100644 index 00000000..2f423133 --- /dev/null +++ b/wsutil/wmem/wmem_core.h @@ -0,0 +1,252 @@ +/** @file + * Definitions for the Wireshark Memory Manager Core + * Copyright 2012, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WMEM_CORE_H__ +#define __WMEM_CORE_H__ + +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <glib.h> +#include <ws_symbol_export.h> +#include <ws_attributes.h> +#include <ws_posix_compat.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** @defgroup wmem Wireshark Memory Manager + * + * Wmem is a memory management framework for Wireshark that makes it simple to + * write dissectors (and other 'user-space' code) that doesn't leak memory. The + * core module provides basic functions like malloc, realloc and free, but + * many other functions are available (see the "Modules" list at the top of + * the generated doxygen HTML). + * + * Any wmem functions which allocate memory are guaranteed to either succeed or + * abort the program. However, they *can* still legally return NULL when the + * amount of requested memory is zero. + * + * @{ + */ + +struct _wmem_allocator_t; +/** A public opaque type representing one wmem allocation pool. */ +typedef struct _wmem_allocator_t wmem_allocator_t; + +/** An enumeration of the different types of available allocators. */ +typedef enum _wmem_allocator_type_t { + WMEM_ALLOCATOR_SIMPLE, /**< A trivial allocator that mallocs requested + memory and tracks allocations via a hash table. As simple as + possible, intended more as a demo than for practical usage. Also + has the benefit of being friendly to tools like valgrind. */ + WMEM_ALLOCATOR_BLOCK, /**< A block allocator that grabs large chunks of + memory at a time (8 MB currently) and serves allocations out of + those chunks. Designed for efficiency, especially in the + free_all operation. */ + WMEM_ALLOCATOR_STRICT, /**< An allocator that does its best to find invalid + memory usage via things like canaries and scrubbing freed + memory. Valgrind is the better choice on platforms that support + it. */ + WMEM_ALLOCATOR_BLOCK_FAST /**< A block allocator like WMEM_ALLOCATOR_BLOCK + but even faster by tracking absolutely minimal metadata and + making 'free' a no-op. Useful only for very short-lived scopes + where there's no reason to free individual allocations because + the next free_all is always just around the corner. */ +} wmem_allocator_type_t; + +/** Allocate the requested amount of memory in the given pool. + * + * @param allocator The allocator object to use to allocate the memory. + * @param size The amount of memory to allocate. + * @return A void pointer to the newly allocated memory. + */ +WS_DLL_PUBLIC +void * +wmem_alloc(wmem_allocator_t *allocator, const size_t size) +G_GNUC_MALLOC; + +/** Allocate memory sufficient to hold one object of the given type. + * + * @param allocator The allocator object to use to allocate the memory. + * @param type The type that the newly allocated memory will hold. + * @return A void pointer to the newly allocated memory. + */ +#define wmem_new(allocator, type) \ + ((type*)wmem_alloc((allocator), sizeof(type))) + +/* + * Overflow-safe multiplication of the size of a type by a number of + * items of that type, returning 0 if the result would overflow (or + * if the number of elements is negative), and the product otherwise. + */ +#define wmem_safe_mult_type_size(type, num) \ + ((((num) <= 0) || ((size_t)sizeof(type) > (G_MAXSSIZE / (size_t)(num)))) ? 0 : (sizeof(type) * (num))) + +/** Allocate memory sufficient to hold n objects of the given type. + * + * @param allocator The allocator object to use to allocate the memory. + * @param type The type that the newly allocated memory will hold. + * @param num The number of objects that the newly allocated memory will hold. + * @return A void pointer to the newly allocated memory. + */ +#define wmem_alloc_array(allocator, type, num) \ + ((type*)wmem_alloc((allocator), wmem_safe_mult_type_size(type, (num)))) + +/** Allocate the requested amount of memory in the given pool. Initializes the + * allocated memory with zeroes. + * + * @param allocator The allocator object to use to allocate the memory. + * @param size The amount of memory to allocate. + * @return A void pointer to the newly allocated and zeroed memory. + */ +WS_DLL_PUBLIC +void * +wmem_alloc0(wmem_allocator_t *allocator, const size_t size) +G_GNUC_MALLOC; + +/** Allocate memory sufficient to hold one object of the given type. + * Initializes the allocated memory with zeroes. + * + * @param allocator The allocator object to use to allocate the memory. + * @param type The type that the newly allocated memory will hold. + * @return A void pointer to the newly allocated and zeroed memory. + */ +#define wmem_new0(allocator, type) \ + ((type*)wmem_alloc0((allocator), sizeof(type))) + +/** Allocate memory sufficient to hold n objects of the given type. + * Initializes the allocated memory with zeroes. + * + * @param allocator The allocator object to use to allocate the memory. + * @param type The type that the newly allocated memory will hold. + * @param num The number of objects that the newly allocated memory will hold. + * @return A void pointer to the newly allocated and zeroed memory. + */ +#define wmem_alloc0_array(allocator, type, num) \ + ((type*)wmem_alloc0((allocator), wmem_safe_mult_type_size(type, (num)))) + +/** Returns the allocated memory to the allocator. This function should only + * be called directly by allocators when the allocated block is sufficiently + * large that the reduced memory usage is worth the cost of the extra function + * call. It's usually easier to just let it get cleaned up when wmem_free_all() + * is called. + * + * @param allocator The allocator object used to originally allocate the memory. + * @param ptr The pointer to the memory block to free. After this function + * returns it no longer points to valid memory. + */ +WS_DLL_PUBLIC +void +wmem_free(wmem_allocator_t *allocator, void *ptr); + +/** Resizes a block of memory, potentially moving it if resizing it in place + * is not possible. + * + * @param allocator The allocator object used to originally allocate the memory. + * @param ptr The pointer to the memory block to resize. + * @param size The new size for the memory block. + * @return The new location of the memory block. If this is different from ptr + * then ptr no longer points to valid memory. + */ +WS_DLL_PUBLIC +void * +wmem_realloc(wmem_allocator_t *allocator, void *ptr, const size_t size) +G_GNUC_MALLOC; + +/** Frees all the memory allocated in a pool. Depending on the allocator + * implementation used this can be significantly cheaper than calling + * wmem_free() on all the individual blocks. It also doesn't require you to have + * external pointers to those blocks. + * + * @param allocator The allocator to free the memory from. + */ +WS_DLL_PUBLIC +void +wmem_free_all(wmem_allocator_t *allocator); + +/** Triggers a garbage-collection in the allocator. This does not free any + * memory, but it can return unused blocks to the operating system or perform + * other optimizations. + * + * @param allocator The allocator in which to trigger the garbage collection. + */ +WS_DLL_PUBLIC +void +wmem_gc(wmem_allocator_t *allocator); + +/** Destroy the given allocator, freeing all memory allocated in it. Once this + * function has been called, no memory allocated with the allocator is valid. + * + * @param allocator The allocator to destroy. + */ +WS_DLL_PUBLIC +void +wmem_destroy_allocator(wmem_allocator_t *allocator); + +/** Create a new allocator of the given type. The type may be overridden by the + * WIRESHARK_DEBUG_WMEM_OVERRIDE environment variable. + * + * @param type The type of allocator to create. + * @return The new allocator. + */ +WS_DLL_PUBLIC +wmem_allocator_t * +wmem_allocator_new(const wmem_allocator_type_t type); + +/** Initialize the wmem subsystem. This must be called before any other wmem + * function, usually at the very beginning of your program. + */ +WS_DLL_PUBLIC +void +wmem_init(void); + +/** Teardown the wmem subsystem. This must be called after all other wmem + * functions, usually at the very end of your program. This function will not + * destroy outstanding allocators, you must do that yourself. + */ +WS_DLL_PUBLIC +void +wmem_cleanup(void); + +WS_DLL_PUBLIC +void +wmem_enter_scope(wmem_allocator_t *allocator); + +WS_DLL_PUBLIC +void +wmem_leave_scope(wmem_allocator_t *allocator); + +WS_DLL_PUBLIC +bool +wmem_in_scope(wmem_allocator_t *allocator); + +/** @} */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __WMEM_CORE_H__ */ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_interval_tree.c b/wsutil/wmem/wmem_interval_tree.c new file mode 100644 index 00000000..8b28549a --- /dev/null +++ b/wsutil/wmem/wmem_interval_tree.c @@ -0,0 +1,190 @@ +/* wmem_interval_tree.c + * Implements an augmented interval tree + * Based on the red-black tree implementation in epan/wmem.* + * Copyright 2015, Matthieu coudron <matthieu.coudron@lip6.fr> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" + +#include <inttypes.h> +#include <string.h> +#include <inttypes.h> +#include <stdio.h> +#include <glib.h> + +#include "wmem-int.h" +#include "wmem_core.h" +#include "wmem_tree-int.h" +#include "wmem_strutl.h" +#include "wmem_interval_tree.h" +#include "wmem_user_cb.h" + + +static void +print_range(const void *value) +{ + const wmem_range_t *range = (const wmem_range_t *)value; + if(!value) { + return; + } + printf("Range: low=%" PRIu64 " high=%" PRIu64 " max_edge=%" PRIu64 "\n", range->low, range->high, range->max_edge); +} + +/** + * In an augmented interval tree, each node saves the maximum edge of its child subtrees + * This function compares the children max_edge with the current max_edge + * and propagates any change to the parent nodes. + */ +static void +update_max_edge(wmem_tree_node_t *node) +{ + wmem_range_t *range; + const wmem_range_t *range_l; + const wmem_range_t *range_r; + uint64_t maxEdge = 0; + + if(!node) { + return ; + } + + range = (wmem_range_t *)node->key; + + range_l = (node->left) ? (const wmem_range_t *) (node->left->key) : NULL; + range_r = (node->right) ? (const wmem_range_t *) (node->right->key) : NULL; + + maxEdge = range->high; + + if(range_r) { + maxEdge = MAX(maxEdge, range_r->max_edge) ; + } + if(range_l) { + maxEdge = MAX(maxEdge, range_l->max_edge) ; + } + + /* update the parent nodes only if a change happened (optimization) */ + if(range->max_edge != maxEdge) { + range->max_edge = maxEdge; + update_max_edge(node->parent); + } +} + +bool +wmem_itree_range_overlap(const wmem_range_t *r1, const wmem_range_t *r2) +{ + return (r1->low <= r2->high && r2->low <= r1->high); +} + + +/* after a rotation, some of the children nodes might (dis)appear, thus we need + * to refresh children max_edge. Changes will propagate to parents */ +static void update_edges_after_rotation(wmem_tree_node_t *node) { + if(node->left) update_max_edge(node->left); + if(node->right) update_max_edge(node->right); +} + +wmem_itree_t * +wmem_itree_new(wmem_allocator_t *allocator) +{ + wmem_itree_t *tree = wmem_tree_new(allocator); + tree->post_rotation_cb = &update_edges_after_rotation; + return tree; +} + +bool +wmem_itree_is_empty(wmem_itree_t *tree) +{ + return wmem_tree_is_empty(tree); +} + +static int +wmem_tree_compare_ranges(const wmem_range_t *ra, const wmem_range_t *rb) +{ + if( ra->low == rb->low) { + return 0; + } + else if(ra->low < rb->low) { + return -1; + } + else { + return 1; + } +} + + +void +wmem_itree_insert(wmem_itree_t *tree, const uint64_t low, const uint64_t high, void *data) +{ + wmem_tree_node_t *node; + wmem_range_t *range = (wmem_range_t *)wmem_new(tree->data_allocator, wmem_range_t); + + ws_assert(low <= high); + range->low = low; + range->high = high; + range->max_edge = 0; + + node = wmem_tree_insert(tree, range, data, (compare_func)wmem_tree_compare_ranges); + + /* in absence of rotation, we still need to update max_edge */ + update_max_edge(node); +} + + +static void +wmem_itree_find_intervals_in_subtree(wmem_tree_node_t *node, wmem_range_t requested, wmem_list_t *results) +{ + const wmem_range_t* current; + + if(!node) { + return; + } + current = (wmem_range_t*)node->key; + + /* there is no child that can possibly match */ + if(requested.low > current->max_edge) { + return; + } + + if(wmem_itree_range_overlap(current, &requested)) { + wmem_list_prepend(results, node->data); + } + + wmem_itree_find_intervals_in_subtree(node->left, requested, results); + wmem_itree_find_intervals_in_subtree(node->right, requested, results); +} + +wmem_list_t * +wmem_itree_find_intervals(wmem_itree_t *tree, wmem_allocator_t *allocator, uint64_t low, uint64_t high) +{ + wmem_list_t *results = NULL; + wmem_range_t requested = { low, high, 0 }; + results = wmem_list_new(allocator); + + wmem_itree_find_intervals_in_subtree(tree->root, requested, results); + return results; +} + + +void +wmem_print_itree(wmem_tree_t *tree) +{ + wmem_print_tree(tree, &print_range, NULL); +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_interval_tree.h b/wsutil/wmem/wmem_interval_tree.h new file mode 100644 index 00000000..1d7bc4df --- /dev/null +++ b/wsutil/wmem/wmem_interval_tree.h @@ -0,0 +1,101 @@ +/** @file + * Definitions for the Wireshark Memory Manager Red-Black Tree + * Based on the red-black tree implementation in epan/emem.* + * Copyright 2015, Matthieu coudron <matthieu.coudron@lip6.fr> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#ifndef __WMEM_INTERVAL_TREE_H__ +#define __WMEM_INTERVAL_TREE_H__ + +#include "wmem_core.h" +#include "wmem_tree.h" +#include "wmem_list.h" + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** @addtogroup wmem + * @{ + * @defgroup wmem-interval-tree Interval Tree + * + * http://www.geeksforgeeks.org/interval-tree/ + * The idea is to augment a self-balancing Binary Search Tree (BST) like Red Black Tree, AVL Tree, etc ... + * to maintain a set of intervals so that all operations can be done in O(Logn) time. + * @{ + * Following wikipedia's convention this is an augmented tree rather then an interval tree + * http://www.wikiwand.com/en/Interval_tree + */ + +struct _wmem_tree_t; +typedef struct _wmem_tree_t wmem_itree_t; + +struct _wmem_range_t { + uint64_t low; /* low is used as the key in the binary tree */ + uint64_t high; /* Max value of the range */ + uint64_t max_edge; /* max value among subtrees */ +}; + +WS_DLL_PUBLIC +wmem_itree_t * +wmem_itree_new(wmem_allocator_t *allocator) +G_GNUC_MALLOC; + + +/** Returns true if the tree is empty (has no nodes). */ +WS_DLL_PUBLIC +bool +wmem_itree_is_empty(wmem_itree_t *tree); + + +/** Inserts a range low-high indexed by "low" in O(log(n)). + * As in wmem_tree, if a key "low" already exists, it will be overwritten with the new data + * + */ +WS_DLL_PUBLIC +void +wmem_itree_insert(wmem_itree_t *tree, const uint64_t low, const uint64_t high, void *data); + + +/* + * Save results in a wmem_list with the scope passed as a parameter. + * wmem_list_t is always allocated even if there is no result + */ +WS_DLL_PUBLIC +wmem_list_t * +wmem_itree_find_intervals(wmem_itree_t *tree, wmem_allocator_t *allocator, uint64_t low, uint64_t high); + + +/** + * Print ranges along the tree + */ +void +wmem_print_itree(wmem_itree_t *tree); + +/** @} + * @} */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __WMEM_INTERVAL_TREE_H__ */ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_list.c b/wsutil/wmem/wmem_list.c new file mode 100644 index 00000000..c03ffe31 --- /dev/null +++ b/wsutil/wmem/wmem_list.c @@ -0,0 +1,276 @@ +/* wmem_list.c + * Wireshark Memory Manager Doubly-Linked List + * Copyright 2012, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <string.h> +#include <glib.h> + +#include "wmem_core.h" +#include "wmem_list.h" + +struct _wmem_list_frame_t { + struct _wmem_list_frame_t *next, *prev; + void *data; +}; + +struct _wmem_list_t { + unsigned count; + wmem_list_frame_t *head, *tail; + wmem_allocator_t *allocator; +}; + +unsigned +wmem_list_count(const wmem_list_t *list) +{ + return list->count; +} + +wmem_list_frame_t * +wmem_list_head(const wmem_list_t *list) +{ + return list->head; +} + +wmem_list_frame_t * +wmem_list_tail(const wmem_list_t *list) +{ + return list->tail; +} + +wmem_list_frame_t * +wmem_list_frame_next(const wmem_list_frame_t *frame) +{ + return frame->next; +} + +wmem_list_frame_t * +wmem_list_frame_prev(const wmem_list_frame_t *frame) +{ + return frame->prev; +} + +void * +wmem_list_frame_data(const wmem_list_frame_t *frame) +{ + return frame->data; +} + +void +wmem_list_remove(wmem_list_t *list, void *data) +{ + wmem_list_frame_t *frame; + + frame = list->head; + + while (frame && frame->data != data) { + frame = frame->next; + } + + if (frame == NULL) { + return; + } + + wmem_list_remove_frame(list, frame); +} + +void +wmem_list_remove_frame(wmem_list_t *list, wmem_list_frame_t *frame) +{ + if (frame->prev) { + frame->prev->next = frame->next; + } + else { + list->head = frame->next; + } + + if (frame->next) { + frame->next->prev = frame->prev; + } + else { + list->tail = frame->prev; + } + + list->count--; + wmem_free(list->allocator, frame); +} + +wmem_list_frame_t * +wmem_list_find(wmem_list_t *list, const void *data) +{ + wmem_list_frame_t *cur; + + for (cur = list->head; cur; cur = cur->next) { + if(cur->data == data) + return cur; + } + + return NULL; +} + +wmem_list_frame_t * +wmem_list_find_custom(wmem_list_t *list, const void *data, GCompareFunc compare_func) +{ + wmem_list_frame_t *cur; + + for (cur = list->head; cur != NULL; cur = cur->next) { + if (compare_func(cur->data, data) == 0) { + return cur; + } + } + + return NULL; +} + +void +wmem_list_prepend(wmem_list_t *list, void *data) +{ + wmem_list_frame_t *new_frame; + + new_frame = wmem_new(list->allocator, wmem_list_frame_t); + + new_frame->data = data; + new_frame->next = list->head; + new_frame->prev = NULL; + + if (list->head) { + list->head->prev = new_frame; + } + else { + list->tail = new_frame; + } + + list->head = new_frame; + list->count++; +} + +void +wmem_list_append(wmem_list_t *list, void *data) +{ + wmem_list_frame_t *new_frame; + + new_frame = wmem_new(list->allocator, wmem_list_frame_t); + new_frame->data = data; + new_frame->next = NULL; + new_frame->prev = list->tail; + + if (list->tail) { + list->tail->next = new_frame; + } + else { + list->head = new_frame; + } + + list->tail = new_frame; + list->count++; +} + +void +wmem_list_insert_sorted(wmem_list_t *list, void* data, GCompareFunc func) +{ + wmem_list_frame_t *new_frame; + wmem_list_frame_t *cur; + wmem_list_frame_t *prev; + + new_frame = wmem_new(list->allocator, wmem_list_frame_t); + new_frame->data = data; + new_frame->next = NULL; + new_frame->prev = NULL; + + list->count++; + + if (!list->head) { + list->head = new_frame; + list->tail = new_frame; + return; + } + + cur = list->head; + + if (func(cur->data, data) >= 0) { + cur->prev = new_frame; + new_frame->next = cur; + list->head = new_frame; + return; + } + + do { + prev = cur; + cur = cur->next; + } while (cur && func(cur->data, data) <= 0); + + if (!cur) { + prev->next = new_frame; + new_frame->prev = prev; + list->tail = new_frame; + return; + } + + new_frame->prev = prev; + new_frame->next = cur; + new_frame->prev->next = new_frame; + new_frame->next->prev = new_frame; +} + +wmem_list_t * +wmem_list_new(wmem_allocator_t *allocator) +{ + wmem_list_t *list; + + list = wmem_new(allocator, wmem_list_t); + + list->count = 0; + list->head = NULL; + list->tail = NULL; + list->allocator = allocator; + + return list; +} + +void +wmem_destroy_list(wmem_list_t *list) +{ + wmem_list_frame_t *cur, *next; + + cur = list->head; + + while (cur) { + next = cur->next; + wmem_free(list->allocator, cur); + cur = next; + } + + wmem_free(list->allocator, list); +} + +void +wmem_list_foreach(wmem_list_t *list, GFunc foreach_func, void * user_data) +{ + wmem_list_frame_t *cur; + + cur = list->head; + + while (cur) { + foreach_func(cur->data, user_data); + cur = cur->next; + } +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_list.h b/wsutil/wmem/wmem_list.h new file mode 100644 index 00000000..fc0e5229 --- /dev/null +++ b/wsutil/wmem/wmem_list.h @@ -0,0 +1,128 @@ +/** @file + * Definitions for the Wireshark Memory Manager Doubly-Linked List + * Copyright 2012, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WMEM_LIST_H__ +#define __WMEM_LIST_H__ + +#include <string.h> +#include <glib.h> + +#include "wmem_core.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** @addtogroup wmem + * @{ + * @defgroup wmem-list Doubly-Linked List + * + * A doubly-linked list implementation on top of wmem. + * + * @{ + */ + +struct _wmem_list_t; +struct _wmem_list_frame_t; + +typedef struct _wmem_list_t wmem_list_t; +typedef struct _wmem_list_frame_t wmem_list_frame_t; + +WS_DLL_PUBLIC +unsigned +wmem_list_count(const wmem_list_t *list); + +WS_DLL_PUBLIC +wmem_list_frame_t * +wmem_list_head(const wmem_list_t *list); + +WS_DLL_PUBLIC +wmem_list_frame_t * +wmem_list_tail(const wmem_list_t *list); + +WS_DLL_PUBLIC +wmem_list_frame_t * +wmem_list_frame_next(const wmem_list_frame_t *frame); + +WS_DLL_PUBLIC +wmem_list_frame_t * +wmem_list_frame_prev(const wmem_list_frame_t *frame); + +WS_DLL_PUBLIC +void * +wmem_list_frame_data(const wmem_list_frame_t *frame); + +WS_DLL_PUBLIC +void +wmem_list_remove(wmem_list_t *list, void *data); + +WS_DLL_PUBLIC +void +wmem_list_remove_frame(wmem_list_t *list, wmem_list_frame_t *frame); + +/* + * Linear search, search is O(n) + */ +WS_DLL_PUBLIC +wmem_list_frame_t * +wmem_list_find(wmem_list_t *list, const void *data); + +WS_DLL_PUBLIC +wmem_list_frame_t * +wmem_list_find_custom(wmem_list_t *list, const void *data, GCompareFunc func); + +WS_DLL_PUBLIC +void +wmem_list_prepend(wmem_list_t *list, void *data); + +WS_DLL_PUBLIC +void +wmem_list_append(wmem_list_t *list, void *data); + +WS_DLL_PUBLIC +void +wmem_list_insert_sorted(wmem_list_t *list, void* data, GCompareFunc func); + + +WS_DLL_PUBLIC +wmem_list_t * +wmem_list_new(wmem_allocator_t *allocator) +G_GNUC_MALLOC; + +WS_DLL_PUBLIC +void +wmem_list_foreach(wmem_list_t *list, GFunc foreach_func, void * user_data); + +WS_DLL_PUBLIC +void +wmem_destroy_list(wmem_list_t *list); + +/** @} + * @} */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __WMEM_LIST_H__ */ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_map.c b/wsutil/wmem/wmem_map.c new file mode 100644 index 00000000..ea76f455 --- /dev/null +++ b/wsutil/wmem/wmem_map.c @@ -0,0 +1,515 @@ +/* wmem_map.c + * Wireshark Memory Manager Hash Map + * Copyright 2014, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#include "config.h" + +#include <glib.h> + +#include "wmem_core.h" +#include "wmem_list.h" +#include "wmem_map.h" +#include "wmem_map_int.h" +#include "wmem_user_cb.h" + +static uint32_t x; /* Used for universal integer hashing (see the HASH macro) */ + +/* Used for the wmem_strong_hash() function */ +static uint32_t preseed; +static uint32_t postseed; + +void +wmem_init_hashing(void) +{ + x = g_random_int(); + if (G_UNLIKELY(x == 0)) + x = 1; + + preseed = g_random_int(); + postseed = g_random_int(); +} + +typedef struct _wmem_map_item_t { + const void *key; + void *value; + struct _wmem_map_item_t *next; +} wmem_map_item_t; + +struct _wmem_map_t { + unsigned count; /* number of items stored */ + + /* The base-2 logarithm of the actual size of the table. We store this + * value for efficiency in hashing, since finding the actual capacity + * becomes just a left-shift (see the CAPACITY macro) whereas taking + * logarithms is expensive. */ + size_t capacity; + + wmem_map_item_t **table; + + GHashFunc hash_func; + GEqualFunc eql_func; + + unsigned metadata_scope_cb_id; + unsigned data_scope_cb_id; + + wmem_allocator_t *metadata_allocator; + wmem_allocator_t *data_allocator; +}; + +/* As per the comment on the 'capacity' member of the wmem_map_t struct, this is + * the base-2 logarithm, meaning the actual default capacity is 2^5 = 32 */ +#define WMEM_MAP_DEFAULT_CAPACITY 5 + +/* Macro for calculating the real capacity of the map by using a left-shift to + * do the 2^x operation. */ +#define CAPACITY(MAP) (((size_t)1) << (MAP)->capacity) + +/* Efficient universal integer hashing: + * https://en.wikipedia.org/wiki/Universal_hashing#Avoiding_modular_arithmetic + */ +#define HASH(MAP, KEY) \ + ((uint32_t)(((MAP)->hash_func(KEY) * x) >> (32 - (MAP)->capacity))) + +static void +wmem_map_init_table(wmem_map_t *map) +{ + map->count = 0; + map->capacity = WMEM_MAP_DEFAULT_CAPACITY; + map->table = wmem_alloc0_array(map->data_allocator, wmem_map_item_t*, CAPACITY(map)); +} + +wmem_map_t * +wmem_map_new(wmem_allocator_t *allocator, + GHashFunc hash_func, GEqualFunc eql_func) +{ + wmem_map_t *map; + + map = wmem_new(allocator, wmem_map_t); + + map->hash_func = hash_func; + map->eql_func = eql_func; + map->metadata_allocator = allocator; + map->data_allocator = allocator; + map->count = 0; + map->table = NULL; + + return map; +} + +static bool +wmem_map_reset_cb(wmem_allocator_t *allocator _U_, wmem_cb_event_t event, + void *user_data) +{ + wmem_map_t *map = (wmem_map_t*)user_data; + + map->count = 0; + map->table = NULL; + + if (event == WMEM_CB_DESTROY_EVENT) { + wmem_unregister_callback(map->metadata_allocator, map->metadata_scope_cb_id); + wmem_free(map->metadata_allocator, map); + } + + return true; +} + +static bool +wmem_map_destroy_cb(wmem_allocator_t *allocator _U_, wmem_cb_event_t event _U_, + void *user_data) +{ + wmem_map_t *map = (wmem_map_t*)user_data; + + wmem_unregister_callback(map->data_allocator, map->data_scope_cb_id); + + return false; +} + +wmem_map_t * +wmem_map_new_autoreset(wmem_allocator_t *metadata_scope, wmem_allocator_t *data_scope, + GHashFunc hash_func, GEqualFunc eql_func) +{ + wmem_map_t *map; + + map = wmem_new(metadata_scope, wmem_map_t); + + map->hash_func = hash_func; + map->eql_func = eql_func; + map->metadata_allocator = metadata_scope; + map->data_allocator = data_scope; + map->count = 0; + map->table = NULL; + + map->metadata_scope_cb_id = wmem_register_callback(metadata_scope, wmem_map_destroy_cb, map); + map->data_scope_cb_id = wmem_register_callback(data_scope, wmem_map_reset_cb, map); + + return map; +} + +static inline void +wmem_map_grow(wmem_map_t *map) +{ + wmem_map_item_t **old_table, *cur, *nxt; + size_t old_cap, i; + unsigned slot; + + /* store the old table and capacity */ + old_table = map->table; + old_cap = CAPACITY(map); + + /* double the size (capacity is base-2 logarithm, so this just means + * increment it) and allocate new table */ + map->capacity++; + map->table = wmem_alloc0_array(map->data_allocator, wmem_map_item_t*, CAPACITY(map)); + + /* copy all the elements over from the old table */ + for (i=0; i<old_cap; i++) { + cur = old_table[i]; + while (cur) { + nxt = cur->next; + slot = HASH(map, cur->key); + cur->next = map->table[slot]; + map->table[slot] = cur; + cur = nxt; + } + } + + /* free the old table */ + wmem_free(map->data_allocator, old_table); +} + +void * +wmem_map_insert(wmem_map_t *map, const void *key, void *value) +{ + wmem_map_item_t **item; + void *old_val; + + /* Make sure we have a table */ + if (map->table == NULL) { + wmem_map_init_table(map); + } + + /* get a pointer to the slot */ + item = &(map->table[HASH(map, key)]); + + /* check existing items in that slot */ + while (*item) { + if (map->eql_func(key, (*item)->key)) { + /* replace and return old value for this key */ + old_val = (*item)->value; + (*item)->value = value; + return old_val; + } + item = &((*item)->next); + } + + /* insert new item */ + (*item) = wmem_new(map->data_allocator, wmem_map_item_t); + + (*item)->key = key; + (*item)->value = value; + (*item)->next = NULL; + + map->count++; + + /* increase size if we are over-full */ + if (map->count >= CAPACITY(map)) { + wmem_map_grow(map); + } + + /* no previous entry, return NULL */ + return NULL; +} + +bool +wmem_map_contains(wmem_map_t *map, const void *key) +{ + wmem_map_item_t *item; + + /* Make sure we have a table */ + if (map->table == NULL) { + return false; + } + + /* find correct slot */ + item = map->table[HASH(map, key)]; + + /* scan list of items in this slot for the correct value */ + while (item) { + if (map->eql_func(key, item->key)) { + return true; + } + item = item->next; + } + + return false; +} + +void * +wmem_map_lookup(wmem_map_t *map, const void *key) +{ + wmem_map_item_t *item; + + /* Make sure we have a table */ + if (map->table == NULL) { + return NULL; + } + + /* find correct slot */ + item = map->table[HASH(map, key)]; + + /* scan list of items in this slot for the correct value */ + while (item) { + if (map->eql_func(key, item->key)) { + return item->value; + } + item = item->next; + } + + return NULL; +} + +bool +wmem_map_lookup_extended(wmem_map_t *map, const void *key, const void **orig_key, void **value) +{ + wmem_map_item_t *item; + + /* Make sure we have a table */ + if (map->table == NULL) { + return false; + } + + /* find correct slot */ + item = map->table[HASH(map, key)]; + + /* scan list of items in this slot for the correct value */ + while (item) { + if (map->eql_func(key, item->key)) { + if (orig_key) { + *orig_key = item->key; + } + if (value) { + *value = item->value; + } + return true; + } + item = item->next; + } + + return false; +} + +void * +wmem_map_remove(wmem_map_t *map, const void *key) +{ + wmem_map_item_t **item, *tmp; + void *value; + + /* Make sure we have a table */ + if (map->table == NULL) { + return NULL; + } + + /* get a pointer to the slot */ + item = &(map->table[HASH(map, key)]); + + /* check the items in that slot */ + while (*item) { + if (map->eql_func(key, (*item)->key)) { + /* found it */ + tmp = (*item); + value = tmp->value; + (*item) = tmp->next; + wmem_free(map->data_allocator, tmp); + map->count--; + return value; + } + item = &((*item)->next); + } + + /* didn't find it */ + return NULL; +} + +bool +wmem_map_steal(wmem_map_t *map, const void *key) +{ + wmem_map_item_t **item, *tmp; + + /* Make sure we have a table */ + if (map->table == NULL) { + return false; + } + + /* get a pointer to the slot */ + item = &(map->table[HASH(map, key)]); + + /* check the items in that slot */ + while (*item) { + if (map->eql_func(key, (*item)->key)) { + /* found it */ + tmp = (*item); + (*item) = tmp->next; + map->count--; + return true; + } + item = &((*item)->next); + } + + /* didn't find it */ + return false; +} + +wmem_list_t* +wmem_map_get_keys(wmem_allocator_t *list_allocator, wmem_map_t *map) +{ + size_t capacity, i; + wmem_map_item_t *cur; + wmem_list_t* list = wmem_list_new(list_allocator); + + if (map->table != NULL) { + capacity = CAPACITY(map); + + /* copy all the elements into the list over from table */ + for (i=0; i<capacity; i++) { + cur = map->table[i]; + while (cur) { + wmem_list_prepend(list, (void*)cur->key); + cur = cur->next; + } + } + } + + return list; +} + +void +wmem_map_foreach(wmem_map_t *map, GHFunc foreach_func, void * user_data) +{ + wmem_map_item_t *cur; + unsigned i; + + /* Make sure we have a table */ + if (map->table == NULL) { + return; + } + + for (i = 0; i < CAPACITY(map); i++) { + cur = map->table[i]; + while (cur) { + foreach_func((void *)cur->key, (void *)cur->value, user_data); + cur = cur->next; + } + } +} + +unsigned +wmem_map_foreach_remove(wmem_map_t *map, GHRFunc foreach_func, void * user_data) +{ + wmem_map_item_t **item, *tmp; + unsigned i, deleted = 0; + + /* Make sure we have a table */ + if (map->table == NULL) { + return 0; + } + + for (i = 0; i < CAPACITY(map); i++) { + item = &(map->table[i]); + while (*item) { + if (foreach_func((void *)(*item)->key, (void *)(*item)->value, user_data)) { + tmp = *item; + *item = tmp->next; + wmem_free(map->data_allocator, tmp); + map->count--; + deleted++; + } else { + item = &((*item)->next); + } + } + } + return deleted; +} + +unsigned +wmem_map_size(wmem_map_t *map) +{ + return map->count; +} + +/* Borrowed from Perl 5.18. This is based on Bob Jenkin's one-at-a-time + * algorithm with some additional randomness seeded in. It is believed to be + * generally secure against collision attacks. See + * http://blog.booking.com/hardening-perls-hash-function.html + */ +uint32_t +wmem_strong_hash(const uint8_t *buf, const size_t len) +{ + const uint8_t * const end = (const uint8_t *)buf + len; + uint32_t hash = preseed + (uint32_t)len; + + while (buf < end) { + hash += (hash << 10); + hash ^= (hash >> 6); + hash += *buf++; + } + + hash += (hash << 10); + hash ^= (hash >> 6); + hash += ((uint8_t*)&postseed)[0]; + + hash += (hash << 10); + hash ^= (hash >> 6); + hash += ((uint8_t*)&postseed)[1]; + + hash += (hash << 10); + hash ^= (hash >> 6); + hash += ((uint8_t*)&postseed)[2]; + + hash += (hash << 10); + hash ^= (hash >> 6); + hash += ((uint8_t*)&postseed)[3]; + + hash += (hash << 10); + hash ^= (hash >> 6); + + hash += (hash << 3); + hash ^= (hash >> 11); + return (hash + (hash << 15)); +} + +unsigned +wmem_str_hash(gconstpointer key) +{ + return wmem_strong_hash((const uint8_t *)key, strlen((const char *)key)); +} + +unsigned +wmem_int64_hash(gconstpointer key) +{ + return wmem_strong_hash((const uint8_t *)key, sizeof(uint64_t)); +} + +unsigned +wmem_double_hash(gconstpointer key) +{ + return wmem_strong_hash((const uint8_t *)key, sizeof(double)); +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_map.h b/wsutil/wmem/wmem_map.h new file mode 100644 index 00000000..a6886dd4 --- /dev/null +++ b/wsutil/wmem/wmem_map.h @@ -0,0 +1,246 @@ +/** @file + * Definitions for the Wireshark Memory Manager Hash Map + * Copyright 2014, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WMEM_MAP_H__ +#define __WMEM_MAP_H__ + +#include <glib.h> + +#include "wmem_core.h" +#include "wmem_list.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** @addtogroup wmem + * @{ + * @defgroup wmem-map Hash Map + * + * A hash map implementation on top of wmem. Provides insertion, deletion and + * lookup in expected amortized constant time. Uses universal hashing to map + * keys into buckets, and provides a generic strong hash function that makes + * it secure against algorithmic complexity attacks, and suitable for use + * even with untrusted data. + * + * @{ + */ + +struct _wmem_map_t; +typedef struct _wmem_map_t wmem_map_t; + +/** Creates a map with the given allocator scope. When the scope is emptied, + * the map is fully destroyed. Items stored in it will not be freed unless they + * were allocated from the same scope. For details on the GHashFunc and + * GEqualFunc parameters, see the glib documentation at: + * https://developer-old.gnome.org/glib/stable/glib-Hash-Tables.html + * + * If the keys are coming from untrusted data, do *not* use glib's default hash + * functions for strings, int64s or doubles. Wmem provides stronger equivalents + * below. Feel free to use the g_direct_hash, g_int_hash, and any of the + * g_*_equal functions though, as they should be safe. + * + * @param allocator The allocator scope with which to create the map. + * @param hash_func The hash function used to place inserted keys. + * @param eql_func The equality function used to compare inserted keys. + * @return The newly-allocated map. + */ +WS_DLL_PUBLIC +wmem_map_t * +wmem_map_new(wmem_allocator_t *allocator, + GHashFunc hash_func, GEqualFunc eql_func) +G_GNUC_MALLOC; + +/** Creates a map with two allocator scopes. The base structure lives in the + * metadata scope, and the map data lives in the data scope. Every time free_all + * occurs in the data scope the map is transparently emptied without affecting + * the location of the base / metadata structure. + * + * WARNING: None of the map (even the part in the metadata scope) can be used + * after the data scope has been *destroyed*. + * + * The primary use for this function is to create maps that reset for each new + * capture file that is loaded. This can be done by specifying wmem_epan_scope() + * as the metadata scope and wmem_file_scope() as the data scope. + */ +WS_DLL_PUBLIC +wmem_map_t * +wmem_map_new_autoreset(wmem_allocator_t *metadata_scope, wmem_allocator_t *data_scope, + GHashFunc hash_func, GEqualFunc eql_func) +G_GNUC_MALLOC; + +/** Inserts a value into the map. + * + * @param map The map to insert into. + * @param key The key to insert by. + * @param value The value to insert. + * @return The previous value stored at this key if any, or NULL. + */ +WS_DLL_PUBLIC +void * +wmem_map_insert(wmem_map_t *map, const void *key, void *value); + +/** Check if a value is in the map. + * + * @param map The map to search in. + * @param key The key to lookup. + * @return true if the key is in the map, otherwise false. + */ +WS_DLL_PUBLIC +bool +wmem_map_contains(wmem_map_t *map, const void *key); + +/** Lookup a value in the map. + * + * @param map The map to search in. + * @param key The key to lookup. + * @return The value stored at the key if any, or NULL. + */ +WS_DLL_PUBLIC +void * +wmem_map_lookup(wmem_map_t *map, const void *key); + +/** Lookup a value in the map, returning the key, value, and a boolean which + * is true if the key is found. + * + * @param map The map to search in. + * @param key The key to lookup. + * @param orig_key (optional) The key that was determined to be a match, if any. + * @param value (optional) The value stored at the key, if any. + * @return true if the key is in the map, otherwise false. + */ +WS_DLL_PUBLIC +bool +wmem_map_lookup_extended(wmem_map_t *map, const void *key, const void **orig_key, void **value); + +/** Remove a value from the map. If no value is stored at that key, nothing + * happens. + * + * @param map The map to remove from. + * @param key The key of the value to remove. + * @return The (removed) value stored at the key if any, or NULL. + */ +WS_DLL_PUBLIC +void * +wmem_map_remove(wmem_map_t *map, const void *key); + +/** Remove a key and value from the map but does not destroy (free) them. If no + * value is stored at that key, nothing happens. + * + * @param map The map to remove from. + * @param key The key of the value to remove. + * @return true if key is found false if not. + */ +WS_DLL_PUBLIC +bool +wmem_map_steal(wmem_map_t *map, const void *key); + +/** Retrieves a list of keys inside the map + * + * @param list_allocator The allocator scope for the returned list. + * @param map The map to extract keys from + * @return list of keys in the map + */ +WS_DLL_PUBLIC +wmem_list_t* +wmem_map_get_keys(wmem_allocator_t *list_allocator, wmem_map_t *map); + +/** Run a function against all key/value pairs in the map. The order + * of the calls is unpredictable, since it is based on the internal + * storage of data. + * + * @param map The map to use + * @param foreach_func the function to call for each key/value pair + * @param user_data user data to pass to the function + */ +WS_DLL_PUBLIC +void +wmem_map_foreach(wmem_map_t *map, GHFunc foreach_func, void * user_data); + +/** Run a function against all key/value pairs in the map. If the + * function returns true, then the key/value pair is removed from + * the map. The order of the calls is unpredictable, since it is + * based on the internal storage of data. + * + * @param map The map to use + * @param foreach_func the function to call for each key/value pair + * @param user_data user data to pass to the function + * @return The number of items removed + */ +WS_DLL_PUBLIC +unsigned +wmem_map_foreach_remove(wmem_map_t *map, GHRFunc foreach_func, void * user_data); + +/** Return the number of elements of the map. + * + * @param map The map to use + * @return the number of elements +*/ +WS_DLL_PUBLIC +unsigned +wmem_map_size(wmem_map_t *map); + +/** Compute a strong hash value for an arbitrary sequence of bytes. Use of this + * hash value should be secure against algorithmic complexity attacks, even for + * short keys. The computation uses a random seed which is generated on wmem + * initialization, so the same key will hash to different values on different + * runs of the application. + * + * @param buf The buffer of bytes (does not have to be aligned). + * @param len The length of buf to use for the hash computation. + * @return The hash value. + */ +WS_DLL_PUBLIC +uint32_t +wmem_strong_hash(const uint8_t *buf, const size_t len); + +/** An implementation of GHashFunc using wmem_strong_hash. Prefer this over + * g_str_hash when the data comes from an untrusted source. + */ +WS_DLL_PUBLIC +unsigned +wmem_str_hash(gconstpointer key); + +/** An implementation of GHashFunc using wmem_strong_hash. Prefer this over + * g_int64_hash when the data comes from an untrusted source. + */ +WS_DLL_PUBLIC +unsigned +wmem_int64_hash(gconstpointer key); + +/** An implementation of GHashFunc using wmem_strong_hash. Prefer this over + * g_double_hash when the data comes from an untrusted source. + */ +WS_DLL_PUBLIC +unsigned +wmem_double_hash(gconstpointer key); + +/** @} + * @} */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __WMEM_MAP_H__ */ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_map_int.h b/wsutil/wmem/wmem_map_int.h new file mode 100644 index 00000000..2798983a --- /dev/null +++ b/wsutil/wmem/wmem_map_int.h @@ -0,0 +1,41 @@ +/** @file + * + * Definitions for the Wireshark Memory Manager Hash Map Internals + * Copyright 2014, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WMEM_MAP_INT_H__ +#define __WMEM_MAP_INT_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +WS_DLL_LOCAL +void +wmem_init_hashing(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __WMEM_MAP_INT_H__ */ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_miscutl.c b/wsutil/wmem/wmem_miscutl.c new file mode 100644 index 00000000..14426447 --- /dev/null +++ b/wsutil/wmem/wmem_miscutl.c @@ -0,0 +1,55 @@ +/* wmem_miscutl.c + * Wireshark Memory Manager Misc Utilities + * Copyright 2013, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <string.h> +#include <glib.h> + +#include "wmem_core.h" +#include "wmem_miscutl.h" + +void * +wmem_memdup(wmem_allocator_t *allocator, const void *source, const size_t size) +{ + void *dest; + + if (!size) + return NULL; + + dest = wmem_alloc(allocator, size); + memcpy(dest, source, size); + + return dest; +} + +int +wmem_compare_int(gconstpointer a, gconstpointer b) +{ + return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b); +} + +int +wmem_compare_uint(gconstpointer a, gconstpointer b) +{ + return GPOINTER_TO_UINT(a) > GPOINTER_TO_UINT(b) ? 1 : (GPOINTER_TO_UINT(a) < GPOINTER_TO_UINT(b) ? -1 : 0); +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_miscutl.h b/wsutil/wmem/wmem_miscutl.h new file mode 100644 index 00000000..714ee5cc --- /dev/null +++ b/wsutil/wmem/wmem_miscutl.h @@ -0,0 +1,73 @@ +/** @file + * Definitions for the Wireshark Memory Manager Misc Utilities + * Copyright 2013, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WMEM_MISCUTL_H__ +#define __WMEM_MISCUTL_H__ + +#include <string.h> +#include <glib.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** @addtogroup wmem + * @{ + * @defgroup wmem-strutl String Utilities + * + * A collection of misc. utility functions for wmem. + * + * @{ + */ + +/** Copies a block of memory. + * + * @param allocator The allocator object to use to allocate memory to copy into. + * @param source The pointer to the memory block to copy. + * @param size The amount of memory to copy. + * @return The location of the memory copy or NULL if size is 0. + */ +WS_DLL_PUBLIC +void * +wmem_memdup(wmem_allocator_t *allocator, const void *source, const size_t size) +G_GNUC_MALLOC; + +/** Generic GCompareFunc implementations to compare signed/unsigned integer + */ +WS_DLL_PUBLIC +int +wmem_compare_int(gconstpointer a, gconstpointer b); + +WS_DLL_PUBLIC +int +wmem_compare_uint(gconstpointer a, gconstpointer b); + +/** @} + * @} */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __WMEM_MISCUTL_H__ */ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_multimap.c b/wsutil/wmem/wmem_multimap.c new file mode 100644 index 00000000..b36e5ced --- /dev/null +++ b/wsutil/wmem/wmem_multimap.c @@ -0,0 +1,185 @@ +/* wmem_multimap.c + * Wireshark Memory Manager Hash Multimap + * Copyright 2021, John Thacker <johnthacker@gmail.com> + * Copyright 2014, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#include "config.h" + +#include <glib.h> + +#include "wmem_core.h" +#include "wmem_list.h" +#include "wmem_map.h" +#include "wmem_multimap.h" +#include "wmem_tree.h" +#include "wmem_user_cb.h" + +struct _wmem_multimap_t { + + wmem_map_t *map; + + unsigned metadata_scope_cb_id; + unsigned data_scope_cb_id; + + wmem_allocator_t *metadata_allocator; + wmem_allocator_t *data_allocator; +}; + +wmem_multimap_t * +wmem_multimap_new(wmem_allocator_t *allocator, + GHashFunc hash_func, GEqualFunc eql_func) +{ + wmem_multimap_t *multimap; + + multimap = wmem_new(allocator, wmem_multimap_t); + + multimap->map = wmem_map_new(allocator, hash_func, eql_func); + multimap->metadata_allocator = allocator; + multimap->data_allocator = allocator; + + return multimap; +} + +static bool +wmem_multimap_reset_cb(wmem_allocator_t *allocator _U_, wmem_cb_event_t event, + void *user_data) +{ + wmem_multimap_t *multimap = (wmem_multimap_t*)user_data; + + if (event == WMEM_CB_DESTROY_EVENT) { + wmem_unregister_callback(multimap->metadata_allocator, multimap->metadata_scope_cb_id); + wmem_free(multimap->metadata_allocator, multimap); + } + + return true; +} + +static bool +wmem_multimap_destroy_cb(wmem_allocator_t *allocator _U_, wmem_cb_event_t event _U_, + void *user_data) +{ + wmem_multimap_t *multimap = (wmem_multimap_t*)user_data; + + wmem_unregister_callback(multimap->data_allocator, multimap->data_scope_cb_id); + + return false; +} + +wmem_multimap_t * +wmem_multimap_new_autoreset(wmem_allocator_t *metadata_scope, wmem_allocator_t *data_scope, + GHashFunc hash_func, GEqualFunc eql_func) +{ + wmem_multimap_t *multimap; + + multimap = wmem_new(metadata_scope, wmem_multimap_t); + + multimap->map = wmem_map_new_autoreset(metadata_scope, data_scope, hash_func, eql_func); + multimap->metadata_allocator = metadata_scope; + multimap->data_allocator = data_scope; + + multimap->metadata_scope_cb_id = wmem_register_callback(metadata_scope, wmem_multimap_destroy_cb, multimap); + multimap->data_scope_cb_id = wmem_register_callback(data_scope, wmem_multimap_reset_cb, multimap); + + return multimap; +} + +wmem_list_t* +wmem_multimap_get_keys(wmem_allocator_t *list_allocator, wmem_multimap_t *map) +{ + return wmem_map_get_keys(list_allocator, map->map); +} + +static void +count_nodes(void * key _U_, void * value, void * user_data) +{ + unsigned* count = (unsigned*)user_data; + (*count) += wmem_tree_count(value); +} + +unsigned +wmem_multimap_size(wmem_multimap_t *map) +{ + unsigned count = 0; + + wmem_map_foreach(map->map, count_nodes, &count); + return count; +} + +unsigned +wmem_multimap_count(wmem_multimap_t *map, const void *key) +{ + wmem_tree_t *tree; + + if ((tree = wmem_map_lookup(map->map, key)) == NULL) { + return 0; + } + return wmem_tree_count(tree); +} + +bool +wmem_multimap_insert32(wmem_multimap_t *map, const void *key, uint32_t frame_num, void *value) +{ + wmem_tree_t *tree; + bool ret = true; + + if ((tree = wmem_map_lookup(map->map, key)) == NULL) { + tree = wmem_tree_new(map->data_allocator); + wmem_map_insert(map->map, key, tree); + ret = false; + } + wmem_tree_insert32(tree, frame_num, value); + + return ret; +} + +void * +wmem_multimap_lookup32(wmem_multimap_t *map, const void *key, uint32_t frame_num) +{ + wmem_tree_t *tree; + + if ((tree = wmem_map_lookup(map->map, key)) == NULL) { + return NULL; + } + return wmem_tree_lookup32(tree, frame_num); +} + +void * +wmem_multimap_lookup32_le(wmem_multimap_t *map, const void *key, uint32_t frame_num) +{ + wmem_tree_t *tree; + + if ((tree = wmem_map_lookup(map->map, key)) == NULL) { + return NULL; + } + return wmem_tree_lookup32_le(tree, frame_num); +} + +void * +wmem_multimap_remove32(wmem_multimap_t *map, const void *key, const uint32_t frame_num) +{ + wmem_tree_t *tree; + + if ((tree = wmem_map_lookup(map->map, key)) == NULL) { + return NULL; + } + return wmem_tree_remove32(tree, frame_num); +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_multimap.h b/wsutil/wmem/wmem_multimap.h new file mode 100644 index 00000000..57f0fefb --- /dev/null +++ b/wsutil/wmem/wmem_multimap.h @@ -0,0 +1,195 @@ +/** @file + * + * Definitions for the Wireshark Memory Manager Hash Multimap + * Copyright 2021, John Thacker <johnthacker@gmail.com> + * Copyright 2014, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WMEM_MULTIMAP_H__ +#define __WMEM_MULTIMAP_H__ + +#include <glib.h> + +#include "wmem_core.h" +#include "wmem_list.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** @addtogroup wmem + * @{ + * @defgroup wmem-multimap Hash Multimap + * + * A hash multimap implementation on top of wmem_map and wmem_tree, storing + * multiple values at each hash key in a tree indexed by a 32 bit integer. + * + * The primary use case is a protocol with an ID used as the hash lookup + * key that can be reused in a capture, and the frame number used as the + * tree key. We often want to find the most recent frame that had a certain + * ID, e.g. for request/response matching, and wmem_multimap_lookup32_le() + * serves that purpose. + * + * Since the tree implementation is a self-balancing red-black tree, lookup + * time is still O(log(n)) even though elements with equivalent hash keys + * are usually added in increasing order of frame number. + * + * NOTE: The multimap does not yet support inserting items without + * specifying the tree key, because the total capacity of individual trees + * (including deleted nodes) is not tracked. + * + * @{ + */ + +typedef struct _wmem_multimap_t wmem_multimap_t; + +/** Creates a multimap with the given allocator scope. When the scope is emptied, + * the map is fully destroyed. Items stored in it will not be freed unless they + * were allocated from the same scope. + * + * @param allocator The allocator scope with which to create the map. + * @param hash_func The hash function used to place inserted keys. + * @param eql_func The equality function used to compare inserted keys. + * @return The newly-allocated map. + */ +WS_DLL_PUBLIC +wmem_multimap_t * +wmem_multimap_new(wmem_allocator_t *allocator, + GHashFunc hash_func, GEqualFunc eql_func) +G_GNUC_MALLOC; + +/** Creates a multimap with two allocator scopes. The base structure lives in the + * metadata scope, and the map data lives in the data scope. Every time free_all + * occurs in the data scope the map is transparently emptied without affecting + * the location of the base / metadata structure. + * + * WARNING: None of the map (even the part in the metadata scope) can be used + * after the data scope has been *destroyed*. + * + * The primary use for this function is to create maps that reset for each new + * capture file that is loaded. This can be done by specifying wmem_epan_scope() + * as the metadata scope and wmem_file_scope() as the data scope. + */ +WS_DLL_PUBLIC +wmem_multimap_t * +wmem_multimap_new_autoreset(wmem_allocator_t *metadata_scope, wmem_allocator_t *data_scope, + GHashFunc hash_func, GEqualFunc eql_func) +G_GNUC_MALLOC; + +/** Retrieves a list of the keys inside the multimap + * + * @param list_allocator The allocator scope for the returned list. + * @param map The multimap to extract keys from + * @return list of keys in the multimap + */ +WS_DLL_PUBLIC +wmem_list_t* +wmem_multimap_get_keys(wmem_allocator_t *list_allocator, wmem_multimap_t *map); + +/** Return the total number of elements in the multimap. + * + * @param map The multimap to use + * @return the number of elements +*/ +WS_DLL_PUBLIC +unsigned +wmem_multimap_size(wmem_multimap_t *map); + +/** Returns the number of values in the multimap with a certain hash key. + * (Note: This is the number of current elements, so this can only be used to + * safely generate unique tree keys prior to insertion if no values have been + * removed, due to how the tree implementation works.) + * + * @param map The multimap to search in. + * @param key The primary key to lookup in the map. + * @return The number of values in the tree stored at map key, or zero if no + * tree exists at that key. + */ +WS_DLL_PUBLIC +unsigned +wmem_multimap_count(wmem_multimap_t *map, const void *key); + +/** Insert a value in the multimap. + * + * @param map The multimap to insert into. + * @param key The key to insert by in the map. + * @param frame_num The key to insert by in the tree. + * @param value The value to insert. + * @return true if there was already a tree mapped at key, in which case the + * caller may safely free key. (This is not necessary if key is allocated with + * a wmem pool.) + * + * Note: as with wmem_tree, if there is already a node with the same pair + * of keys, then the existing value will simply be overwritten. This is not + * a problem if the value is wmem allocated, but if it is manually managed, + * then you must ensure that the pair is unique or do a lookup before inserting. + */ +WS_DLL_PUBLIC +bool +wmem_multimap_insert32(wmem_multimap_t *map, const void *key, uint32_t frame_num, void *value); + +/** Lookup a value in the multimap combination with an exact match. + * + * @param map The multimap to search in. + * @param key The primary key to lookup in the map. + * @param frame_num The secondary key to lookup in the tree. + * @return The value stored at the keys if any, or NULL. + */ +WS_DLL_PUBLIC +void * +wmem_multimap_lookup32(wmem_multimap_t *map, const void *key, const uint32_t frame_num); + +/** Lookup a value in the multimap with an exact match for the map key + * and the largest value less than or equal to the tree key. This is + * useful for request/response matching where IDs can be reused. + * + * @param map The multimap to search in. + * @param key The primary key to lookup in the map. + * @param frame_num The secondary key to lookup in the tree. + * @return The value stored at the primary key in the map and with the largest + * key in the tree that is less than or equal to the second key if any, or NULL. + */ +WS_DLL_PUBLIC +void * +wmem_multimap_lookup32_le(wmem_multimap_t *map, const void *key, const uint32_t frame_num); + +/** Remove a value from the multimap. If no value is stored at that key pair, + * nothing happens. As with wmem_tree, this is not really a remove, but the + * value is set to NULL so that wmem_multimap_lookup32 not will find it. + * + * @param map The multimap to remove from. + * @param key The map key of the value to remove. + * @param frame_num The tree key of the value to remove. + * @return The (removed) value stored at the key if any, or NULL. + */ +WS_DLL_PUBLIC +void * +wmem_multimap_remove32(wmem_multimap_t *map, const void *key, const uint32_t frame_num); + +/** @} + * @} */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __WMEM_MULTIMAP_H__ */ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_queue.h b/wsutil/wmem/wmem_queue.h new file mode 100644 index 00000000..49cd3e39 --- /dev/null +++ b/wsutil/wmem/wmem_queue.h @@ -0,0 +1,70 @@ +/** @file + * + * Definitions for the Wireshark Memory Manager Queue + * Copyright 2013, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WMEM_QUEUE_H__ +#define __WMEM_QUEUE_H__ + +#include <string.h> +#include <glib.h> + +#include "wmem_core.h" +#include "wmem_list.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** @addtogroup wmem + * @{ + * @defgroup wmem-queue Queue + * + * A queue implementation on top of wmem. + * + * @{ + */ + +/* Wmem queue is implemented as a dumb wrapper over Wmem list and stack */ +typedef wmem_list_t wmem_queue_t; + +#define wmem_queue_count(X) wmem_list_count(X) + +#define wmem_queue_peek(QUEUE) wmem_stack_peek(QUEUE) + +#define wmem_queue_pop(QUEUE) wmem_stack_pop(QUEUE) + +#define wmem_queue_push(QUEUE, DATA) wmem_list_append((QUEUE), (DATA)) + +#define wmem_queue_new(ALLOCATOR) wmem_list_new(ALLOCATOR) + +#define wmem_destroy_queue(QUEUE) wmem_destroy_list(QUEUE) + +/** @} + * @} */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __WMEM_QUEUE_H__ */ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_stack.c b/wsutil/wmem/wmem_stack.c new file mode 100644 index 00000000..5123aad1 --- /dev/null +++ b/wsutil/wmem/wmem_stack.c @@ -0,0 +1,57 @@ +/* wmem_stack.c + * Wireshark Memory Manager Stack + * Copyright 2012, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <string.h> +#include <glib.h> + +#include "wmem-int.h" +#include "wmem_core.h" +#include "wmem_stack.h" +#include "wmem_list.h" + +/* Wmem stack is implemented as a simple wrapper over Wmem list */ + +void * +wmem_stack_peek(const wmem_stack_t *stack) +{ + wmem_list_frame_t *frame; + + frame = wmem_list_head(stack); + + ws_assert(frame); + + return wmem_list_frame_data(frame); +} + +void * +wmem_stack_pop(wmem_stack_t *stack) +{ + void *data; + + data = wmem_stack_peek(stack); + + wmem_list_remove(stack, data); + + return data; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_stack.h b/wsutil/wmem/wmem_stack.h new file mode 100644 index 00000000..b4fd2d56 --- /dev/null +++ b/wsutil/wmem/wmem_stack.h @@ -0,0 +1,73 @@ +/** @file + * Definitions for the Wireshark Memory Manager Stack + * Copyright 2012, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WMEM_STACK_H__ +#define __WMEM_STACK_H__ + +#include <string.h> +#include <glib.h> + +#include "wmem_core.h" +#include "wmem_list.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** @addtogroup wmem + * @{ + * @defgroup wmem-stack Stack + * + * A stack implementation on top of wmem. + * + * @{ + */ + +/* Wmem stack is implemented as a simple wrapper over Wmem list */ +typedef wmem_list_t wmem_stack_t; + +#define wmem_stack_count(X) wmem_list_count(X) + +WS_DLL_PUBLIC +void * +wmem_stack_peek(const wmem_stack_t *stack); + +WS_DLL_PUBLIC +void * +wmem_stack_pop(wmem_stack_t *stack); + +#define wmem_stack_push(STACK, DATA) wmem_list_prepend((STACK), (DATA)) + +#define wmem_stack_new(ALLOCATOR) wmem_list_new(ALLOCATOR) + +#define wmem_destroy_stack(STACK) wmem_destroy_list(STACK) + +/** @} + * @} */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __WMEM_STACK_H__ */ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_strbuf.c b/wsutil/wmem/wmem_strbuf.c new file mode 100644 index 00000000..ff49f6b8 --- /dev/null +++ b/wsutil/wmem/wmem_strbuf.c @@ -0,0 +1,470 @@ +/* wmem_strbuf.c + * Wireshark Memory Manager String Buffer + * Copyright 2012, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" +#include "wmem_strbuf.h" + +#include <stdio.h> +#include <errno.h> + +#include "wmem-int.h" +#include "wmem_strutl.h" + +#include <wsutil/unicode-utils.h> + +#define DEFAULT_MINIMUM_SIZE 16 + +/* _ROOM accounts for the null-terminator, _RAW_ROOM does not. + * Some functions need one, some functions need the other. */ +#define WMEM_STRBUF_ROOM(S) ((S)->alloc_size - (S)->len - 1) +#define WMEM_STRBUF_RAW_ROOM(S) ((S)->alloc_size - (S)->len) + +wmem_strbuf_t * +wmem_strbuf_new_sized(wmem_allocator_t *allocator, + size_t alloc_size) +{ + wmem_strbuf_t *strbuf; + + strbuf = wmem_new(allocator, wmem_strbuf_t); + + strbuf->allocator = allocator; + strbuf->len = 0; + strbuf->alloc_size = alloc_size ? alloc_size : DEFAULT_MINIMUM_SIZE; + + strbuf->str = (char *)wmem_alloc(strbuf->allocator, strbuf->alloc_size); + strbuf->str[0] = '\0'; + + return strbuf; +} + +wmem_strbuf_t * +wmem_strbuf_new_len(wmem_allocator_t *allocator, const char *str, size_t len) +{ + wmem_strbuf_t *strbuf; + size_t alloc_size; + + alloc_size = DEFAULT_MINIMUM_SIZE; + + /* +1 for the null-terminator */ + while (alloc_size < (len + 1)) { + alloc_size *= 2; + } + + strbuf = wmem_strbuf_new_sized(allocator, alloc_size); + + if (str && len > 0) { + ws_assert(strbuf->alloc_size >= len + 1); + memcpy(strbuf->str, str, len); + strbuf->str[len] = '\0'; + strbuf->len = len; + } + + return strbuf; +} + +wmem_strbuf_t * +wmem_strbuf_new(wmem_allocator_t *allocator, const char *str) +{ + return wmem_strbuf_new_len(allocator, str, str ? strlen(str) : 0); +} + +wmem_strbuf_t * +wmem_strbuf_dup(wmem_allocator_t *allocator, const wmem_strbuf_t *src) +{ + wmem_strbuf_t *new; + + new = wmem_strbuf_new_sized(allocator, src->alloc_size); + new->len = src->len; + memcpy(new->str, src->str, new->len); + new->str[new->len] = '\0'; + return new; +} + +/* grows the allocated size of the wmem_strbuf_t. If max_size is set, then + * not guaranteed to grow by the full amount to_add */ +static inline void +wmem_strbuf_grow(wmem_strbuf_t *strbuf, const size_t to_add) +{ + size_t new_alloc_len, new_len; + + /* short-circuit for efficiency if we have room already; greatly speeds up + * repeated calls to wmem_strbuf_append_c and others which grow a little bit + * at a time. + */ + if (WMEM_STRBUF_ROOM(strbuf) >= to_add) { + return; + } + + new_alloc_len = strbuf->alloc_size; + new_len = strbuf->len + to_add; + + /* +1 for the null-terminator */ + while (new_alloc_len < (new_len + 1)) { + new_alloc_len *= 2; + } + + if (new_alloc_len == strbuf->alloc_size) { + return; + } + + strbuf->str = (char *)wmem_realloc(strbuf->allocator, strbuf->str, new_alloc_len); + + strbuf->alloc_size = new_alloc_len; +} + +void +wmem_strbuf_append(wmem_strbuf_t *strbuf, const char *str) +{ + size_t append_len; + + if (!str || str[0] == '\0') { + return; + } + + append_len = strlen(str); + wmem_strbuf_grow(strbuf, append_len); + + ws_assert(WMEM_STRBUF_RAW_ROOM(strbuf) >= append_len + 1); + memcpy(&strbuf->str[strbuf->len], str, append_len); + strbuf->len += append_len; + strbuf->str[strbuf->len] = '\0'; +} + +void +wmem_strbuf_append_len(wmem_strbuf_t *strbuf, const char *str, size_t append_len) +{ + + if (!append_len || !str) { + return; + } + + wmem_strbuf_grow(strbuf, append_len); + + memcpy(&strbuf->str[strbuf->len], str, append_len); + strbuf->len += append_len; + strbuf->str[strbuf->len] = '\0'; +} + +static inline +int _strbuf_vsnprintf(wmem_strbuf_t *strbuf, const char *format, va_list ap) +{ + int want_len; + char *buffer = &strbuf->str[strbuf->len]; + size_t buffer_size = WMEM_STRBUF_RAW_ROOM(strbuf); + + want_len = vsnprintf(buffer, buffer_size, format, ap); + if (want_len < 0) { + /* Error. */ + g_warning("%s: vsnprintf: (%d) %s", G_STRFUNC, want_len, g_strerror(errno)); + return -1; + } + if ((size_t)want_len < buffer_size) { + /* Success. */ + strbuf->len += want_len; + return 0; + } + + /* Not enough space in buffer, output was truncated. */ + strbuf->str[strbuf->len] = '\0'; /* Reset. */ + + return want_len; /* Length (not including terminating null) that would be written + if there was enough space in buffer. */ +} + +void +wmem_strbuf_append_vprintf(wmem_strbuf_t *strbuf, const char *fmt, va_list ap) +{ + int want_len; + va_list ap2; + + va_copy(ap2, ap); + /* Try to write buffer, check if output fits. */ + want_len = _strbuf_vsnprintf(strbuf, fmt, ap2); + va_end(ap2); + if (want_len <= 0) + return; + + /* Resize buffer and try again. */ + wmem_strbuf_grow(strbuf, want_len); + want_len = _strbuf_vsnprintf(strbuf, fmt, ap); + /* Second time must succeed or error out. */ + ws_assert(want_len <= 0); +} + +void +wmem_strbuf_append_printf(wmem_strbuf_t *strbuf, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + wmem_strbuf_append_vprintf(strbuf, format, ap); + va_end(ap); +} + +void +wmem_strbuf_append_c(wmem_strbuf_t *strbuf, const char c) +{ + wmem_strbuf_grow(strbuf, 1); + + strbuf->str[strbuf->len] = c; + strbuf->len++; + strbuf->str[strbuf->len] = '\0'; +} + +void +wmem_strbuf_append_c_count(wmem_strbuf_t *strbuf, const char c, size_t count) +{ + wmem_strbuf_grow(strbuf, count); + + while (count-- > 0) { + strbuf->str[strbuf->len++] = c; + } + strbuf->str[strbuf->len] = '\0'; +} + +void +wmem_strbuf_append_unichar(wmem_strbuf_t *strbuf, const gunichar c) +{ + char buf[6]; + size_t charlen; + + charlen = g_unichar_to_utf8(c, buf); + + wmem_strbuf_grow(strbuf, charlen); + + memcpy(&strbuf->str[strbuf->len], buf, charlen); + strbuf->len += charlen; + strbuf->str[strbuf->len] = '\0'; +} + +void +wmem_strbuf_append_unichar_validated(wmem_strbuf_t *strbuf, const gunichar c) +{ + if (g_unichar_validate(c)) { + wmem_strbuf_append_unichar(strbuf, c); + } else { + wmem_strbuf_append_unichar(strbuf, UNICODE_REPLACEMENT_CHARACTER); + } +} + +static const char hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + +#define HEX_CODELEN 4 + +void +wmem_strbuf_append_hex(wmem_strbuf_t *strbuf, uint8_t ch) +{ + wmem_strbuf_grow(strbuf, HEX_CODELEN * 1); + + strbuf->str[strbuf->len++] = '\\'; + strbuf->str[strbuf->len++] = 'x'; + strbuf->str[strbuf->len++] = hex[(ch >> 4) & 0xF]; + strbuf->str[strbuf->len++] = hex[(ch >> 0) & 0xF]; + strbuf->str[strbuf->len] = '\0'; +} + +#define BMP_CODELEN 6 + +static inline +void append_hex_bmp(wmem_strbuf_t *strbuf, gunichar ch) +{ + wmem_strbuf_grow(strbuf, BMP_CODELEN * 1); + + strbuf->str[strbuf->len++] = '\\'; + strbuf->str[strbuf->len++] = 'u'; + strbuf->str[strbuf->len++] = hex[(ch >> 12) & 0xF]; + strbuf->str[strbuf->len++] = hex[(ch >> 8) & 0xF]; + strbuf->str[strbuf->len++] = hex[(ch >> 4) & 0xF]; + strbuf->str[strbuf->len++] = hex[(ch >> 0) & 0xF]; + strbuf->str[strbuf->len] = '\0'; +} + +#define ANY_CODELEN 10 + +static inline +void append_hex_any(wmem_strbuf_t *strbuf, gunichar ch) +{ + wmem_strbuf_grow(strbuf, ANY_CODELEN * 1); + + strbuf->str[strbuf->len++] = '\\'; + strbuf->str[strbuf->len++] = 'U'; + strbuf->str[strbuf->len++] = hex[(ch >> 28) & 0xF]; + strbuf->str[strbuf->len++] = hex[(ch >> 24) & 0xF]; + strbuf->str[strbuf->len++] = hex[(ch >> 20) & 0xF]; + strbuf->str[strbuf->len++] = hex[(ch >> 16) & 0xF]; + strbuf->str[strbuf->len++] = hex[(ch >> 12) & 0xF]; + strbuf->str[strbuf->len++] = hex[(ch >> 8) & 0xF]; + strbuf->str[strbuf->len++] = hex[(ch >> 4) & 0xF]; + strbuf->str[strbuf->len++] = hex[(ch >> 0) & 0xF]; + strbuf->str[strbuf->len] = '\0'; +} + +size_t +wmem_strbuf_append_hex_unichar(wmem_strbuf_t *strbuf, gunichar ch) +{ + if (ch <= 0x7f) { + wmem_strbuf_append_hex(strbuf, (uint8_t)ch); + return HEX_CODELEN; + } + if (ch <= 0xffff) { + append_hex_bmp(strbuf, ch); + return BMP_CODELEN; + } + append_hex_any(strbuf, ch); + return ANY_CODELEN; +} + +void +wmem_strbuf_truncate(wmem_strbuf_t *strbuf, const size_t len) +{ + if (len >= strbuf->len) { + return; + } + + strbuf->str[len] = '\0'; + strbuf->len = len; +} + +const char * +wmem_strbuf_get_str(const wmem_strbuf_t *strbuf) +{ + return strbuf->str; +} + +size_t +wmem_strbuf_get_len(const wmem_strbuf_t *strbuf) +{ + return strbuf->len; +} + +static inline int +_memcmp_len(const void *s1, size_t s1_len, const void *s2, size_t s2_len) +{ + size_t len; + int cmp; + + len = MIN(s1_len, s2_len); + if ((cmp = memcmp(s1, s2, len)) != 0) + return cmp; + if (s1_len < s2_len) + return -1; + if (s1_len > s2_len) + return 1; + return 0; +} + +WS_DLL_PUBLIC +int +wmem_strbuf_strcmp(const wmem_strbuf_t *sb1, const wmem_strbuf_t *sb2) +{ + return _memcmp_len(sb1->str, sb1->len, sb2->str, sb2->len); +} + +const char * +wmem_strbuf_strstr(const wmem_strbuf_t *haystack, const wmem_strbuf_t *needle) +{ + return ws_memmem(haystack->str, haystack->len, needle->str, needle->len); +} + +/* Truncates the allocated memory down to the minimal amount, frees the header + * structure, and returns a non-const pointer to the raw string. The + * wmem_strbuf_t structure cannot be used after this is called. + */ +char * +wmem_strbuf_finalize(wmem_strbuf_t *strbuf) +{ + if (strbuf == NULL) + return NULL; + + char *ret = (char *)wmem_realloc(strbuf->allocator, strbuf->str, strbuf->len+1); + + wmem_free(strbuf->allocator, strbuf); + + return ret; +} + +void +wmem_strbuf_destroy(wmem_strbuf_t *strbuf) +{ + if (strbuf == NULL) + return; + + wmem_free(strbuf->allocator, strbuf->str); + wmem_free(strbuf->allocator, strbuf); +} + +static bool +string_utf8_validate(const char *str, ssize_t max_len, const char **endpptr) +{ + bool valid; + const char *endp; + + if (max_len <= 0) { + if (endpptr) { + *endpptr = str; + } + return true; + } + + valid = g_utf8_validate(str, max_len, &endp); + + if (valid || *endp != '\0') { + if (endpptr) { + *endpptr = endp; + } + return valid; + } + + /* Invalid because of a nul byte. Skip nuls and continue. */ + max_len -= endp - str; + str = endp; + while (max_len > 0 && *str == '\0') { + str++; + max_len--; + } + return string_utf8_validate(str, max_len, endpptr); +} + +/* g_utf8_validate() returns false in the string contains embedded NUL + * bytes. We accept \x00 as valid and work around that to validate the + * entire len bytes. */ +bool +wmem_strbuf_utf8_validate(wmem_strbuf_t *strbuf, const char **endpptr) +{ + return string_utf8_validate(strbuf->str, strbuf->len, endpptr); +} + +void +wmem_strbuf_utf8_make_valid(wmem_strbuf_t *strbuf) +{ + wmem_strbuf_t *tmp = ws_utf8_make_valid_strbuf(strbuf->allocator, strbuf->str, strbuf->len); + + wmem_free(strbuf->allocator, strbuf->str); + strbuf->str = tmp->str; + strbuf->len = tmp->len; + strbuf->alloc_size = tmp->alloc_size; + + wmem_free(strbuf->allocator, tmp); +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_strbuf.h b/wsutil/wmem/wmem_strbuf.h new file mode 100644 index 00000000..9c00b6e4 --- /dev/null +++ b/wsutil/wmem/wmem_strbuf.h @@ -0,0 +1,194 @@ +/** @file + * Definitions for the Wireshark Memory Manager String Buffer + * Copyright 2012, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WMEM_STRBUF_H__ +#define __WMEM_STRBUF_H__ + +#include <ws_codepoints.h> + +#include "wmem_core.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** @addtogroup wmem + * @{ + * @defgroup wmem-strbuf String Buffer + * + * A string object implementation on top of wmem. + * + * @{ + */ + +/* Holds a wmem-allocated string-buffer. + * len is the length of the string (not counting the null-terminator) and + * should be the same as strlen(str) unless the string contains embedded + * nulls. + * alloc_size is the size of the raw buffer pointed to by str, regardless of + * what string is actually being stored (i.e. the buffer contents) + * max_size is the maximum permitted alloc_size (NOT the maximum permitted len, + * which must be one shorter than alloc_size to permit null-termination). + * When max_size is 0 (the default), no maximum is enforced. + */ +struct _wmem_strbuf_t { + /* read-only fields */ + wmem_allocator_t *allocator; + char *str; + size_t len; + + /* private fields */ + size_t alloc_size; +}; + +typedef struct _wmem_strbuf_t wmem_strbuf_t; + +WS_DLL_PUBLIC +wmem_strbuf_t * +wmem_strbuf_new_sized(wmem_allocator_t *allocator, size_t alloc_size) +G_GNUC_MALLOC; + +WS_DLL_PUBLIC +wmem_strbuf_t * +wmem_strbuf_new(wmem_allocator_t *allocator, const char *str) +G_GNUC_MALLOC; + +#define wmem_strbuf_create(allocator) \ + wmem_strbuf_new(allocator, "") + +WS_DLL_PUBLIC +wmem_strbuf_t * +wmem_strbuf_new_len(wmem_allocator_t *allocator, const char *str, size_t len) +G_GNUC_MALLOC; + +WS_DLL_PUBLIC +wmem_strbuf_t * +wmem_strbuf_dup(wmem_allocator_t *allocator, const wmem_strbuf_t *strbuf) +G_GNUC_MALLOC; + +WS_DLL_PUBLIC +void +wmem_strbuf_append(wmem_strbuf_t *strbuf, const char *str); + +/* Appends up to append_len bytes (as allowed by strbuf->max_size) from + * str. Ensures that strbuf is null terminated afterwards but will copy + * embedded nulls. */ +WS_DLL_PUBLIC +void +wmem_strbuf_append_len(wmem_strbuf_t *strbuf, const char *str, size_t append_len); + +WS_DLL_PUBLIC +void +wmem_strbuf_append_printf(wmem_strbuf_t *strbuf, const char *format, ...) +G_GNUC_PRINTF(2, 3); + +WS_DLL_PUBLIC +void +wmem_strbuf_append_vprintf(wmem_strbuf_t *strbuf, const char *fmt, va_list ap); + +WS_DLL_PUBLIC +void +wmem_strbuf_append_c(wmem_strbuf_t *strbuf, const char c); + +WS_DLL_PUBLIC +void +wmem_strbuf_append_c_count(wmem_strbuf_t *strbuf, const char c, size_t count); + +WS_DLL_PUBLIC +void +wmem_strbuf_append_unichar(wmem_strbuf_t *strbuf, const gunichar c); + +#define wmem_strbuf_append_unichar_repl(buf) \ + wmem_strbuf_append_unichar(buf, UNICODE_REPLACEMENT_CHARACTER) + +/* As wmem_strbuf_append_unichar but appends a REPLACEMENT CHARACTER + * instead for any invalid Unicode codepoints. + */ +WS_DLL_PUBLIC +void +wmem_strbuf_append_unichar_validated(wmem_strbuf_t *strbuf, const gunichar c); + +WS_DLL_PUBLIC +void +wmem_strbuf_append_hex(wmem_strbuf_t *strbuf, uint8_t); + +/* Returns the number of characters written (4, 6 or 10). */ +WS_DLL_PUBLIC +size_t +wmem_strbuf_append_hex_unichar(wmem_strbuf_t *strbuf, gunichar); + +WS_DLL_PUBLIC +void +wmem_strbuf_truncate(wmem_strbuf_t *strbuf, const size_t len); + +WS_DLL_PUBLIC +const char * +wmem_strbuf_get_str(const wmem_strbuf_t *strbuf); + +WS_DLL_PUBLIC +size_t +wmem_strbuf_get_len(const wmem_strbuf_t *strbuf); + +WS_DLL_PUBLIC +int +wmem_strbuf_strcmp(const wmem_strbuf_t *sb1, const wmem_strbuf_t *sb2); + +WS_DLL_PUBLIC +const char * +wmem_strbuf_strstr(const wmem_strbuf_t *haystack, const wmem_strbuf_t *needle); + +/** Truncates the allocated memory down to the minimal amount, frees the header + * structure, and returns a non-const pointer to the raw string. The + * wmem_strbuf_t structure cannot be used after this is called. Basically a + * destructor for when you still need the underlying C-string. + */ +WS_DLL_PUBLIC +char * +wmem_strbuf_finalize(wmem_strbuf_t *strbuf); + +WS_DLL_PUBLIC +void +wmem_strbuf_destroy(wmem_strbuf_t *strbuf); + +/* Validates the string buffer as UTF-8. + * Unlike g_utf8_validate(), accepts embedded NUL bytes as valid UTF-8. + * If endpptr is non-NULL, then the end of the valid range is stored there + * (i.e. the first invalid character, or the end of the buffer otherwise). + */ +WS_DLL_PUBLIC +bool +wmem_strbuf_utf8_validate(wmem_strbuf_t *strbuf, const char **endptr); + +WS_DLL_PUBLIC +void +wmem_strbuf_utf8_make_valid(wmem_strbuf_t *strbuf); + +/** @} + * @} */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __WMEM_STRBUF_H__ */ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_strutl.c b/wsutil/wmem/wmem_strutl.c new file mode 100644 index 00000000..99f1f8c6 --- /dev/null +++ b/wsutil/wmem/wmem_strutl.c @@ -0,0 +1,159 @@ +/* wmem_strutl.c + * Wireshark Memory Manager String Utilities + * Copyright 2012, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#define _GNU_SOURCE +#include "config.h" +#include "wmem_strutl.h" + +#include <string.h> +#include <stdio.h> +#include <errno.h> + +char * +wmem_strdup(wmem_allocator_t *allocator, const char *src) +{ + size_t len; + + /* If the string is NULL, just return the string "<NULL>" so that the + * callers don't have to bother checking it. */ + if (!src) { + src = "<NULL>"; + } + + len = strlen(src) + 1; /* +1 for the null-terminator */ + + return (char *)memcpy(wmem_alloc(allocator, len), src, len); +} + +char * +wmem_strndup(wmem_allocator_t *allocator, const char *src, const size_t len) +{ + char *dst; + unsigned i; + + dst = (char *)wmem_alloc(allocator, len+1); + + for (i=0; (i < len) && src[i]; i++) { + dst[i] = src[i]; + } + + dst[i] = '\0'; + + return dst; +} + +char * +wmem_strdup_printf(wmem_allocator_t *allocator, const char *fmt, ...) +{ + va_list ap; + char *dst; + + va_start(ap, fmt); + dst = wmem_strdup_vprintf(allocator, fmt, ap); + va_end(ap); + + return dst; +} + +#ifdef HAVE_VASPRINTF +static char * +_strdup_vasprintf(const char *fmt, va_list ap) +{ + char *str = NULL; + int ret; + + ret = vasprintf(&str, fmt, ap); + if (ret == -1 && errno == ENOMEM) { + /* Out of memory. We have to mimic GLib here and abort. */ + g_error("%s: failed to allocate memory", G_STRLOC); + } + return str; +} +#endif /* HAVE_VASPRINTF */ + +#define WMEM_STRDUP_VPRINTF_DEFAULT_BUFFER 256 +char * +wmem_strdup_vprintf(wmem_allocator_t *allocator, const char *fmt, va_list ap) +{ + va_list ap2; + char buf[WMEM_STRDUP_VPRINTF_DEFAULT_BUFFER]; + int needed_len; + char *new_buf; + size_t new_buf_size; + +#ifdef HAVE_VASPRINTF + if (allocator == NULL) { + return _strdup_vasprintf(fmt, ap); + } +#endif + + va_copy(ap2, ap); + needed_len = vsnprintf(buf, sizeof(buf), fmt, ap2); + va_end(ap2); + + new_buf_size = needed_len + 1; + new_buf = wmem_alloc(allocator, new_buf_size); + + if (new_buf_size <= WMEM_STRDUP_VPRINTF_DEFAULT_BUFFER) { + memcpy(new_buf, buf, new_buf_size); + return new_buf; + } + vsnprintf(new_buf, new_buf_size, fmt, ap); + return new_buf; +} + +/* Return the first occurrence of needle in haystack. + * If not found, return NULL. + * If either haystack or needle has 0 length, return NULL.*/ +const uint8_t * +ws_memmem(const void *_haystack, size_t haystack_len, + const void *_needle, size_t needle_len) +{ +#ifdef HAVE_MEMMEM + return memmem(_haystack, haystack_len, _needle, needle_len); +#else + /* Algorithm copied from GNU's glibc 2.3.2 memmem() under LGPL 2.1+ */ + const uint8_t *haystack = _haystack; + const uint8_t *needle = _needle; + const uint8_t *begin; + const uint8_t *const last_possible = haystack + haystack_len - needle_len; + + if (needle_len == 0) { + return NULL; + } + + if (needle_len > haystack_len) { + return NULL; + } + + for (begin = haystack ; begin <= last_possible; ++begin) { + if (begin[0] == needle[0] && + !memcmp(&begin[1], needle + 1, + needle_len - 1)) { + return begin; + } + } + + return NULL; +#endif /* HAVE_MEMMEM */ +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_strutl.h b/wsutil/wmem/wmem_strutl.h new file mode 100644 index 00000000..ee3aed65 --- /dev/null +++ b/wsutil/wmem/wmem_strutl.h @@ -0,0 +1,95 @@ +/** @file + * Definitions for the Wireshark Memory Manager String Utilities + * Copyright 2012, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WMEM_STRUTL_H__ +#define __WMEM_STRUTL_H__ + +#include <stdarg.h> + +#include "wmem_core.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** @addtogroup wmem + * @{ + * @defgroup wmem-strutl String Utilities + * + * A collection of utility function for operating on C strings with wmem. + * + * @{ + */ + +WS_DLL_PUBLIC +char * +wmem_strdup(wmem_allocator_t *allocator, const char *src) +G_GNUC_MALLOC; + +#define ws_strdup(src) wmem_strdup(NULL, src) + +WS_DLL_PUBLIC +char * +wmem_strndup(wmem_allocator_t *allocator, const char *src, const size_t len) +G_GNUC_MALLOC; + +#define ws_strndup(src, len) wmem_strndup(NULL, src, len) + +WS_DLL_PUBLIC +char * +wmem_strdup_printf(wmem_allocator_t *allocator, const char *fmt, ...) +G_GNUC_MALLOC G_GNUC_PRINTF(2, 3); + +#define ws_strdup_printf(...) wmem_strdup_printf(NULL, __VA_ARGS__) + +WS_DLL_PUBLIC +char * +wmem_strdup_vprintf(wmem_allocator_t *allocator, const char *fmt, va_list ap) +G_GNUC_MALLOC; + +#define ws_strdup_vprintf(fmt, ap) wmem_strdup_vprintf(NULL, fmt, ap) + +/** + * Return the first occurrence of needle in haystack. + * + * @param haystack The data to search + * @param haystack_len The length of the search data + * @param needle The string to look for + * @param needle_len The length of the search string + * @return A pointer to the first occurrence of "needle" in + * "haystack". If "needle" isn't found or is NULL, or if + * "needle_len" is 0, NULL is returned. + */ +WS_DLL_PUBLIC +const uint8_t *ws_memmem(const void *haystack, size_t haystack_len, + const void *needle, size_t needle_len); + +/** @} + * @} */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __WMEM_STRUTL_H__ */ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_test.c b/wsutil/wmem/wmem_test.c new file mode 100644 index 00000000..0dc908b7 --- /dev/null +++ b/wsutil/wmem/wmem_test.c @@ -0,0 +1,1496 @@ +/* wmem_test.c + * Wireshark Memory Manager Tests + * Copyright 2012, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" + +#include <stdio.h> +#include <glib.h> + +#include "wmem.h" +#include "wmem_tree-int.h" +#include "wmem_allocator.h" +#include "wmem_allocator_block.h" +#include "wmem_allocator_block_fast.h" +#include "wmem_allocator_simple.h" +#include "wmem_allocator_strict.h" + +#include <wsutil/time_util.h> + +#define STRING_80 "12345678901234567890123456789012345678901234567890123456789012345678901234567890" +#define MAX_ALLOC_SIZE (1024*64) +#define MAX_SIMULTANEOUS_ALLOCS 1024 +#define CONTAINER_ITERS 10000 + +typedef void (*wmem_verify_func)(wmem_allocator_t *allocator); + +/* A local copy of wmem_allocator_new that ignores the + * WIRESHARK_DEBUG_WMEM_OVERRIDE variable so that test functions are + * guaranteed to actually get the allocator type they asked for */ +static wmem_allocator_t * +wmem_allocator_force_new(const wmem_allocator_type_t type) +{ + wmem_allocator_t *allocator; + + allocator = wmem_new(NULL, wmem_allocator_t); + allocator->type = type; + allocator->callbacks = NULL; + allocator->in_scope = true; + + switch (type) { + case WMEM_ALLOCATOR_SIMPLE: + wmem_simple_allocator_init(allocator); + break; + case WMEM_ALLOCATOR_BLOCK: + wmem_block_allocator_init(allocator); + break; + case WMEM_ALLOCATOR_BLOCK_FAST: + wmem_block_fast_allocator_init(allocator); + break; + case WMEM_ALLOCATOR_STRICT: + wmem_strict_allocator_init(allocator); + break; + default: + g_assert_not_reached(); + /* This is necessary to squelch MSVC errors; is there + any way to tell it that g_assert_not_reached() + never returns? */ + return NULL; + }; + + return allocator; +} + +/* A helper for generating pseudo-random strings. Just uses glib's random number + * functions to generate 'numbers' in the printable character range. */ +static char * +wmem_test_rand_string(wmem_allocator_t *allocator, int minlen, int maxlen) +{ + char *str; + int len, i; + + len = g_random_int_range(minlen, maxlen); + + /* +1 for null-terminator */ + str = (char*)wmem_alloc(allocator, len + 1); + str[len] = '\0'; + + for (i=0; i<len; i++) { + /* ASCII normal printable range is 32 (space) to 126 (tilde) */ + str[i] = (char) g_random_int_range(32, 126); + } + + return str; +} + +static int +wmem_test_compare_guint32(const void *a, const void *b) +{ + uint32_t l, r; + + l = *(const uint32_t*)a; + r = *(const uint32_t*)b; + + return l - r; +} + +/* Some helpers for properly testing callback functionality */ +wmem_allocator_t *expected_allocator; +void *expected_user_data; +wmem_cb_event_t expected_event; +int cb_called_count; +int cb_continue_count; +bool value_seen[CONTAINER_ITERS]; + +static bool +wmem_test_cb(wmem_allocator_t *allocator, wmem_cb_event_t event, + void *user_data) +{ + g_assert_true(allocator == expected_allocator); + g_assert_true(event == expected_event); + + cb_called_count++; + + return *(bool*)user_data; +} + +static bool +wmem_test_foreach_cb(const void *key _U_, void *value, void *user_data) +{ + g_assert_true(user_data == expected_user_data); + + g_assert_true(! value_seen[GPOINTER_TO_INT(value)]); + value_seen[GPOINTER_TO_INT(value)] = true; + + cb_called_count++; + cb_continue_count--; + + return (cb_continue_count == 0); +} + +/* ALLOCATOR TESTING FUNCTIONS (/wmem/allocator/) */ + +static void +wmem_test_allocator_callbacks(void) +{ + wmem_allocator_t *allocator; + bool t = true; + bool f = false; + unsigned cb_id; + + allocator = wmem_allocator_new(WMEM_ALLOCATOR_STRICT); + + expected_allocator = allocator; + + wmem_register_callback(expected_allocator, &wmem_test_cb, &f); + wmem_register_callback(expected_allocator, &wmem_test_cb, &f); + cb_id = wmem_register_callback(expected_allocator, &wmem_test_cb, &t); + wmem_register_callback(expected_allocator, &wmem_test_cb, &t); + wmem_register_callback(expected_allocator, &wmem_test_cb, &f); + + expected_event = WMEM_CB_FREE_EVENT; + + cb_called_count = 0; + wmem_free_all(allocator); + g_assert_true(cb_called_count == 5); + + cb_called_count = 0; + wmem_free_all(allocator); + g_assert_true(cb_called_count == 2); + + cb_called_count = 0; + wmem_free_all(allocator); + g_assert_true(cb_called_count == 2); + + wmem_unregister_callback(allocator, cb_id); + cb_called_count = 0; + wmem_free_all(allocator); + g_assert_true(cb_called_count == 1); + + cb_id = wmem_register_callback(expected_allocator, &wmem_test_cb, &f); + wmem_register_callback(expected_allocator, &wmem_test_cb, &t); + + cb_called_count = 0; + wmem_free_all(allocator); + g_assert_true(cb_called_count == 3); + + wmem_unregister_callback(allocator, cb_id); + cb_called_count = 0; + wmem_free_all(allocator); + g_assert_true(cb_called_count == 2); + + wmem_register_callback(expected_allocator, &wmem_test_cb, &t); + + expected_event = WMEM_CB_DESTROY_EVENT; + cb_called_count = 0; + wmem_destroy_allocator(allocator); + g_assert_true(cb_called_count == 3); +} + +static void +wmem_test_allocator_det(wmem_allocator_t *allocator, wmem_verify_func verify, + unsigned len) +{ + int i; + char *ptrs[MAX_SIMULTANEOUS_ALLOCS]; + + /* we use wmem_alloc0 in part because it tests slightly more code, but + * primarily so that if the allocator doesn't give us enough memory or + * gives us memory that includes its own metadata, we write to it and + * things go wrong, causing the tests to fail */ + for (i=0; i<MAX_SIMULTANEOUS_ALLOCS; i++) { + ptrs[i] = (char *)wmem_alloc0(allocator, len); + } + for (i=MAX_SIMULTANEOUS_ALLOCS-1; i>=0; i--) { + /* no wmem_realloc0 so just use memset manually */ + ptrs[i] = (char *)wmem_realloc(allocator, ptrs[i], 4*len); + memset(ptrs[i], 0, 4*len); + } + for (i=0; i<MAX_SIMULTANEOUS_ALLOCS; i++) { + wmem_free(allocator, ptrs[i]); + } + + if (verify) (*verify)(allocator); + wmem_free_all(allocator); + wmem_gc(allocator); + if (verify) (*verify)(allocator); +} + +static void +wmem_test_allocator_jumbo(wmem_allocator_type_t type, wmem_verify_func verify) +{ + wmem_allocator_t *allocator; + char *ptr, *ptr1; + + allocator = wmem_allocator_force_new(type); + + ptr = (char*)wmem_alloc0(allocator, 4*1024*1024); + wmem_free(allocator, ptr); + wmem_gc(allocator); + ptr = (char*)wmem_alloc0(allocator, 4*1024*1024); + + if (verify) (*verify)(allocator); + wmem_free(allocator, ptr); + wmem_gc(allocator); + if (verify) (*verify)(allocator); + + ptr = (char *)wmem_alloc0(allocator, 10*1024*1024); + ptr1 = (char *)wmem_alloc0(allocator, 13*1024*1024); + ptr1 = (char *)wmem_realloc(allocator, ptr1, 10*1024*1024); + memset(ptr1, 0, 10*1024*1024); + ptr = (char *)wmem_realloc(allocator, ptr, 13*1024*1024); + memset(ptr, 0, 13*1024*1024); + if (verify) (*verify)(allocator); + wmem_gc(allocator); + if (verify) (*verify)(allocator); + wmem_free(allocator, ptr1); + if (verify) (*verify)(allocator); + wmem_free_all(allocator); + wmem_gc(allocator); + if (verify) (*verify)(allocator); + + wmem_destroy_allocator(allocator); +} + +static void +wmem_test_allocator(wmem_allocator_type_t type, wmem_verify_func verify, + int iterations) +{ + int i; + char *ptrs[MAX_SIMULTANEOUS_ALLOCS]; + wmem_allocator_t *allocator; + + allocator = wmem_allocator_force_new(type); + + if (verify) (*verify)(allocator); + + /* start with some fairly simple deterministic tests */ + + wmem_test_allocator_det(allocator, verify, 8); + + wmem_test_allocator_det(allocator, verify, 64); + + wmem_test_allocator_det(allocator, verify, 512); + + for (i=0; i<MAX_SIMULTANEOUS_ALLOCS; i++) { + ptrs[i] = wmem_alloc0_array(allocator, char, 32); + } + + if (verify) (*verify)(allocator); + wmem_free_all(allocator); + wmem_gc(allocator); + if (verify) (*verify)(allocator); + + /* now do some random fuzz-like tests */ + + /* reset our ptr array */ + for (i=0; i<MAX_SIMULTANEOUS_ALLOCS; i++) { + ptrs[i] = NULL; + } + + /* Run enough iterations to fill the array 32 times */ + for (i=0; i<iterations; i++) { + int ptrs_index; + int new_size; + + /* returns value 0 <= x < MAX_SIMULTANEOUS_ALLOCS which is a valid + * index into ptrs */ + ptrs_index = g_test_rand_int_range(0, MAX_SIMULTANEOUS_ALLOCS); + + if (ptrs[ptrs_index] == NULL) { + /* if that index is unused, allocate some random amount of memory + * between 0 and MAX_ALLOC_SIZE */ + new_size = g_test_rand_int_range(0, MAX_ALLOC_SIZE); + + ptrs[ptrs_index] = (char *) wmem_alloc0(allocator, new_size); + } + else if (g_test_rand_bit()) { + /* the index is used, and our random bit has determined we will be + * reallocating instead of freeing. Do so to some random size + * between 0 and MAX_ALLOC_SIZE, then manually zero the + * new memory */ + new_size = g_test_rand_int_range(0, MAX_ALLOC_SIZE); + + ptrs[ptrs_index] = (char *) wmem_realloc(allocator, + ptrs[ptrs_index], new_size); + + if (new_size) + memset(ptrs[ptrs_index], 0, new_size); + } + else { + /* the index is used, and our random bit has determined we will be + * freeing instead of reallocating. Do so and NULL the pointer for + * the next iteration. */ + wmem_free(allocator, ptrs[ptrs_index]); + ptrs[ptrs_index] = NULL; + } + if (verify) (*verify)(allocator); + } + + wmem_destroy_allocator(allocator); +} + +static void +wmem_test_allocator_block(void) +{ + wmem_test_allocator(WMEM_ALLOCATOR_BLOCK, &wmem_block_verify, + MAX_SIMULTANEOUS_ALLOCS*64); + wmem_test_allocator_jumbo(WMEM_ALLOCATOR_BLOCK, &wmem_block_verify); +} + +static void +wmem_test_allocator_block_fast(void) +{ + wmem_test_allocator(WMEM_ALLOCATOR_BLOCK_FAST, NULL, + MAX_SIMULTANEOUS_ALLOCS*4); + wmem_test_allocator_jumbo(WMEM_ALLOCATOR_BLOCK, NULL); +} + +static void +wmem_test_allocator_simple(void) +{ + wmem_test_allocator(WMEM_ALLOCATOR_SIMPLE, NULL, + MAX_SIMULTANEOUS_ALLOCS*64); + wmem_test_allocator_jumbo(WMEM_ALLOCATOR_SIMPLE, NULL); +} + +static void +wmem_test_allocator_strict(void) +{ + wmem_test_allocator(WMEM_ALLOCATOR_STRICT, &wmem_strict_check_canaries, + MAX_SIMULTANEOUS_ALLOCS*64); + wmem_test_allocator_jumbo(WMEM_ALLOCATOR_STRICT, &wmem_strict_check_canaries); +} + +/* UTILITY TESTING FUNCTIONS (/wmem/utils/) */ + +static void +wmem_test_miscutls(void) +{ + wmem_allocator_t *allocator; + const char *source = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + char *ret; + + allocator = wmem_allocator_new(WMEM_ALLOCATOR_STRICT); + + ret = (char*) wmem_memdup(allocator, NULL, 0); + g_assert_true(ret == NULL); + + ret = (char*) wmem_memdup(allocator, source, 5); + ret[4] = '\0'; + g_assert_cmpstr(ret, ==, "ABCD"); + + ret = (char*) wmem_memdup(allocator, source, 1); + g_assert_true(ret[0] == 'A'); + wmem_strict_check_canaries(allocator); + + ret = (char*) wmem_memdup(allocator, source, 10); + ret[9] = '\0'; + g_assert_cmpstr(ret, ==, "ABCDEFGHI"); + + wmem_destroy_allocator(allocator); +} + +static void +wmem_test_strutls(void) +{ + wmem_allocator_t *allocator; + const char *orig_str; + char *new_str; + + allocator = wmem_allocator_new(WMEM_ALLOCATOR_STRICT); + + orig_str = "TEST1"; + new_str = wmem_strdup(allocator, orig_str); + g_assert_cmpstr(new_str, ==, orig_str); + new_str[0] = 'X'; + g_assert_cmpstr(new_str, >, orig_str); + wmem_strict_check_canaries(allocator); + + orig_str = "TEST123456789"; + new_str = wmem_strndup(allocator, orig_str, 6); + g_assert_cmpstr(new_str, ==, "TEST12"); + g_assert_cmpstr(new_str, <, orig_str); + new_str[0] = 'X'; + g_assert_cmpstr(new_str, >, orig_str); + wmem_strict_check_canaries(allocator); + + new_str = wmem_strdup_printf(allocator, "abc %s %% %d", "boo", 23); + g_assert_cmpstr(new_str, ==, "abc boo % 23"); + new_str = wmem_strdup_printf(allocator, "%s", STRING_80); + g_assert_cmpstr(new_str, ==, STRING_80); + wmem_strict_check_canaries(allocator); + + orig_str = "Short String"; + new_str = wmem_strdup_printf(allocator, "TEST %s", orig_str); + g_assert_cmpstr(new_str, ==, "TEST Short String"); + + orig_str = "Very Long..............................." + "........................................" + "........................................" + "........................................" + "........................................" + "........................................" + "..................................String"; + new_str = wmem_strdup_printf(allocator, "TEST %s", orig_str); + g_assert_cmpstr(new_str, ==, + "TEST Very Long..............................." + "........................................" + "........................................" + "........................................" + "........................................" + "........................................" + "..................................String"); + + wmem_destroy_allocator(allocator); +} + +#define RESOURCE_USAGE_START get_resource_usage(&start_utime, &start_stime) + +#define RESOURCE_USAGE_END \ + get_resource_usage(&end_utime, &end_stime); \ + utime_ms = (end_utime - start_utime) * 1000.0; \ + stime_ms = (end_stime - start_stime) * 1000.0 + +/* NOTE: You have to run "wmem_test -m perf" to run the performance tests. */ +static void +wmem_test_stringperf(void) +{ +#define LOOP_COUNT (1 * 1000 * 1000) + wmem_allocator_t *allocator; +#ifdef _WIN32 + char buffer[1]; +#endif + char **str_ptr = g_new(char *, LOOP_COUNT); + char *s_val = "test string"; + double d_val = 1000.2; + unsigned u_val = 54321; + int i_val = -12345; + int i; + double start_utime, start_stime, end_utime, end_stime, utime_ms, stime_ms; + + allocator = wmem_allocator_new(WMEM_ALLOCATOR_BLOCK); + +/* C99 snprintf */ + + RESOURCE_USAGE_START; + for (i = 0; i < LOOP_COUNT; i++) { + snprintf(NULL, 0, "%s", s_val); + } + RESOURCE_USAGE_END; + g_test_minimized_result(utime_ms + stime_ms, + "snprintf 1 string: u %.3f ms s %.3f ms", utime_ms, stime_ms); + + RESOURCE_USAGE_START; + for (i = 0; i < LOOP_COUNT; i++) { + snprintf(NULL, 0, "%s%s%s%s%s", s_val, s_val, s_val, s_val, s_val); + } + RESOURCE_USAGE_END; + g_test_minimized_result(utime_ms + stime_ms, + "snprintf 5 strings: u %.3f ms s %.3f ms", utime_ms, stime_ms); + + RESOURCE_USAGE_START; + for (i = 0; i < LOOP_COUNT; i++) { + snprintf(NULL, 0, "%s%u%3.5f%02d", s_val, u_val, d_val, i_val); + } + RESOURCE_USAGE_END; + g_test_minimized_result(utime_ms + stime_ms, + "snprintf mixed args: u %.3f ms s %.3f ms", utime_ms, stime_ms); + +/* GLib g_snprintf (can use C99 or Gnulib) */ + + RESOURCE_USAGE_START; + for (i = 0; i < LOOP_COUNT; i++) { + g_snprintf(NULL, 0, "%s", s_val); + } + RESOURCE_USAGE_END; + g_test_minimized_result(utime_ms + stime_ms, + "g_printf_string_upper_bound (via g_snprintf) 1 string: u %.3f ms s %.3f ms", utime_ms, stime_ms); + + RESOURCE_USAGE_START; + for (i = 0; i < LOOP_COUNT; i++) { + g_snprintf(NULL, 0, "%s%s%s%s%s", s_val, s_val, s_val, s_val, s_val); + } + RESOURCE_USAGE_END; + g_test_minimized_result(utime_ms + stime_ms, + "g_printf_string_upper_bound (via g_snprintf) 5 strings: u %.3f ms s %.3f ms", utime_ms, stime_ms); + + RESOURCE_USAGE_START; + for (i = 0; i < LOOP_COUNT; i++) { + g_snprintf(NULL, 0, "%s%u%3.5f%02d", s_val, u_val, d_val, i_val); + } + RESOURCE_USAGE_END; + g_test_minimized_result(utime_ms + stime_ms, + "g_printf_string_upper_bound (via g_snprintf) mixed args: u %.3f ms s %.3f ms", utime_ms, stime_ms); + +/* Windows _snprintf_s */ + +#ifdef _WIN32 + RESOURCE_USAGE_START; + for (i = 0; i < LOOP_COUNT; i++) { + _snprintf_s(buffer, 1, _TRUNCATE, "%s", s_val); + } + RESOURCE_USAGE_END; + g_test_minimized_result(utime_ms + stime_ms, + "_snprintf_s upper bound 1 string: u %.3f ms s %.3f ms", utime_ms, stime_ms); + + RESOURCE_USAGE_START; + for (i = 0; i < LOOP_COUNT; i++) { + _snprintf_s(buffer, 1, _TRUNCATE, "%s%s%s%s%s", s_val, s_val, s_val, s_val, s_val); + } + RESOURCE_USAGE_END; + g_test_minimized_result(utime_ms + stime_ms, + "_snprintf_s upper bound 5 strings: u %.3f ms s %.3f ms", utime_ms, stime_ms); + + RESOURCE_USAGE_START; + for (i = 0; i < LOOP_COUNT; i++) { + _snprintf_s(buffer, 1, _TRUNCATE, "%s%u%3.5f%02d", s_val, u_val, d_val, i_val); + } + RESOURCE_USAGE_END; + g_test_minimized_result(utime_ms + stime_ms, + "_snprintf_s upper bound mixed args: u %.3f ms s %.3f ms", utime_ms, stime_ms); +#endif + +/* GLib strdup */ + + RESOURCE_USAGE_START; + for (i = 0; i < LOOP_COUNT; i++) { + str_ptr[i] = g_strdup_printf("%s%s", s_val, s_val); + } + RESOURCE_USAGE_END; + g_test_minimized_result(utime_ms + stime_ms, + "g_strdup_printf 2 strings: u %.3f ms s %.3f ms", utime_ms, stime_ms); + for (i = 0; i < LOOP_COUNT; i++) { + g_free(str_ptr[i]); + } + + RESOURCE_USAGE_START; + for (i = 0; i < LOOP_COUNT; i++) { + str_ptr[i] = g_strdup_printf("%s%s%s%s%s", s_val, s_val, s_val, s_val, s_val); + } + RESOURCE_USAGE_END; + g_test_minimized_result(utime_ms + stime_ms, + "g_strdup_printf 5 strings: u %.3f ms s %.3f ms", utime_ms, stime_ms); + for (i = 0; i < LOOP_COUNT; i++) { + g_free(str_ptr[i]); + } + +/* wmem strdup null allocator */ + + RESOURCE_USAGE_START; + for (i = 0; i < LOOP_COUNT; i++) { + str_ptr[i] = wmem_strdup_printf(NULL, "%s%s", s_val, s_val); + } + RESOURCE_USAGE_END; + g_test_minimized_result(utime_ms + stime_ms, + "wmem_strdup_printf() 2 strings: u %.3f ms s %.3f ms", utime_ms, stime_ms); + for (i = 0; i < LOOP_COUNT; i++) { + g_free(str_ptr[i]); + } + + RESOURCE_USAGE_START; + for (i = 0; i < LOOP_COUNT; i++) { + str_ptr[i] = wmem_strdup_printf(NULL, "%s%s%s%s%s", s_val, s_val, s_val, s_val, s_val); + } + RESOURCE_USAGE_END; + g_test_minimized_result(utime_ms + stime_ms, + "wmem_strdup_printf(NULL) 5 strings: u %.3f ms s %.3f ms", utime_ms, stime_ms); + for (i = 0; i < LOOP_COUNT; i++) { + g_free(str_ptr[i]); + } + +/* wmem strdup strict allocator */ + + RESOURCE_USAGE_START; + for (i = 0; i < LOOP_COUNT; i++) { + wmem_strdup_printf(allocator, "%s%s", s_val, s_val); + } + RESOURCE_USAGE_END; + g_test_minimized_result(utime_ms + stime_ms, + "wmem_strdup_printf(allocator) 2 strings: u %.3f ms s %.3f ms", utime_ms, stime_ms); + + RESOURCE_USAGE_START; + for (i = 0; i < LOOP_COUNT; i++) { + wmem_strdup_printf(allocator, "%s%s%s%s%s", s_val, s_val, s_val, s_val, s_val); + } + RESOURCE_USAGE_END; + g_test_minimized_result(utime_ms + stime_ms, + "wmem_strdup_printf(allocator) 5 strings: u %.3f ms s %.3f ms", utime_ms, stime_ms); + + wmem_destroy_allocator(allocator); + g_free(str_ptr); +} + +/* DATA STRUCTURE TESTING FUNCTIONS (/wmem/datastruct/) */ + +static void +wmem_test_array(void) +{ + wmem_allocator_t *allocator; + wmem_array_t *array; + unsigned int i, j, k; + uint32_t val, *buf; + uint32_t vals[8]; + uint32_t *raw; + uint32_t lastint; + + allocator = wmem_allocator_new(WMEM_ALLOCATOR_STRICT); + + array = wmem_array_new(allocator, sizeof(uint32_t)); + g_assert_true(array); + g_assert_true(wmem_array_get_count(array) == 0); + + for (i=0; i<CONTAINER_ITERS; i++) { + val = i; + wmem_array_append_one(array, val); + g_assert_true(wmem_array_get_count(array) == i+1); + + val = *(uint32_t*)wmem_array_index(array, i); + g_assert_true(val == i); + g_assert_true(wmem_array_try_index(array, i, &val) == 0); + g_assert_true(val == i); + g_assert_true(wmem_array_try_index(array, i+1, &val) < 0); + + } + wmem_strict_check_canaries(allocator); + + for (i=0; i<CONTAINER_ITERS; i++) { + val = *(uint32_t*)wmem_array_index(array, i); + g_assert_true(val == i); + g_assert_true(wmem_array_try_index(array, i, &val) == 0); + g_assert_true(val == i); + } + + wmem_destroy_array(array); + + array = wmem_array_sized_new(allocator, sizeof(uint32_t), 73); + wmem_array_set_null_terminator(array); + for (i=0; i<75; i++) + g_assert_true(wmem_array_try_index(array, i, &val) < 0); + + for (i=0; i<CONTAINER_ITERS; i++) { + for (j=0; j<8; j++) { + vals[j] = i+j; + } + + wmem_array_append(array, vals, 8); + g_assert_true(wmem_array_get_count(array) == 8*(i+1)); + } + wmem_strict_check_canaries(allocator); + + buf = (uint32_t*)wmem_array_get_raw(array); + for (i=0; i<CONTAINER_ITERS; i++) { + for (j=0; j<8; j++) { + g_assert_true(buf[i*8 + j] == i+j); + } + } + + wmem_array_sort(array, wmem_test_compare_guint32); + for (i=0, k=0; i<8; i++) { + for (j=0; j<=i; j++, k++) { + val = *(uint32_t*)wmem_array_index(array, k); + g_assert_true(val == i); + g_assert_true(wmem_array_try_index(array, k, &val) == 0); + g_assert_true(val == i); + } + } + for (j=k; k<8*(CONTAINER_ITERS+1)-j; k++) { + val = *(uint32_t*)wmem_array_index(array, k); + g_assert_true(val == ((k-j)/8)+8); + g_assert_true(wmem_array_try_index(array, k, &val) == 0); + g_assert_true(val == ((k-j)/8)+8); + } + for (i=0; i<7; i++) { + for (j=0; j<7-i; j++, k++) { + val = *(uint32_t*)wmem_array_index(array, k); + g_assert_true(val == CONTAINER_ITERS+i); + g_assert_true(wmem_array_try_index(array, k, &val) == 0); + g_assert_true(val == CONTAINER_ITERS+i); + } + } + g_assert_true(k == wmem_array_get_count(array)); + + lastint = 77; + wmem_array_append_one(array, lastint); + + raw = (uint32_t*)wmem_array_get_raw(array); + g_assert_true(raw[wmem_array_get_count(array)] == 0); + g_assert_true(raw[wmem_array_get_count(array) - 1] == lastint); + + wmem_destroy_array(array); + + wmem_destroy_allocator(allocator); +} + +static void +check_val_list(void * val, void * val_to_check) +{ + g_assert_true(val == val_to_check); +} + +static int +str_compare(gconstpointer a, gconstpointer b) +{ + return strcmp((const char*)a, (const char*)b); +} + +static void +wmem_test_list(void) +{ + wmem_allocator_t *allocator; + wmem_list_t *list; + wmem_list_frame_t *frame; + unsigned int i; + int int1; + int int2; + char* str1; + char* str2; + + allocator = wmem_allocator_new(WMEM_ALLOCATOR_STRICT); + + list = wmem_list_new(allocator); + g_assert_true(list); + g_assert_true(wmem_list_count(list) == 0); + + frame = wmem_list_head(list); + g_assert_true(frame == NULL); + + for (i=0; i<CONTAINER_ITERS; i++) { + wmem_list_prepend(list, GINT_TO_POINTER(i)); + g_assert_true(wmem_list_count(list) == i+1); + g_assert_true(wmem_list_find(list, GINT_TO_POINTER(i))); + + frame = wmem_list_head(list); + g_assert_true(frame); + g_assert_true(wmem_list_frame_data(frame) == GINT_TO_POINTER(i)); + } + wmem_strict_check_canaries(allocator); + + i = CONTAINER_ITERS - 1; + frame = wmem_list_head(list); + while (frame) { + g_assert_true(wmem_list_frame_data(frame) == GINT_TO_POINTER(i)); + i--; + frame = wmem_list_frame_next(frame); + } + + i = 0; + frame = wmem_list_tail(list); + while (frame) { + g_assert_true(wmem_list_frame_data(frame) == GINT_TO_POINTER(i)); + i++; + frame = wmem_list_frame_prev(frame); + } + + i = CONTAINER_ITERS - 2; + while (wmem_list_count(list) > 1) { + wmem_list_remove(list, GINT_TO_POINTER(i)); + i--; + } + wmem_list_remove(list, GINT_TO_POINTER(CONTAINER_ITERS - 1)); + g_assert_true(wmem_list_count(list) == 0); + g_assert_true(wmem_list_head(list) == NULL); + g_assert_true(wmem_list_tail(list) == NULL); + + for (i=0; i<CONTAINER_ITERS; i++) { + wmem_list_append(list, GINT_TO_POINTER(i)); + g_assert_true(wmem_list_count(list) == i+1); + + frame = wmem_list_head(list); + g_assert_true(frame); + } + wmem_strict_check_canaries(allocator); + + i = 0; + frame = wmem_list_head(list); + while (frame) { + g_assert_true(wmem_list_frame_data(frame) == GINT_TO_POINTER(i)); + i++; + frame = wmem_list_frame_next(frame); + } + + i = CONTAINER_ITERS - 1; + frame = wmem_list_tail(list); + while (frame) { + g_assert_true(wmem_list_frame_data(frame) == GINT_TO_POINTER(i)); + i--; + frame = wmem_list_frame_prev(frame); + } + + wmem_destroy_allocator(allocator); + + list = wmem_list_new(NULL); + for (i=0; i<CONTAINER_ITERS; i++) { + wmem_list_prepend(list, GINT_TO_POINTER(i)); + } + g_assert_true(wmem_list_count(list) == CONTAINER_ITERS); + wmem_destroy_list(list); + + list = wmem_list_new(NULL); + for (i=0; i<CONTAINER_ITERS; i++) { + wmem_list_append(list, GINT_TO_POINTER(1)); + } + wmem_list_foreach(list, check_val_list, GINT_TO_POINTER(1)); + wmem_destroy_list(list); + + list = wmem_list_new(NULL); + wmem_list_insert_sorted(list, GINT_TO_POINTER(5), wmem_compare_int); + wmem_list_insert_sorted(list, GINT_TO_POINTER(8), wmem_compare_int); + wmem_list_insert_sorted(list, GINT_TO_POINTER(1), wmem_compare_int); + wmem_list_insert_sorted(list, GINT_TO_POINTER(2), wmem_compare_int); + wmem_list_insert_sorted(list, GINT_TO_POINTER(9), wmem_compare_int); + g_assert_true(wmem_list_count(list) == 5); + frame = wmem_list_head(list); + int1 = GPOINTER_TO_INT(wmem_list_frame_data(frame)); + while ((frame = wmem_list_frame_next(frame))) { + int2 = GPOINTER_TO_INT(wmem_list_frame_data(frame)); + g_assert_true(int1 <= int2); + int1 = int2; + } + wmem_destroy_list(list); + + list = wmem_list_new(NULL); + wmem_list_insert_sorted(list, GINT_TO_POINTER(5), wmem_compare_int); + wmem_list_insert_sorted(list, GINT_TO_POINTER(1), wmem_compare_int); + wmem_list_insert_sorted(list, GINT_TO_POINTER(7), wmem_compare_int); + wmem_list_insert_sorted(list, GINT_TO_POINTER(3), wmem_compare_int); + wmem_list_insert_sorted(list, GINT_TO_POINTER(2), wmem_compare_int); + wmem_list_insert_sorted(list, GINT_TO_POINTER(2), wmem_compare_int); + g_assert_true(wmem_list_count(list) == 6); + frame = wmem_list_head(list); + int1 = GPOINTER_TO_INT(wmem_list_frame_data(frame)); + while ((frame = wmem_list_frame_next(frame))) { + int2 = GPOINTER_TO_INT(wmem_list_frame_data(frame)); + g_assert_true(int1 <= int2); + int1 = int2; + } + wmem_destroy_list(list); + + list = wmem_list_new(NULL); + wmem_list_insert_sorted(list, "abc", str_compare); + wmem_list_insert_sorted(list, "bcd", str_compare); + wmem_list_insert_sorted(list, "aaa", str_compare); + wmem_list_insert_sorted(list, "bbb", str_compare); + wmem_list_insert_sorted(list, "zzz", str_compare); + wmem_list_insert_sorted(list, "ggg", str_compare); + g_assert_true(wmem_list_count(list) == 6); + frame = wmem_list_head(list); + str1 = (char*)wmem_list_frame_data(frame); + while ((frame = wmem_list_frame_next(frame))) { + str2 = (char*)wmem_list_frame_data(frame); + g_assert_true(strcmp(str1, str2) <= 0); + str1 = str2; + } + wmem_destroy_list(list); +} + +static void +check_val_map(void * key _U_, void * val, void * user_data) +{ + g_assert_true(val == user_data); +} + +static gboolean +equal_val_map(void * key _U_, void * val, void * user_data) +{ + return val == user_data; +} + +static void +wmem_test_map(void) +{ + wmem_allocator_t *allocator, *extra_allocator; + wmem_map_t *map; + char *str_key; + const void *str_key_ret; + unsigned int i; + unsigned int *key_ret; + unsigned int *value_ret; + void *ret; + + allocator = wmem_allocator_new(WMEM_ALLOCATOR_STRICT); + extra_allocator = wmem_allocator_new(WMEM_ALLOCATOR_STRICT); + + /* insertion, lookup and removal of simple integer keys */ + map = wmem_map_new(allocator, g_direct_hash, g_direct_equal); + g_assert_true(map); + + for (i=0; i<CONTAINER_ITERS; i++) { + ret = wmem_map_insert(map, GINT_TO_POINTER(i), GINT_TO_POINTER(777777)); + g_assert_true(ret == NULL); + ret = wmem_map_insert(map, GINT_TO_POINTER(i), GINT_TO_POINTER(i)); + g_assert_true(ret == GINT_TO_POINTER(777777)); + ret = wmem_map_insert(map, GINT_TO_POINTER(i), GINT_TO_POINTER(i)); + g_assert_true(ret == GINT_TO_POINTER(i)); + } + for (i=0; i<CONTAINER_ITERS; i++) { + ret = wmem_map_lookup(map, GINT_TO_POINTER(i)); + g_assert_true(ret == GINT_TO_POINTER(i)); + g_assert_true(wmem_map_contains(map, GINT_TO_POINTER(i)) == true); + g_assert_true(wmem_map_lookup_extended(map, GINT_TO_POINTER(i), NULL, NULL)); + key_ret = NULL; + g_assert_true(wmem_map_lookup_extended(map, GINT_TO_POINTER(i), GINT_TO_POINTER(&key_ret), NULL)); + g_assert_true(key_ret == GINT_TO_POINTER(i)); + value_ret = NULL; + g_assert_true(wmem_map_lookup_extended(map, GINT_TO_POINTER(i), NULL, GINT_TO_POINTER(&value_ret))); + g_assert_true(value_ret == GINT_TO_POINTER(i)); + key_ret = NULL; + value_ret = NULL; + g_assert_true(wmem_map_lookup_extended(map, GINT_TO_POINTER(i), GINT_TO_POINTER(&key_ret), GINT_TO_POINTER(&value_ret))); + g_assert_true(key_ret == GINT_TO_POINTER(i)); + g_assert_true(value_ret == GINT_TO_POINTER(i)); + ret = wmem_map_remove(map, GINT_TO_POINTER(i)); + g_assert_true(ret == GINT_TO_POINTER(i)); + g_assert_true(wmem_map_contains(map, GINT_TO_POINTER(i)) == false); + ret = wmem_map_lookup(map, GINT_TO_POINTER(i)); + g_assert_true(ret == NULL); + ret = wmem_map_remove(map, GINT_TO_POINTER(i)); + g_assert_true(ret == NULL); + } + wmem_free_all(allocator); + + /* test auto-reset functionality */ + map = wmem_map_new_autoreset(allocator, extra_allocator, g_direct_hash, g_direct_equal); + g_assert_true(map); + for (i=0; i<CONTAINER_ITERS; i++) { + ret = wmem_map_insert(map, GINT_TO_POINTER(i), GINT_TO_POINTER(777777)); + g_assert_true(ret == NULL); + ret = wmem_map_insert(map, GINT_TO_POINTER(i), GINT_TO_POINTER(i)); + g_assert_true(ret == GINT_TO_POINTER(777777)); + ret = wmem_map_insert(map, GINT_TO_POINTER(i), GINT_TO_POINTER(i)); + g_assert_true(ret == GINT_TO_POINTER(i)); + } + wmem_free_all(extra_allocator); + for (i=0; i<CONTAINER_ITERS; i++) { + g_assert_true(wmem_map_lookup(map, GINT_TO_POINTER(i)) == NULL); + } + wmem_free_all(allocator); + + map = wmem_map_new(allocator, wmem_str_hash, g_str_equal); + g_assert_true(map); + + /* string keys and for-each */ + for (i=0; i<CONTAINER_ITERS; i++) { + str_key = wmem_test_rand_string(allocator, 1, 64); + wmem_map_insert(map, str_key, GINT_TO_POINTER(i)); + ret = wmem_map_lookup(map, str_key); + g_assert_true(ret == GINT_TO_POINTER(i)); + g_assert_true(wmem_map_contains(map, str_key) == true); + str_key_ret = NULL; + value_ret = NULL; + g_assert_true(wmem_map_lookup_extended(map, str_key, &str_key_ret, GINT_TO_POINTER(&value_ret)) == true); + g_assert_true(g_str_equal(str_key_ret, str_key)); + g_assert_true(value_ret == GINT_TO_POINTER(i)); + } + + /* test foreach */ + map = wmem_map_new(allocator, wmem_str_hash, g_str_equal); + g_assert_true(map); + for (i=0; i<CONTAINER_ITERS; i++) { + str_key = wmem_test_rand_string(allocator, 1, 64); + wmem_map_insert(map, str_key, GINT_TO_POINTER(2)); + } + wmem_map_foreach(map, check_val_map, GINT_TO_POINTER(2)); + + wmem_map_foreach_remove(map, equal_val_map, GINT_TO_POINTER(2)); + g_assert_true(wmem_map_size(map) == 0); + + /* test size */ + map = wmem_map_new(allocator, g_direct_hash, g_direct_equal); + g_assert_true(map); + for (i=0; i<CONTAINER_ITERS; i++) { + wmem_map_insert(map, GINT_TO_POINTER(i), GINT_TO_POINTER(i)); + } + g_assert_true(wmem_map_size(map) == CONTAINER_ITERS); + + for (i=0; i<CONTAINER_ITERS; i+=2) { + wmem_map_foreach_remove(map, equal_val_map, GINT_TO_POINTER(i)); + } + g_assert_true(wmem_map_size(map) == CONTAINER_ITERS/2); + + wmem_destroy_allocator(extra_allocator); + wmem_destroy_allocator(allocator); +} + +static void +wmem_test_queue(void) +{ + wmem_allocator_t *allocator; + wmem_queue_t *queue; + unsigned int i; + + allocator = wmem_allocator_new(WMEM_ALLOCATOR_STRICT); + + queue = wmem_queue_new(allocator); + g_assert_true(queue); + g_assert_true(wmem_queue_count(queue) == 0); + + for (i=0; i<CONTAINER_ITERS; i++) { + wmem_queue_push(queue, GINT_TO_POINTER(i)); + + g_assert_true(wmem_queue_count(queue) == i+1); + g_assert_true(wmem_queue_peek(queue) == GINT_TO_POINTER(0)); + } + wmem_strict_check_canaries(allocator); + + for (i=0; i<CONTAINER_ITERS; i++) { + g_assert_true(wmem_queue_peek(queue) == GINT_TO_POINTER(i)); + g_assert_true(wmem_queue_pop(queue) == GINT_TO_POINTER(i)); + g_assert_true(wmem_queue_count(queue) == CONTAINER_ITERS-i-1); + } + g_assert_true(wmem_queue_count(queue) == 0); + + wmem_destroy_queue(queue); + + wmem_destroy_allocator(allocator); +} + +static void +wmem_test_stack(void) +{ + wmem_allocator_t *allocator; + wmem_stack_t *stack; + unsigned int i; + + allocator = wmem_allocator_new(WMEM_ALLOCATOR_STRICT); + + stack = wmem_stack_new(allocator); + g_assert_true(stack); + g_assert_true(wmem_stack_count(stack) == 0); + + for (i=0; i<CONTAINER_ITERS; i++) { + wmem_stack_push(stack, GINT_TO_POINTER(i)); + + g_assert_true(wmem_stack_count(stack) == i+1); + g_assert_true(wmem_stack_peek(stack) == GINT_TO_POINTER(i)); + } + wmem_strict_check_canaries(allocator); + + for (i=CONTAINER_ITERS; i>0; i--) { + g_assert_true(wmem_stack_peek(stack) == GINT_TO_POINTER(i-1)); + g_assert_true(wmem_stack_pop(stack) == GINT_TO_POINTER(i-1)); + g_assert_true(wmem_stack_count(stack) == i-1); + } + g_assert_true(wmem_stack_count(stack) == 0); + + wmem_destroy_stack(stack); + + wmem_destroy_allocator(allocator); +} + +static void +wmem_test_strbuf(void) +{ + wmem_allocator_t *allocator; + wmem_strbuf_t *strbuf; + int i; + + allocator = wmem_allocator_new(WMEM_ALLOCATOR_STRICT); + + strbuf = wmem_strbuf_new(allocator, "TEST"); + g_assert_true(strbuf); + g_assert_cmpstr(wmem_strbuf_get_str(strbuf), ==, "TEST"); + g_assert_cmpuint(wmem_strbuf_get_len(strbuf), ==, 4); + + wmem_strbuf_append(strbuf, "FUZZ"); + g_assert_cmpstr(wmem_strbuf_get_str(strbuf), ==, "TESTFUZZ"); + g_assert_cmpuint(wmem_strbuf_get_len(strbuf), ==, 8); + + wmem_strbuf_append_printf(strbuf, "%d%s", 3, "a"); + g_assert_cmpstr(wmem_strbuf_get_str(strbuf), ==, "TESTFUZZ3a"); + g_assert_cmpuint(wmem_strbuf_get_len(strbuf), ==, 10); + + wmem_strbuf_append_c(strbuf, 'q'); + g_assert_cmpstr(wmem_strbuf_get_str(strbuf), ==, "TESTFUZZ3aq"); + g_assert_cmpuint(wmem_strbuf_get_len(strbuf), ==, 11); + + wmem_strbuf_append_unichar(strbuf, g_utf8_get_char("\xC2\xA9")); + g_assert_cmpstr(wmem_strbuf_get_str(strbuf), ==, "TESTFUZZ3aq\xC2\xA9"); + g_assert_cmpuint(wmem_strbuf_get_len(strbuf), ==, 13); + + wmem_strbuf_append_c_count(strbuf, '+', 8); + g_assert_cmpstr(wmem_strbuf_get_str(strbuf), ==, "TESTFUZZ3aq\xC2\xA9++++++++"); + g_assert_cmpuint(wmem_strbuf_get_len(strbuf), ==, 21); + + wmem_strbuf_truncate(strbuf, 32); + wmem_strbuf_truncate(strbuf, 24); + wmem_strbuf_truncate(strbuf, 16); + wmem_strbuf_truncate(strbuf, 13); + g_assert_cmpstr(wmem_strbuf_get_str(strbuf), ==, "TESTFUZZ3aq\xC2\xA9"); + g_assert_cmpuint(wmem_strbuf_get_len(strbuf), ==, 13); + + wmem_strbuf_truncate(strbuf, 3); + g_assert_cmpstr(wmem_strbuf_get_str(strbuf), ==, "TES"); + g_assert_cmpuint(wmem_strbuf_get_len(strbuf), ==, 3); + + wmem_strbuf_append_len(strbuf, "TFUZZ1234", 5); + g_assert_cmpstr(wmem_strbuf_get_str(strbuf), ==, "TESTFUZZ"); + g_assert_cmpuint(wmem_strbuf_get_len(strbuf), ==, 8); + + wmem_free_all(allocator); + + strbuf = wmem_strbuf_new(allocator, "TEST"); + for (i=0; i<1024; i++) { + if (g_test_rand_bit()) { + wmem_strbuf_append(strbuf, "ABC"); + } + else { + wmem_strbuf_append_printf(strbuf, "%d%d", 3, 777); + } + wmem_strict_check_canaries(allocator); + } + g_assert_true(strlen(wmem_strbuf_get_str(strbuf)) == + wmem_strbuf_get_len(strbuf)); + + wmem_destroy_allocator(allocator); +} + +static void +wmem_test_strbuf_validate(void) +{ + wmem_strbuf_t *strbuf; + const char *endptr; + + strbuf = wmem_strbuf_new(NULL, "TEST\xEF ABC"); + g_assert_false(wmem_strbuf_utf8_validate(strbuf, &endptr)); + g_assert_true(endptr == &strbuf->str[4]); + wmem_strbuf_destroy(strbuf); + + strbuf = wmem_strbuf_new(NULL, NULL); + wmem_strbuf_append_len(strbuf, "TEST\x00\x00 ABC", 10); + g_assert_true(wmem_strbuf_utf8_validate(strbuf, &endptr)); + wmem_strbuf_destroy(strbuf); + + strbuf = wmem_strbuf_new(NULL, NULL); + wmem_strbuf_append_len(strbuf, "TEST\x00\xEF ABC", 10); + g_assert_false(wmem_strbuf_utf8_validate(strbuf, &endptr)); + g_assert_true(endptr == &strbuf->str[5]); + wmem_strbuf_destroy(strbuf); + + strbuf = wmem_strbuf_new(NULL, NULL); + wmem_strbuf_append_len(strbuf, "TEST\x00 ABC \x00 DEF \x00", 17); + g_assert_true(wmem_strbuf_utf8_validate(strbuf, &endptr)); + wmem_strbuf_destroy(strbuf); +} + +static void +wmem_test_tree(void) +{ + wmem_allocator_t *allocator, *extra_allocator; + wmem_tree_t *tree; + uint32_t i; + int seen_values = 0; + int j; + char *str_key; +#define WMEM_TREE_MAX_KEY_COUNT 8 +#define WMEM_TREE_MAX_KEY_LEN 4 + int key_count; + wmem_tree_key_t keys[WMEM_TREE_MAX_KEY_COUNT]; + + allocator = wmem_allocator_new(WMEM_ALLOCATOR_STRICT); + extra_allocator = wmem_allocator_new(WMEM_ALLOCATOR_STRICT); + + tree = wmem_tree_new(allocator); + g_assert_true(tree); + g_assert_true(wmem_tree_is_empty(tree)); + + /* test basic 32-bit key operations */ + for (i=0; i<CONTAINER_ITERS; i++) { + g_assert_true(wmem_tree_lookup32(tree, i) == NULL); + if (i > 0) { + g_assert_true(wmem_tree_lookup32_le(tree, i) == GINT_TO_POINTER(i-1)); + } + wmem_tree_insert32(tree, i, GINT_TO_POINTER(i)); + g_assert_true(wmem_tree_lookup32(tree, i) == GINT_TO_POINTER(i)); + g_assert_true(!wmem_tree_is_empty(tree)); + } + g_assert_true(wmem_tree_count(tree) == CONTAINER_ITERS); + wmem_free_all(allocator); + + tree = wmem_tree_new(allocator); + for (i=0; i<CONTAINER_ITERS; i++) { + uint32_t rand_int; + do { + rand_int = g_test_rand_int(); + } while (wmem_tree_lookup32(tree, rand_int)); + wmem_tree_insert32(tree, rand_int, GINT_TO_POINTER(i)); + g_assert_true(wmem_tree_lookup32(tree, rand_int) == GINT_TO_POINTER(i)); + } + g_assert_true(wmem_tree_count(tree) == CONTAINER_ITERS); + wmem_free_all(allocator); + + /* test auto-reset functionality */ + tree = wmem_tree_new_autoreset(allocator, extra_allocator); + for (i=0; i<CONTAINER_ITERS; i++) { + g_assert_true(wmem_tree_lookup32(tree, i) == NULL); + wmem_tree_insert32(tree, i, GINT_TO_POINTER(i)); + g_assert_true(wmem_tree_lookup32(tree, i) == GINT_TO_POINTER(i)); + } + g_assert_true(wmem_tree_count(tree) == CONTAINER_ITERS); + wmem_free_all(extra_allocator); + g_assert_true(wmem_tree_count(tree) == 0); + for (i=0; i<CONTAINER_ITERS; i++) { + g_assert_true(wmem_tree_lookup32(tree, i) == NULL); + g_assert_true(wmem_tree_lookup32_le(tree, i) == NULL); + } + wmem_free_all(allocator); + + /* test array key functionality */ + tree = wmem_tree_new(allocator); + key_count = g_random_int_range(1, WMEM_TREE_MAX_KEY_COUNT); + for (j=0; j<key_count; j++) { + keys[j].length = g_random_int_range(1, WMEM_TREE_MAX_KEY_LEN); + } + keys[key_count].length = 0; + for (i=0; i<CONTAINER_ITERS; i++) { + for (j=0; j<key_count; j++) { + keys[j].key = (uint32_t*)wmem_test_rand_string(allocator, + (keys[j].length*4), (keys[j].length*4)+1); + } + wmem_tree_insert32_array(tree, keys, GINT_TO_POINTER(i)); + g_assert_true(wmem_tree_lookup32_array(tree, keys) == GINT_TO_POINTER(i)); + } + wmem_free_all(allocator); + + tree = wmem_tree_new(allocator); + keys[0].length = 1; + keys[0].key = wmem_new(allocator, uint32_t); + *(keys[0].key) = 0; + keys[1].length = 0; + for (i=0; i<CONTAINER_ITERS; i++) { + wmem_tree_insert32_array(tree, keys, GINT_TO_POINTER(i)); + *(keys[0].key) += 4; + } + *(keys[0].key) = 0; + for (i=0; i<CONTAINER_ITERS; i++) { + g_assert_true(wmem_tree_lookup32_array(tree, keys) == GINT_TO_POINTER(i)); + for (j=0; j<3; j++) { + (*(keys[0].key)) += 1; + g_assert_true(wmem_tree_lookup32_array_le(tree, keys) == + GINT_TO_POINTER(i)); + } + *(keys[0].key) += 1; + } + wmem_free_all(allocator); + + /* test string key functionality */ + tree = wmem_tree_new(allocator); + for (i=0; i<CONTAINER_ITERS; i++) { + str_key = wmem_test_rand_string(allocator, 1, 64); + wmem_tree_insert_string(tree, str_key, GINT_TO_POINTER(i), 0); + g_assert_true(wmem_tree_lookup_string(tree, str_key, 0) == + GINT_TO_POINTER(i)); + } + wmem_free_all(allocator); + + tree = wmem_tree_new(allocator); + for (i=0; i<CONTAINER_ITERS; i++) { + str_key = wmem_test_rand_string(allocator, 1, 64); + wmem_tree_insert_string(tree, str_key, GINT_TO_POINTER(i), + WMEM_TREE_STRING_NOCASE); + g_assert_true(wmem_tree_lookup_string(tree, str_key, + WMEM_TREE_STRING_NOCASE) == GINT_TO_POINTER(i)); + } + wmem_free_all(allocator); + + /* test for-each functionality */ + tree = wmem_tree_new(allocator); + expected_user_data = GINT_TO_POINTER(g_test_rand_int()); + for (i=0; i<CONTAINER_ITERS; i++) { + int tmp; + do { + tmp = g_test_rand_int(); + } while (wmem_tree_lookup32(tree, tmp)); + value_seen[i] = false; + wmem_tree_insert32(tree, tmp, GINT_TO_POINTER(i)); + } + + cb_called_count = 0; + cb_continue_count = CONTAINER_ITERS; + wmem_tree_foreach(tree, wmem_test_foreach_cb, expected_user_data); + g_assert_true(cb_called_count == CONTAINER_ITERS); + g_assert_true(cb_continue_count == 0); + + for (i=0; i<CONTAINER_ITERS; i++) { + g_assert_true(value_seen[i]); + value_seen[i] = false; + } + + cb_called_count = 0; + cb_continue_count = 10; + wmem_tree_foreach(tree, wmem_test_foreach_cb, expected_user_data); + g_assert_true(cb_called_count == 10); + g_assert_true(cb_continue_count == 0); + + for (i=0; i<CONTAINER_ITERS; i++) { + if (value_seen[i]) { + seen_values++; + } + } + g_assert_true(seen_values == 10); + + wmem_destroy_allocator(extra_allocator); + wmem_destroy_allocator(allocator); +} + + +/* to be used as userdata in the callback wmem_test_itree_check_overlap_cb*/ +typedef struct wmem_test_itree_user_data { + wmem_range_t range; + unsigned counter; +} wmem_test_itree_user_data_t; + + +/* increase userData counter in case the range match the userdata range */ +static bool +wmem_test_itree_check_overlap_cb (const void *key, void *value _U_, void *userData) +{ + const wmem_range_t *ckey = (const wmem_range_t *)key; + struct wmem_test_itree_user_data * d = (struct wmem_test_itree_user_data *)userData; + g_assert_true(key); + g_assert_true(d); + + if(wmem_itree_range_overlap(ckey, &d->range)) { + d->counter++; + } + + return false; +} + + +static bool +wmem_test_overlap(uint64_t low, uint64_t high, uint64_t lowbis, uint64_t highbis) +{ + wmem_range_t r1 = {low, high, 0}; + wmem_range_t r2 = {lowbis, highbis, 0}; + return wmem_itree_range_overlap(&r1, &r2); +} + +static void +wmem_test_itree(void) +{ + wmem_allocator_t *allocator, *extra_allocator; + wmem_itree_t *tree; + int i = 0; + int32_t max_rand = 0; + wmem_test_itree_user_data_t userData; + wmem_range_t range, r2; + + allocator = wmem_allocator_new(WMEM_ALLOCATOR_STRICT); + extra_allocator = wmem_allocator_new(WMEM_ALLOCATOR_STRICT); + + tree = wmem_itree_new(allocator); + g_assert_true(tree); + g_assert_true(wmem_itree_is_empty(tree)); + + wmem_free_all(allocator); + + /* make sure that wmem_test_overlap is correct (well it's no proof but...)*/ + g_assert_true(wmem_test_overlap(0, 10, 0, 4)); + g_assert_true(wmem_test_overlap(0, 10, 9, 14)); + g_assert_true(wmem_test_overlap(5, 10, 3, 8)); + g_assert_true(wmem_test_overlap(5, 10, 1, 12)); + g_assert_true(!wmem_test_overlap(0, 10, 11, 12)); + + /* Generate a reference range, then fill an itree with random ranges, + then we count greedily the number of overlapping ranges and compare + the result with the optimized result + */ + + userData.counter = 0; + + tree = wmem_itree_new(allocator); + + /* even though keys are uint64_t, we use INT32_MAX as a max because of the type returned by + g_test_rand_int_range. + */ + max_rand = INT32_MAX; + r2.max_edge = range.max_edge = 0; + range.low = g_test_rand_int_range(0, max_rand); + range.high = g_test_rand_int_range( (int32_t)range.low, (int32_t)max_rand); + userData.range = range; + + for (i=0; i<CONTAINER_ITERS; i++) { + + wmem_list_t *results = NULL; + + /* reset the search */ + userData.counter = 0; + r2.low = (uint64_t)g_test_rand_int_range(0, 100); + r2.high = (uint64_t)g_test_rand_int_range( (int32_t)r2.low, 100); + + wmem_itree_insert(tree, r2.low, r2.high, GINT_TO_POINTER(i)); + + /* greedy search */ + wmem_tree_foreach(tree, wmem_test_itree_check_overlap_cb, &userData); + + /* Optimized search */ + results = wmem_itree_find_intervals(tree, allocator, range.low, range.high); + + /* keep it as a loop instead of wmem_list_count in case one */ + g_assert_true(wmem_list_count(results) == userData.counter); + } + + wmem_destroy_allocator(extra_allocator); + wmem_destroy_allocator(allocator); +} + + +int +main(int argc, char **argv) +{ + int ret; + + wmem_init(); + + g_test_init(&argc, &argv, NULL); + + g_test_add_func("/wmem/allocator/block", wmem_test_allocator_block); + g_test_add_func("/wmem/allocator/blk_fast", wmem_test_allocator_block_fast); + g_test_add_func("/wmem/allocator/simple", wmem_test_allocator_simple); + g_test_add_func("/wmem/allocator/strict", wmem_test_allocator_strict); + g_test_add_func("/wmem/allocator/callbacks", wmem_test_allocator_callbacks); + + g_test_add_func("/wmem/utils/misc", wmem_test_miscutls); + g_test_add_func("/wmem/utils/strings", wmem_test_strutls); + + if (g_test_perf()) { + g_test_add_func("/wmem/utils/stringperf", wmem_test_stringperf); + } + + g_test_add_func("/wmem/datastruct/array", wmem_test_array); + g_test_add_func("/wmem/datastruct/list", wmem_test_list); + g_test_add_func("/wmem/datastruct/map", wmem_test_map); + g_test_add_func("/wmem/datastruct/queue", wmem_test_queue); + g_test_add_func("/wmem/datastruct/stack", wmem_test_stack); + g_test_add_func("/wmem/datastruct/strbuf", wmem_test_strbuf); + g_test_add_func("/wmem/datastruct/strbuf/validate", wmem_test_strbuf_validate); + g_test_add_func("/wmem/datastruct/tree", wmem_test_tree); + g_test_add_func("/wmem/datastruct/itree", wmem_test_itree); + + ret = g_test_run(); + + wmem_cleanup(); + + return ret; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_tree-int.h b/wsutil/wmem/wmem_tree-int.h new file mode 100644 index 00000000..d6b6cc10 --- /dev/null +++ b/wsutil/wmem/wmem_tree-int.h @@ -0,0 +1,85 @@ +/** @file + * + * Definitions for the Wireshark Memory Manager Red-Black Tree + * Copyright 2013, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WMEM_TREE_INT_H__ +#define __WMEM_TREE_INT_H__ + +#include "wmem_tree.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef enum _wmem_node_color_t { + WMEM_NODE_COLOR_RED, + WMEM_NODE_COLOR_BLACK +} wmem_node_color_t; + + +struct _wmem_tree_node_t { + struct _wmem_tree_node_t *parent; + struct _wmem_tree_node_t *left; + struct _wmem_tree_node_t *right; + + const void *key; + void *data; + + wmem_node_color_t color; + bool is_subtree; + bool is_removed; + + +}; + +typedef struct _wmem_tree_node_t wmem_tree_node_t; + + +typedef struct _wmem_itree_node_t wmem_itree_node_t; + +struct _wmem_tree_t { + wmem_allocator_t *metadata_allocator; + wmem_allocator_t *data_allocator; + wmem_tree_node_t *root; + unsigned metadata_scope_cb_id; + unsigned data_scope_cb_id; + + void (*post_rotation_cb)(wmem_tree_node_t *); +}; + +typedef int (*compare_func)(const void *a, const void *b); + +wmem_tree_node_t * +wmem_tree_insert(wmem_tree_t *tree, const void *key, void *data, compare_func cmp); + +typedef struct _wmem_range_t wmem_range_t; + +bool +wmem_itree_range_overlap(const wmem_range_t *r1, const wmem_range_t *r2); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __WMEM_TREE__INTERNALS_H__ */ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_tree.c b/wsutil/wmem/wmem_tree.c new file mode 100644 index 00000000..05fa638e --- /dev/null +++ b/wsutil/wmem/wmem_tree.c @@ -0,0 +1,869 @@ +/* wmem_tree.c + * Wireshark Memory Manager Red-Black Tree + * Based on the red-black tree implementation in epan/emem.* + * Copyright 2013, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" + +#include <string.h> +#include <stdio.h> +#include <glib.h> + +#include "wmem-int.h" +#include "wmem_core.h" +#include "wmem_strutl.h" +#include "wmem_tree.h" +#include "wmem_tree-int.h" +#include "wmem_user_cb.h" + + +static wmem_tree_node_t * +node_uncle(wmem_tree_node_t *node) +{ + wmem_tree_node_t *parent, *grandparent; + + parent = node->parent; + if (parent == NULL) { + return NULL; + } + + grandparent = parent->parent; + if (grandparent == NULL) { + return NULL; + } + + if (parent == grandparent->left) { + return grandparent->right; + } + else { + return grandparent->left; + } +} + +static void rb_insert_case1(wmem_tree_t *tree, wmem_tree_node_t *node); +static void rb_insert_case2(wmem_tree_t *tree, wmem_tree_node_t *node); + +static void +rotate_left(wmem_tree_t *tree, wmem_tree_node_t *node) +{ + if (node->parent) { + if (node->parent->left == node) { + node->parent->left = node->right; + } + else { + node->parent->right = node->right; + } + } + else { + tree->root = node->right; + } + + node->right->parent = node->parent; + node->parent = node->right; + node->right = node->right->left; + if (node->right) { + node->right->parent = node; + } + node->parent->left = node; + + if (tree->post_rotation_cb) { + tree->post_rotation_cb (node); + } +} + +static void +rotate_right(wmem_tree_t *tree, wmem_tree_node_t *node) +{ + if (node->parent) { + if (node->parent->left == node) { + node->parent->left = node->left; + } + else { + node->parent->right = node->left; + } + } + else { + tree->root = node->left; + } + + node->left->parent = node->parent; + node->parent = node->left; + node->left = node->left->right; + if (node->left) { + node->left->parent = node; + } + node->parent->right = node; + + + if (tree->post_rotation_cb) { + tree->post_rotation_cb (node); + } +} + +static void +rb_insert_case5(wmem_tree_t *tree, wmem_tree_node_t *node) +{ + wmem_tree_node_t *parent, *grandparent; + + parent = node->parent; + grandparent = parent->parent; + + parent->color = WMEM_NODE_COLOR_BLACK; + grandparent->color = WMEM_NODE_COLOR_RED; + + if (node == parent->left && parent == grandparent->left) { + rotate_right(tree, grandparent); + } + else { + rotate_left(tree, grandparent); + } +} + +static void +rb_insert_case4(wmem_tree_t *tree, wmem_tree_node_t *node) +{ + wmem_tree_node_t *parent, *grandparent; + + parent = node->parent; + grandparent = parent->parent; + if (!grandparent) { + return; + } + + if (node == parent->right && parent == grandparent->left) { + rotate_left(tree, parent); + node = node->left; + } + else if (node == parent->left && parent == grandparent->right) { + rotate_right(tree, parent); + node = node->right; + } + + rb_insert_case5(tree, node); +} + +static void +rb_insert_case3(wmem_tree_t *tree, wmem_tree_node_t *node) +{ + wmem_tree_node_t *parent, *grandparent, *uncle; + + uncle = node_uncle(node); + + if (uncle && uncle->color == WMEM_NODE_COLOR_RED) { + parent = node->parent; + grandparent = parent->parent; + + parent->color = WMEM_NODE_COLOR_BLACK; + uncle->color = WMEM_NODE_COLOR_BLACK; + grandparent->color = WMEM_NODE_COLOR_RED; + + rb_insert_case1(tree, grandparent); + } + else { + rb_insert_case4(tree, node); + } +} + +static void +rb_insert_case2(wmem_tree_t *tree, wmem_tree_node_t *node) +{ + /* parent is always non-NULL here */ + if (node->parent->color == WMEM_NODE_COLOR_RED) { + rb_insert_case3(tree, node); + } +} + +static void +rb_insert_case1(wmem_tree_t *tree, wmem_tree_node_t *node) +{ + wmem_tree_node_t *parent = node->parent; + + if (parent == NULL) { + node->color = WMEM_NODE_COLOR_BLACK; + } + else { + rb_insert_case2(tree, node); + } +} + +wmem_tree_t * +wmem_tree_new(wmem_allocator_t *allocator) +{ + wmem_tree_t *tree; + + tree = wmem_new0(allocator, wmem_tree_t); + tree->metadata_allocator = allocator; + tree->data_allocator = allocator; + + return tree; +} + +static bool +wmem_tree_reset_cb(wmem_allocator_t *allocator _U_, wmem_cb_event_t event, + void *user_data) +{ + wmem_tree_t *tree = (wmem_tree_t *)user_data; + + tree->root = NULL; + + if (event == WMEM_CB_DESTROY_EVENT) { + wmem_unregister_callback(tree->metadata_allocator, tree->metadata_scope_cb_id); + wmem_free(tree->metadata_allocator, tree); + } + + return true; +} + +static bool +wmem_tree_destroy_cb(wmem_allocator_t *allocator _U_, wmem_cb_event_t event _U_, + void *user_data) +{ + wmem_tree_t *tree = (wmem_tree_t *)user_data; + + wmem_unregister_callback(tree->data_allocator, tree->data_scope_cb_id); + + return false; +} + +wmem_tree_t * +wmem_tree_new_autoreset(wmem_allocator_t *metadata_scope, wmem_allocator_t *data_scope) +{ + wmem_tree_t *tree; + + tree = wmem_new0(metadata_scope, wmem_tree_t); + tree->metadata_allocator = metadata_scope; + tree->data_allocator = data_scope; + + tree->metadata_scope_cb_id = wmem_register_callback(metadata_scope, wmem_tree_destroy_cb, + tree); + tree->data_scope_cb_id = wmem_register_callback(data_scope, wmem_tree_reset_cb, + tree); + + return tree; +} + +static void +free_tree_node(wmem_allocator_t *allocator, wmem_tree_node_t* node, bool free_keys, bool free_values) +{ + if (node == NULL) { + return; + } + + if (node->left) { + free_tree_node(allocator, node->left, free_keys, free_values); + } + + if (node->is_subtree) { + wmem_tree_destroy((wmem_tree_t *)node->data, free_keys, free_values); + node->data = NULL; + } + + if (node->right) { + free_tree_node(allocator, node->right, free_keys, free_values); + } + + if (free_keys) { + wmem_free(allocator, (void*)node->key); + } + + if (free_values) { + wmem_free(allocator, node->data); + } + wmem_free(allocator, node); +} + +void +wmem_tree_destroy(wmem_tree_t *tree, bool free_keys, bool free_values) +{ + free_tree_node(tree->data_allocator, tree->root, free_keys, free_values); + if (tree->metadata_allocator) { + wmem_unregister_callback(tree->metadata_allocator, tree->metadata_scope_cb_id); + } + if (tree->data_allocator) { + wmem_unregister_callback(tree->data_allocator, tree->data_scope_cb_id); + } + wmem_free(tree->metadata_allocator, tree); +} + +bool +wmem_tree_is_empty(wmem_tree_t *tree) +{ + return tree->root == NULL; +} + +static bool +count_nodes(const void *key _U_, void *value _U_, void *userdata) +{ + unsigned* count = (unsigned*)userdata; + (*count)++; + return false; +} + +unsigned +wmem_tree_count(wmem_tree_t* tree) +{ + unsigned count = 0; + + /* Recursing through the tree counting each node is the simplest approach. + We don't keep track of the count within the tree because it can get + complicated with subtrees within the tree */ + wmem_tree_foreach(tree, count_nodes, &count); + + return count; +} + +static wmem_tree_node_t * +create_node(wmem_allocator_t *allocator, wmem_tree_node_t *parent, const void *key, + void *data, wmem_node_color_t color, bool is_subtree) +{ + wmem_tree_node_t *node; + + node = wmem_new(allocator, wmem_tree_node_t); + + node->left = NULL; + node->right = NULL; + node->parent = parent; + + node->key = key; + node->data = data; + + node->color = color; + node->is_subtree = is_subtree; + node->is_removed = false; + + return node; +} + +#define CREATE_DATA(TRANSFORM, DATA) ((TRANSFORM) ? (TRANSFORM)(DATA) : (DATA)) + + +/** + * return inserted node + */ +static wmem_tree_node_t * +lookup_or_insert32_node(wmem_tree_t *tree, uint32_t key, + void*(*func)(void*), void* data, bool is_subtree, bool replace) +{ + wmem_tree_node_t *node = tree->root; + wmem_tree_node_t *new_node = NULL; + + /* is this the first node ?*/ + if (!node) { + new_node = create_node(tree->data_allocator, NULL, GUINT_TO_POINTER(key), + CREATE_DATA(func, data), WMEM_NODE_COLOR_BLACK, is_subtree); + tree->root = new_node; + return new_node; + } + + /* it was not the new root so walk the tree until we find where to + * insert this new leaf. + */ + while (!new_node) { + /* this node already exists, so just return the data pointer*/ + if (key == GPOINTER_TO_UINT(node->key)) { + if (replace) { + node->data = CREATE_DATA(func, data); + } + return node; + } + else if (key < GPOINTER_TO_UINT(node->key)) { + if (node->left) { + node = node->left; + } + else { + /* new node to the left */ + new_node = create_node(tree->data_allocator, node, GUINT_TO_POINTER(key), + CREATE_DATA(func, data), WMEM_NODE_COLOR_RED, + is_subtree); + node->left = new_node; + } + } + else if (key > GPOINTER_TO_UINT(node->key)) { + if (node->right) { + node = node->right; + } + else { + /* new node to the right */ + new_node = create_node(tree->data_allocator, node, GUINT_TO_POINTER(key), + CREATE_DATA(func, data), WMEM_NODE_COLOR_RED, + is_subtree); + node->right = new_node; + } + } + } + + /* node will now point to the newly created node */ + rb_insert_case1(tree, new_node); + + return new_node; +} + + +static void * +lookup_or_insert32(wmem_tree_t *tree, uint32_t key, + void*(*func)(void*), void* data, bool is_subtree, bool replace) +{ + wmem_tree_node_t *node = lookup_or_insert32_node(tree, key, func, data, is_subtree, replace); + return node->data; +} + +static void * +wmem_tree_lookup(wmem_tree_t *tree, const void *key, compare_func cmp) +{ + wmem_tree_node_t *node; + + if (tree == NULL || key == NULL) { + return NULL; + } + + node = tree->root; + + while (node) { + int result = cmp(key, node->key); + if (result == 0) { + return node->data; + } + else if (result < 0) { + node = node->left; + } + else if (result > 0) { + node = node->right; + } + } + + return NULL; +} + +wmem_tree_node_t * +wmem_tree_insert(wmem_tree_t *tree, const void *key, void *data, compare_func cmp) +{ + wmem_tree_node_t *node = tree->root; + wmem_tree_node_t *new_node = NULL; + + /* is this the first node ?*/ + if (!node) { + tree->root = create_node(tree->data_allocator, node, key, + data, WMEM_NODE_COLOR_BLACK, false); + return tree->root; + } + + /* it was not the new root so walk the tree until we find where to + * insert this new leaf. + */ + while (!new_node) { + int result = cmp(key, node->key); + if (result == 0) { + node->data = data; + node->is_removed = data ? false : true; + return node; + } + else if (result < 0) { + if (node->left) { + node = node->left; + } + else { + new_node = create_node(tree->data_allocator, node, key, + data, WMEM_NODE_COLOR_RED, false); + node->left = new_node; + } + } + else if (result > 0) { + if (node->right) { + node = node->right; + } + else { + /* new node to the right */ + new_node = create_node(tree->data_allocator, node, key, + data, WMEM_NODE_COLOR_RED, false); + node->right = new_node; + } + } + } + + /* node will now point to the newly created node */ + rb_insert_case1(tree, new_node); + + return new_node; +} + +void +wmem_tree_insert32(wmem_tree_t *tree, uint32_t key, void *data) +{ + lookup_or_insert32(tree, key, NULL, data, false, true); +} + +bool wmem_tree_contains32(wmem_tree_t *tree, uint32_t key) +{ + if (!tree) { + return false; + } + + wmem_tree_node_t *node = tree->root; + + while (node) { + if (key == GPOINTER_TO_UINT(node->key)) { + return true; + } + else if (key < GPOINTER_TO_UINT(node->key)) { + node = node->left; + } + else if (key > GPOINTER_TO_UINT(node->key)) { + node = node->right; + } + } + + return false; +} + +void * +wmem_tree_lookup32(wmem_tree_t *tree, uint32_t key) +{ + if (!tree) { + return NULL; + } + + wmem_tree_node_t *node = tree->root; + + while (node) { + if (key == GPOINTER_TO_UINT(node->key)) { + return node->data; + } + else if (key < GPOINTER_TO_UINT(node->key)) { + node = node->left; + } + else if (key > GPOINTER_TO_UINT(node->key)) { + node = node->right; + } + } + + return NULL; +} + +void * +wmem_tree_lookup32_le(wmem_tree_t *tree, uint32_t key) +{ + if (!tree) { + return NULL; + } + + wmem_tree_node_t *node = tree->root; + + while (node) { + if (key == GPOINTER_TO_UINT(node->key)) { + return node->data; + } + else if (key < GPOINTER_TO_UINT(node->key)) { + if (node->left == NULL) { + break; + } + node = node->left; + } + else if (key > GPOINTER_TO_UINT(node->key)) { + if (node->right == NULL) { + break; + } + node = node->right; + } + } + + if (!node) { + return NULL; + } + + /* If we are still at the root of the tree this means that this node + * is either smaller than the search key and then we return this + * node or else there is no smaller key available and then + * we return NULL. + */ + if (node->parent == NULL) { + if (key > GPOINTER_TO_UINT(node->key)) { + return node->data; + } else { + return NULL; + } + } + + if (GPOINTER_TO_UINT(node->key) <= key) { + /* if our key is <= the search key, we have the right node */ + return node->data; + } + else if (node == node->parent->left) { + /* our key is bigger than the search key and we're a left child, + * we have to check if any of our ancestors are smaller. */ + while (node) { + if (key > GPOINTER_TO_UINT(node->key)) { + return node->data; + } + node=node->parent; + } + return NULL; + } + else { + /* our key is bigger than the search key and we're a right child, + * our parent is the one we want */ + return node->parent->data; + } +} + +void * +wmem_tree_remove32(wmem_tree_t *tree, uint32_t key) +{ + void *ret = wmem_tree_lookup32(tree, key); + if (ret) { + /* Not really a remove, but set data to NULL to mark node with is_removed */ + wmem_tree_insert32(tree, key, NULL); + } + return ret; +} + +void +wmem_tree_insert_string(wmem_tree_t* tree, const char* k, void* v, uint32_t flags) +{ + char *key; + compare_func cmp; + + key = wmem_strdup(tree->data_allocator, k); + + if (flags & WMEM_TREE_STRING_NOCASE) { + cmp = (compare_func)g_ascii_strcasecmp; + } else { + cmp = (compare_func)strcmp; + } + + wmem_tree_insert(tree, key, v, cmp); +} + +void * +wmem_tree_lookup_string(wmem_tree_t* tree, const char* k, uint32_t flags) +{ + compare_func cmp; + + if (flags & WMEM_TREE_STRING_NOCASE) { + cmp = (compare_func)g_ascii_strcasecmp; + } else { + cmp = (compare_func)strcmp; + } + + return wmem_tree_lookup(tree, k, cmp); +} + +void * +wmem_tree_remove_string(wmem_tree_t* tree, const char* k, uint32_t flags) +{ + void *ret = wmem_tree_lookup_string(tree, k, flags); + if (ret) { + /* Not really a remove, but set data to NULL to mark node with is_removed */ + wmem_tree_insert_string(tree, k, NULL, flags); + } + return ret; +} + +static void * +create_sub_tree(void* d) +{ + return wmem_tree_new(((wmem_tree_t *)d)->data_allocator); +} + +void +wmem_tree_insert32_array(wmem_tree_t *tree, wmem_tree_key_t *key, void *data) +{ + wmem_tree_t *insert_tree = NULL; + wmem_tree_key_t *cur_key; + uint32_t i, insert_key32 = 0; + + for (cur_key = key; cur_key->length > 0; cur_key++) { + for (i = 0; i < cur_key->length; i++) { + /* Insert using the previous key32 */ + if (!insert_tree) { + insert_tree = tree; + } else { + insert_tree = (wmem_tree_t *)lookup_or_insert32(insert_tree, + insert_key32, create_sub_tree, tree, true, false); + } + insert_key32 = cur_key->key[i]; + } + } + + ws_assert(insert_tree); + + wmem_tree_insert32(insert_tree, insert_key32, data); +} + +static void * +wmem_tree_lookup32_array_helper(wmem_tree_t *tree, wmem_tree_key_t *key, + void*(*helper)(wmem_tree_t*, uint32_t)) +{ + wmem_tree_t *lookup_tree = NULL; + wmem_tree_key_t *cur_key; + uint32_t i, lookup_key32 = 0; + + if (!tree || !key) { + return NULL; + } + + for (cur_key = key; cur_key->length > 0; cur_key++) { + for (i = 0; i < cur_key->length; i++) { + /* Lookup using the previous key32 */ + if (!lookup_tree) { + lookup_tree = tree; + } + else { + lookup_tree = + (wmem_tree_t *)(*helper)(lookup_tree, lookup_key32); + if (!lookup_tree) { + return NULL; + } + } + lookup_key32 = cur_key->key[i]; + } + } + + /* Assert if we didn't get any valid keys */ + ws_assert(lookup_tree); + + return (*helper)(lookup_tree, lookup_key32); +} + +void * +wmem_tree_lookup32_array(wmem_tree_t *tree, wmem_tree_key_t *key) +{ + return wmem_tree_lookup32_array_helper(tree, key, wmem_tree_lookup32); +} + +void * +wmem_tree_lookup32_array_le(wmem_tree_t *tree, wmem_tree_key_t *key) +{ + return wmem_tree_lookup32_array_helper(tree, key, wmem_tree_lookup32_le); +} + +static bool +wmem_tree_foreach_nodes(wmem_tree_node_t* node, wmem_foreach_func callback, + void *user_data) +{ + bool stop_traverse = false; + + if (!node) { + return false; + } + + if (node->left) { + if (wmem_tree_foreach_nodes(node->left, callback, user_data)) { + return true; + } + } + + if (node->is_subtree) { + stop_traverse = wmem_tree_foreach((wmem_tree_t *)node->data, + callback, user_data); + } else if (!node->is_removed) { + /* No callback for "removed" nodes */ + stop_traverse = callback(node->key, node->data, user_data); + } + + if (stop_traverse) { + return true; + } + + if(node->right) { + if (wmem_tree_foreach_nodes(node->right, callback, user_data)) { + return true; + } + } + + return false; +} + +bool +wmem_tree_foreach(wmem_tree_t* tree, wmem_foreach_func callback, + void *user_data) +{ + if(!tree->root) + return false; + + return wmem_tree_foreach_nodes(tree->root, callback, user_data); +} + +static void wmem_print_subtree(wmem_tree_t *tree, uint32_t level, wmem_printer_func key_printer, wmem_printer_func data_printer); + +static void +wmem_print_indent(uint32_t level) { + uint32_t i; + for (i=0; i<level; i++) { + printf(" "); + } +} + +static void +wmem_tree_print_nodes(const char *prefix, wmem_tree_node_t *node, uint32_t level, + wmem_printer_func key_printer, wmem_printer_func data_printer) +{ + if (!node) + return; + + wmem_print_indent(level); + + printf("%sNODE:%p parent:%p left:%p right:%p colour:%s key:%p %s:%p\n", + prefix, + (void *)node, (void *)node->parent, + (void *)node->left, (void *)node->right, + node->color?"Black":"Red", node->key, + node->is_subtree?"tree":"data", node->data); + if (key_printer) { + wmem_print_indent(level); + key_printer(node->key); + printf("\n"); + } + if (data_printer && !node->is_subtree) { + wmem_print_indent(level); + data_printer(node->data); + printf("\n"); + } + + if (node->left) + wmem_tree_print_nodes("L-", node->left, level+1, key_printer, data_printer); + if (node->right) + wmem_tree_print_nodes("R-", node->right, level+1, key_printer, data_printer); + + if (node->is_subtree) + wmem_print_subtree((wmem_tree_t *)node->data, level+1, key_printer, data_printer); +} + + +static void +wmem_print_subtree(wmem_tree_t *tree, uint32_t level, wmem_printer_func key_printer, wmem_printer_func data_printer) +{ + if (!tree) + return; + + wmem_print_indent(level); + + printf("WMEM tree:%p root:%p\n", (void *)tree, (void *)tree->root); + if (tree->root) { + wmem_tree_print_nodes("Root-", tree->root, level, key_printer, data_printer); + } +} + +void +wmem_print_tree(wmem_tree_t *tree, wmem_printer_func key_printer, wmem_printer_func data_printer) +{ + wmem_print_subtree(tree, 0, key_printer, data_printer); +} +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_tree.h b/wsutil/wmem/wmem_tree.h new file mode 100644 index 00000000..f639ff9a --- /dev/null +++ b/wsutil/wmem/wmem_tree.h @@ -0,0 +1,263 @@ +/** @file + * Definitions for the Wireshark Memory Manager Red-Black Tree + * Based on the red-black tree implementation in epan/emem.* + * Copyright 2013, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WMEM_TREE_H__ +#define __WMEM_TREE_H__ + +#include "wmem_core.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** @addtogroup wmem + * @{ + * @defgroup wmem-tree Red/Black Tree + * + * Binary trees are a well-known and popular device in computer science to + * handle storage of objects based on a search key or identity. The + * particular binary tree style implemented here is the red/black tree, which + * has the nice property of being self-balancing. This guarantees O(log(n)) + * time for lookups, compared to linked lists that are O(n). This means + * red/black trees scale very well when many objects are being stored. + * + * @{ + */ + +struct _wmem_tree_t; +typedef struct _wmem_tree_t wmem_tree_t; + +/** Creates a tree with the given allocator scope. When the scope is emptied, + * the tree is fully destroyed. */ +WS_DLL_PUBLIC +wmem_tree_t * +wmem_tree_new(wmem_allocator_t *allocator) +G_GNUC_MALLOC; + +/** Creates a tree with two allocator scopes. The base structure lives in the + * metadata scope, and the tree data lives in the data scope. Every time free_all + * occurs in the data scope the tree is transparently emptied without affecting + * the location of the base / metadata structure. + * + * WARNING: None of the tree (even the part in the metadata scope) can be used + * after the data scope has been *destroyed*. + * + * The primary use for this function is to create trees that reset for each new + * capture file that is loaded. This can be done by specifying wmem_epan_scope() + * as the metadata scope and wmem_file_scope() as the data scope. + */ +WS_DLL_PUBLIC +wmem_tree_t * +wmem_tree_new_autoreset(wmem_allocator_t *metadata_scope, wmem_allocator_t *data_scope) +G_GNUC_MALLOC; + +/** Cleanup memory used by tree. Intended for NULL scope allocated trees */ +WS_DLL_PUBLIC +void +wmem_tree_destroy(wmem_tree_t *tree, bool free_keys, bool free_values); + +/** Returns true if the tree is empty (has no nodes). */ +WS_DLL_PUBLIC +bool +wmem_tree_is_empty(wmem_tree_t *tree); + +/** Returns number of nodes in tree */ +WS_DLL_PUBLIC +unsigned +wmem_tree_count(wmem_tree_t* tree); + +/** Insert a node indexed by a uint32_t key value. + * + * Data is a pointer to the structure you want to be able to retrieve by + * searching for the same key later. + * + * NOTE: If you insert a node to a key that already exists in the tree this + * function will simply overwrite the old value. If the structures you are + * storing are allocated in a wmem pool this is not a problem as they will still + * be freed with the pool. If you are managing them manually however, you must + * either ensure the key is unique, or do a lookup before each insert. + */ +WS_DLL_PUBLIC +void +wmem_tree_insert32(wmem_tree_t *tree, uint32_t key, void *data); + +/** Look up a node in the tree indexed by a uint32_t integer value. Return true + * if present. + */ +WS_DLL_PUBLIC +bool +wmem_tree_contains32(wmem_tree_t *tree, uint32_t key); + +/** Look up a node in the tree indexed by a uint32_t integer value. If no node is + * found the function will return NULL. + */ +WS_DLL_PUBLIC +void * +wmem_tree_lookup32(wmem_tree_t *tree, uint32_t key); + +/** Look up a node in the tree indexed by a uint32_t integer value. + * Returns the node that has the largest key that is less than or equal + * to the search key, or NULL if no such key exists. + */ +WS_DLL_PUBLIC +void * +wmem_tree_lookup32_le(wmem_tree_t *tree, uint32_t key); + +/** Remove a node in the tree indexed by a uint32_t integer value. This is not + * really a remove, but the value is set to NULL so that wmem_tree_lookup32 + * not will find it. + */ +WS_DLL_PUBLIC +void * +wmem_tree_remove32(wmem_tree_t *tree, uint32_t key); + +/** case insensitive strings as keys */ +#define WMEM_TREE_STRING_NOCASE 0x00000001 +/** Insert a new value under a string key. Like wmem_tree_insert32 but where the + * key is a null-terminated string instead of a uint32_t. You may pass + * WMEM_TREE_STRING_NOCASE to the flags argument in order to make it store the + * key in a case-insensitive way. (Note that "case-insensitive" refers + * only to the ASCII letters A-Z and a-z; it is locale-independent. + * Do not expect it to honor the rules of your language; for example, "I" + * will always be mapped to "i". */ +WS_DLL_PUBLIC +void +wmem_tree_insert_string(wmem_tree_t *tree, const char* key, void *data, + uint32_t flags); + +/** Lookup the value under a string key, like wmem_tree_lookup32 but where the + * keye is a null-terminated string instead of a uint32_t. See + * wmem_tree_insert_string for an explanation of flags. */ +WS_DLL_PUBLIC +void * +wmem_tree_lookup_string(wmem_tree_t* tree, const char* key, uint32_t flags); + +/** Remove the value under a string key. This is not really a remove, but the + * value is set to NULL so that wmem_tree_lookup_string not will find it. + * See wmem_tree_insert_string for an explanation of flags. */ +WS_DLL_PUBLIC +void * +wmem_tree_remove_string(wmem_tree_t* tree, const char* key, uint32_t flags); + +typedef struct _wmem_tree_key_t { + uint32_t length; /**< length in uint32_t words */ + uint32_t *key; +} wmem_tree_key_t; + +/** Insert a node indexed by a sequence of uint32_t key values. + * + * Takes as key an array of uint32_t vectors of type wmem_tree_key_t. It will + * iterate through each key to search further down the tree until it reaches an + * element where length==0, indicating the end of the array. You MUST terminate + * the key array by {0, NULL} or this will crash. + * + * NOTE: length indicates the number of uint32_t values in the vector, not the + * number of bytes. + * + * NOTE: all the "key" members of the "key" argument MUST be aligned on + * 32-bit boundaries; otherwise, this code will crash on platforms such + * as SPARC that require aligned pointers. + * + * If you use ...32_array() calls you MUST make sure that every single node + * you add to a specific tree always has a key of exactly the same number of + * keylen words or it will crash. Or at least that every single item that sits + * behind the same top level node always has exactly the same number of words. + * + * One way to guarantee this is the way that NFS does this for the + * nfs_name_snoop_known tree which holds filehandles for both v2 and v3. + * v2 filehandles are always 32 bytes (8 words) while v3 filehandles can have + * any length (though 32 bytes are most common). + * The NFS dissector handles this by providing a uint32_t containing the length + * as the very first item in this vector : + * + * wmem_tree_key_t fhkey[3]; + * + * fhlen=nns->fh_length; + * fhkey[0].length=1; + * fhkey[0].key=&fhlen; + * fhkey[1].length=fhlen/4; + * fhkey[1].key=nns->fh; + * fhkey[2].length=0; + */ +WS_DLL_PUBLIC +void +wmem_tree_insert32_array(wmem_tree_t *tree, wmem_tree_key_t *key, void *data); + +/** Look up a node in the tree indexed by a sequence of uint32_t integer values. + * See wmem_tree_insert32_array for details on the key. + */ +WS_DLL_PUBLIC +void * +wmem_tree_lookup32_array(wmem_tree_t *tree, wmem_tree_key_t *key); + +/** Look up a node in the tree indexed by a multi-part tree value. + * The function will return the node that has the largest key that is + * equal to or smaller than the search key, or NULL if no such key was + * found. + * + * NOTE: The key returned will be "less" in key order. The usefulness + * of the returned node must be verified prior to use. + * + * See wmem_tree_insert32_array for details on the key. + */ +WS_DLL_PUBLIC +void * +wmem_tree_lookup32_array_le(wmem_tree_t *tree, wmem_tree_key_t *key); + +/** Function type for processing one node of a tree during a traversal. Value is + * the value of the node, userdata is whatever was passed to the traversal + * function. If the function returns true the traversal will end prematurely. + */ +typedef bool (*wmem_foreach_func)(const void *key, void *value, void *userdata); + + +/** Function type to print key/data of nodes in wmem_print_tree_verbose */ +typedef void (*wmem_printer_func)(const void *data); + + +/** Inorder traversal (left/parent/right) of the tree and call + * callback(value, userdata) for each value found. + * + * Returns true if the traversal was ended prematurely by the callback. + */ +WS_DLL_PUBLIC +bool +wmem_tree_foreach(wmem_tree_t* tree, wmem_foreach_func callback, + void *user_data); + + +/* Accepts callbacks to print the key and/or data (both printers can be null) */ +WS_DLL_PUBLIC +void +wmem_print_tree(wmem_tree_t *tree, wmem_printer_func key_printer, wmem_printer_func data_printer); + +/** @} + * @} */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __WMEM_TREE_H__ */ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_user_cb.c b/wsutil/wmem/wmem_user_cb.c new file mode 100644 index 00000000..232b1692 --- /dev/null +++ b/wsutil/wmem/wmem_user_cb.c @@ -0,0 +1,104 @@ +/* wmem_user_cb.c + * Wireshark Memory Manager User Callbacks + * Copyright 2012, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "wmem_core.h" +#include "wmem_allocator.h" + +#include "wmem_user_cb.h" +#include "wmem_user_cb_int.h" + +typedef struct _wmem_user_cb_container_t { + wmem_user_cb_t cb; + void *user_data; + struct _wmem_user_cb_container_t *next; + unsigned id; +} wmem_user_cb_container_t; + +void +wmem_call_callbacks(wmem_allocator_t *allocator, wmem_cb_event_t event) +{ + wmem_user_cb_container_t **prev, *cur; + bool again; + + prev = &(allocator->callbacks); + cur = allocator->callbacks; + + while (cur) { + + /* call it */ + again = cur->cb(allocator, event, cur->user_data); + + /* if the callback requested deregistration, or this is being triggered + * by the final destruction of the allocator, remove the callback */ + if (! again || event == WMEM_CB_DESTROY_EVENT) { + *prev = cur->next; + wmem_free(NULL, cur); + cur = *prev; + } + else { + prev = &(cur->next); + cur = cur->next; + } + } +} + +unsigned +wmem_register_callback(wmem_allocator_t *allocator, + wmem_user_cb_t callback, void *user_data) +{ + wmem_user_cb_container_t *container; + static unsigned next_id = 1; + + container = wmem_new(NULL, wmem_user_cb_container_t); + + container->cb = callback; + container->user_data = user_data; + container->next = allocator->callbacks; + container->id = next_id++; + + allocator->callbacks = container; + + return container->id; +} + +void +wmem_unregister_callback(wmem_allocator_t *allocator, unsigned id) +{ + wmem_user_cb_container_t **prev, *cur; + + prev = &(allocator->callbacks); + cur = allocator->callbacks; + + while (cur) { + + if (cur->id == id) { + *prev = cur->next; + wmem_free(NULL, cur); + return; + } + + prev = &(cur->next); + cur = cur->next; + } +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_user_cb.h b/wsutil/wmem/wmem_user_cb.h new file mode 100644 index 00000000..7d2a37e3 --- /dev/null +++ b/wsutil/wmem/wmem_user_cb.h @@ -0,0 +1,92 @@ +/** @file + * Definitions for the Wireshark Memory Manager User Callbacks + * Copyright 2012, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WMEM_USER_CB_H__ +#define __WMEM_USER_CB_H__ + +#include <glib.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** @addtogroup wmem + * @{ + * @defgroup wmem-user-cb User Callbacks + * + * User callbacks. + * + * @{ + */ + +/** The events that can trigger a callback. */ +typedef enum _wmem_cb_event_t { + WMEM_CB_FREE_EVENT, /**< wmem_free_all() */ + WMEM_CB_DESTROY_EVENT /**< wmem_destroy_allocator() */ +} wmem_cb_event_t; + +/** Function signature for registered user callbacks. + * + * allocator The allocator that triggered this callback. + * event The event type that triggered this callback. + * user_data Whatever user_data was originally passed to the call to + * wmem_register_callback(). + * @return false to unregister the callback, true otherwise. + */ +typedef bool (*wmem_user_cb_t) (wmem_allocator_t*, wmem_cb_event_t, void*); + +/** Register a callback function with the given allocator pool. + * + * @param allocator The allocator with which to register the callback. + * @param callback The function to be called as the callback. + * @param user_data An arbitrary data pointer that is passed to the callback as + * a way to specify extra parameters or store extra data. Note + * that this pointer is not freed when a callback is finished, + * you have to do that yourself in the callback, or just + * allocate it in the appropriate wmem pool. + * @return ID of this callback that can be passed back to + * wmem_unregister_callback(). + */ +WS_DLL_PUBLIC +unsigned +wmem_register_callback(wmem_allocator_t *allocator, wmem_user_cb_t callback, + void *user_data); + +/** Unregister the callback function with the given ID. + * + * @param allocator The allocator from which to unregister the callback. + * @param id The callback id as returned from wmem_register_callback(). + */ +WS_DLL_PUBLIC +void +wmem_unregister_callback(wmem_allocator_t *allocator, unsigned id); + +/** @} + * @} */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __WMEM_USER_CB_H__ */ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wmem/wmem_user_cb_int.h b/wsutil/wmem/wmem_user_cb_int.h new file mode 100644 index 00000000..9f3ed58a --- /dev/null +++ b/wsutil/wmem/wmem_user_cb_int.h @@ -0,0 +1,45 @@ +/** @file + * + * Definitions for the Wireshark Memory Manager User Callback Internals + * Copyright 2012, Evan Huus <eapache@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WMEM_USER_CB_INT_H__ +#define __WMEM_USER_CB_INT_H__ + +#include <glib.h> + +#include "wmem_user_cb.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +WS_DLL_LOCAL +void +wmem_call_callbacks(wmem_allocator_t *allocator, wmem_cb_event_t event); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __WMEM_USER_CB_INT_H__ */ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/ws_assert.h b/wsutil/ws_assert.h new file mode 100644 index 00000000..f4880fd8 --- /dev/null +++ b/wsutil/ws_assert.h @@ -0,0 +1,129 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WS_ASSERT_H__ +#define __WS_ASSERT_H__ + +#include <ws_symbol_export.h> +#include <ws_attributes.h> +#include <stdbool.h> +#include <string.h> +#include <wsutil/wslog.h> +#include <wsutil/wmem/wmem.h> + +#if defined(ENABLE_ASSERT) +#define WS_ASSERT_ENABLED 1 +#elif defined(NDEBUG) +#define WS_ASSERT_ENABLED 0 +#else +#define WS_ASSERT_ENABLED 1 +#endif + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * We don't want to execute the expression without assertions because + * it might be time and space costly and the goal here is to optimize for + * that case. However removing it completely is not good enough + * because it might generate many unused variable warnings. So we use + * if (false) and let the compiler optimize away the dead execution branch. + */ +#define ws_assert_if_active(active, expr) \ + do { \ + if ((active) && !(expr)) \ + ws_error("assertion failed: %s", #expr); \ + } while (0) + +/* + * ws_abort_if_fail() is not conditional on having assertions enabled. + * Usually used to appease a static analyzer. + */ +#define ws_abort_if_fail(expr) \ + ws_assert_if_active(true, expr) + +/* + * ws_assert() cannot produce side effects, otherwise code will + * behave differently because of having assertions enabled/disabled, and + * probably introduce some difficult to track bugs. + */ +#define ws_assert(expr) \ + ws_assert_if_active(WS_ASSERT_ENABLED, expr) + + +#define ws_assert_streq(s1, s2) \ + ws_assert((s1) && (s2) && strcmp((s1), (s2)) == 0) + +#define ws_assert_utf8(str, len) \ + do { \ + const char *__assert_endptr; \ + if (WS_ASSERT_ENABLED && \ + !g_utf8_validate(str, len, &__assert_endptr)) { \ + ws_log_utf8_full(LOG_DOMAIN_UTF_8, LOG_LEVEL_ERROR, \ + __FILE__, __LINE__, __func__, \ + str, len, __assert_endptr); \ + } \ + } while (0) + +/* + * We don't want to disable ws_assert_not_reached() with (optional) assertions + * disabled. + * That would blast compiler warnings everywhere for no benefit, not + * even a miniscule performance gain. Reaching this function is always + * a programming error and will unconditionally abort execution. + * + * Note: With g_assert_not_reached() if the compiler supports unreachable + * built-ins (which recent versions of GCC and MSVC do) there is no warning + * blast with g_assert_not_reached() and G_DISABLE_ASSERT. However if that + * is not the case then g_assert_not_reached() is simply (void)0 and that + * causes the spurious warnings, because the compiler can't tell anymore + * that a certain code path is not used. We avoid that with + * ws_assert_not_reached(). There is no reason to ever use a no-op here. + */ +#define ws_assert_not_reached() \ + ws_error("assertion \"not reached\" failed") + +/* + * These macros can be used as an alternative to ws_assert() to + * assert some condition on function arguments. This must only be used + * to catch programming errors, in situations where an assertion is + * appropriate. And it should only be used if failing the condition + * doesn't necessarily lead to an inconsistent state for the program. + * + * It is possible to set the fatal log domain to "InvalidArg" to abort + * execution for debugging purposes, if one of these checks fail. + */ + +#define ws_warn_badarg(str) \ + ws_log_full(LOG_DOMAIN_EINVAL, LOG_LEVEL_WARNING, \ + __FILE__, __LINE__, __func__, \ + "invalid argument: %s", str) + +#define ws_return_str_if(expr, scope) \ + do { \ + if (WS_ASSERT_ENABLED && (expr)) { \ + ws_warn_badarg(#expr); \ + return wmem_strdup_printf(scope, "(invalid argument: %s)", #expr); \ + } \ + } while (0) + +#define ws_return_val_if(expr, val) \ + do { \ + if (WS_ASSERT_ENABLED && (expr)) { \ + ws_warn_badarg(#expr); \ + return (val); \ + } \ + } while (0) + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __WS_ASSERT_H__ */ diff --git a/wsutil/ws_cpuid.h b/wsutil/ws_cpuid.h new file mode 100644 index 00000000..cc17e2a5 --- /dev/null +++ b/wsutil/ws_cpuid.h @@ -0,0 +1,141 @@ +/** @file + * Get the CPU info on x86 processors that support it + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* + * Get CPU info on platforms where the x86 cpuid instruction can be used. + * + * Skip 32-bit versions for GCC and Clang, as older IA-32 processors don't + * have cpuid. + * + * Intel has documented the CPUID instruction in the "Intel(r) 64 and IA-32 + * Architectures Developer's Manual" at + * + * https://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-vol-2a-manual.html + * + * The ws_cpuid() routine will return 0 if cpuinfo isn't available, including + * on non-x86 platforms and on 32-bit x86 platforms with GCC and Clang, as + * well as non-MSVC and non-GCC-or-Clang platforms. + * + * The "selector" argument to ws_cpuid() is the "initial EAX value" for the + * instruction. The initial ECX value is 0. + * + * The "CPUInfo" argument points to 4 32-bit values into which the + * resulting values of EAX, EBX, ECX, and EDX are store, in order. + */ + +#include "ws_attributes.h" + +#include <inttypes.h> +#include <stdbool.h> + +#if defined(_MSC_VER) /* MSVC */ + +/* + * XXX - do the same IA-32 (which doesn't have CPUID prior to some versions + * of the 80486 and all versions of the 80586^Woriginal Pentium) vs. + * x86-64 (which always has CPUID) stuff that we do with GCC/Clang? + * + * You will probably not be happy running current versions of Wireshark + * on an 80386 or 80486 machine, and we're dropping support for IA-32 + * on Windows anyway, so the answer is probably "no". + */ +#if defined(_M_IX86) || defined(_M_X64) +static bool +ws_cpuid(uint32_t *CPUInfo, uint32_t selector) +{ + /* https://docs.microsoft.com/en-us/cpp/intrinsics/cpuid-cpuidex */ + + CPUInfo[0] = CPUInfo[1] = CPUInfo[2] = CPUInfo[3] = 0; + __cpuid((int *) CPUInfo, selector); + /* XXX, how to check if it's supported on MSVC? just in case clear all flags above */ + return true; +} +#else /* not x86 */ +static bool +ws_cpuid(uint32_t *CPUInfo _U_, int selector _U_) +{ + /* Not x86, so no cpuid instruction */ + return false; +} +#endif + +#elif defined(__GNUC__) /* GCC/clang */ + +#if defined(__x86_64__) +static inline bool +ws_cpuid(uint32_t *CPUInfo, int selector) +{ + __asm__ __volatile__("cpuid" + : "=a" (CPUInfo[0]), + "=b" (CPUInfo[1]), + "=c" (CPUInfo[2]), + "=d" (CPUInfo[3]) + : "a" (selector), + "c" (0)); + return true; +} +#elif defined(__i386__) +static bool +ws_cpuid(uint32_t *CPUInfo _U_, int selector _U_) +{ + /* + * TODO: need a test if older processors have the cpuid instruction. + * + * The correct way to test for this, according to the Intel64/IA-32 + * documentation from Intel, in section 17.1 "USING THE CPUID + * INSTRUCTION", is to try to change the ID bit (bit 21) in + * EFLAGS. If it can be changed, the machine supports CPUID, + * otherwise it doesn't. + * + * Some 486's, and all subsequent processors, support CPUID. + * + * For those who are curious, the way you distinguish between + * an 80386 and an 80486 is to try to set the flag in EFLAGS + * that causes unaligned accesses to fault - that's bit 18. + * However, if the SMAP bit is set in CR4, that bit controls + * whether explicit supervisor-mode access to user-mode pages + * are allowed, so that should presumably only be done in a + * very controlled environment, such as the system boot process. + * + * So, if you want to find out what type of CPU the system has, + * it's probably best to ask the OS, if it supplies the result + * of any CPU type testing it's done. + */ + return false; +} +#else /* not x86 */ +static bool +ws_cpuid(uint32_t *CPUInfo _U_, int selector _U_) +{ + /* Not x86, so no cpuid instruction */ + return false; +} +#endif + +#else /* Other compilers */ + +static bool +ws_cpuid(uint32_t *CPUInfo _U_, int selector _U_) +{ + return false; +} +#endif + +static int +ws_cpuid_sse42(void) +{ + uint32_t CPUInfo[4]; + + if (!ws_cpuid(CPUInfo, 1)) + return 0; + + /* in ECX bit 20 toggled on */ + return (CPUInfo[2] & (1 << 20)); +} diff --git a/wsutil/ws_getopt.c b/wsutil/ws_getopt.c new file mode 100644 index 00000000..ced6e177 --- /dev/null +++ b/wsutil/ws_getopt.c @@ -0,0 +1,271 @@ +/* + * musl as a whole is licensed under the following standard MIT license: + * + * ---------------------------------------------------------------------- + * Copyright © 2005-2020 Rich Felker, et al. + * + * 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. + * ---------------------------------------------------------------------- + */ + +#include <wsutil/ws_getopt.h> + +#include <stddef.h> +#include <stdlib.h> +#include <limits.h> +#include <stdio.h> +#include <string.h> +#include <wchar.h> + +#include <ws_codepoints.h> + +char *ws_optarg; +int ws_optind=1, ws_opterr=1, ws_optopt, ws_optpos, ws_optreset=0; + +static void __getopt_msg(const char *prog, const char *errstr, + const char *optbuf, size_t optsize) +{ + FILE *f = stderr; + if ((fputs(prog, f) < 0) || + (fputs(errstr, f) < 0) || + (fwrite(optbuf, sizeof(char), optsize, f) != optsize)) { + return; + } + putc('\n', f); +} + +static void permute(char *const *argv, int dest, int src) +{ + char **av = (char **)argv; + char *tmp = av[src]; + int i; + for (i=src; i>dest; i--) + av[i] = av[i-1]; + av[dest] = tmp; +} + +int ws_getopt(int argc, char * const argv[], const char *optstring) +{ + int i; + wchar_t c, d; + int k, l; + char *optchar; + + if (!ws_optind || ws_optreset) { + ws_optreset = 0; + ws_optpos = 0; + ws_optind = 1; + } + + if (ws_optind >= argc || !argv[ws_optind]) + return -1; + + if (argv[ws_optind][0] != '-') { + if (optstring[0] == '-') { + ws_optarg = argv[ws_optind++]; + return 1; + } + return -1; + } + + if (!argv[ws_optind][1]) + return -1; + + if (argv[ws_optind][1] == '-' && !argv[ws_optind][2]) { + ws_optind++; + return -1; + } + + if (!ws_optpos) ws_optpos++; + if ((k = mbtowc(&c, argv[ws_optind]+ws_optpos, MB_LEN_MAX)) < 0) { + k = 1; + c = UNICODE_REPLACEMENT_CHARACTER; /* replacement char */ + } + optchar = argv[ws_optind]+ws_optpos; + ws_optpos += k; + + if (!argv[ws_optind][ws_optpos]) { + ws_optind++; + ws_optpos = 0; + } + + if (optstring[0] == '-' || optstring[0] == '+') + optstring++; + + i = 0; + d = 0; + do { + l = mbtowc(&d, optstring+i, MB_LEN_MAX); + if (l>0) i+=l; else i++; + } while (l && d != c); + + if (d != c || c == ':') { + ws_optopt = c; + if (optstring[0] != ':' && ws_opterr) + __getopt_msg(argv[0], ": unrecognized option: ", optchar, k); + return '?'; + } + if (optstring[i] == ':') { + ws_optarg = 0; + if (optstring[i+1] != ':' || ws_optpos) { + ws_optarg = argv[ws_optind++] + ws_optpos; + ws_optpos = 0; + } + if (ws_optind > argc) { + ws_optopt = c; + if (optstring[0] == ':') return ':'; + if (ws_opterr) __getopt_msg(argv[0], + ": option requires an argument: ", + optchar, k); + return '?'; + } + } + return c; +} + +static int __getopt_long_core(int argc, char *const *argv, const char *optstring, const struct ws_option *longopts, int *idx, int longonly); + +static int __getopt_long(int argc, char *const *argv, const char *optstring, const struct ws_option *longopts, int *idx, int longonly) +{ + int ret, skipped, resumed; + if (!ws_optind || ws_optreset) { + ws_optreset = 0; + ws_optpos = 0; + ws_optind = 1; + } + if (ws_optind >= argc || !argv[ws_optind]) return -1; + skipped = ws_optind; + if (optstring[0] != '+' && optstring[0] != '-') { + int i; + for (i=ws_optind; ; i++) { + if (i >= argc || !argv[i]) return -1; + if (argv[i][0] == '-' && argv[i][1]) break; + } + ws_optind = i; + } + resumed = ws_optind; + ret = __getopt_long_core(argc, argv, optstring, longopts, idx, longonly); + if (resumed > skipped) { + int i, cnt = ws_optind-resumed; + for (i=0; i<cnt; i++) + permute(argv, skipped, ws_optind-1); + ws_optind = skipped + cnt; + } + return ret; +} + +static int __getopt_long_core(int argc, char *const *argv, const char *optstring, const struct ws_option *longopts, int *idx, int longonly) +{ + ws_optarg = 0; + if (longopts && argv[ws_optind][0] == '-' && + ((longonly && argv[ws_optind][1] && argv[ws_optind][1] != '-') || + (argv[ws_optind][1] == '-' && argv[ws_optind][2]))) + { + int colon = optstring[optstring[0]=='+'||optstring[0]=='-']==':'; + int i, cnt, match = -1; + char *arg = NULL, *opt, *start = argv[ws_optind]+1; + for (cnt=i=0; longopts[i].name; i++) { + const char *name = longopts[i].name; + opt = start; + if (*opt == '-') opt++; + while (*opt && *opt != '=' && *opt == *name) { + name++; + opt++; + } + if (*opt && *opt != '=') continue; + arg = opt; + match = i; + if (!*name) { + cnt = 1; + break; + } + cnt++; + } + if (cnt==1 && longonly && arg-start == mblen(start, MB_LEN_MAX)) { + ptrdiff_t l = arg - start; + for (i=0; optstring[i]; i++) { + ptrdiff_t j; + for (j=0; j<l && start[j]==optstring[i+j]; j++); + if (j==l) { + cnt++; + break; + } + } + } + if (cnt==1) { + i = match; + opt = arg; + ws_optind++; + if (*opt == '=') { + if (!longopts[i].has_arg) { + ws_optopt = longopts[i].val; + if (colon || !ws_opterr) + return '?'; + __getopt_msg(argv[0], + ": option does not take an argument: ", + longopts[i].name, + strlen(longopts[i].name)); + return '?'; + } + ws_optarg = opt+1; + } else if (longopts[i].has_arg == ws_required_argument) { + if (!(ws_optarg = argv[ws_optind])) { + ws_optopt = longopts[i].val; + if (colon) return ':'; + if (!ws_opterr) return '?'; + __getopt_msg(argv[0], + ": option requires an argument: ", + longopts[i].name, + strlen(longopts[i].name)); + return '?'; + } + ws_optind++; + } + if (idx) *idx = i; + if (longopts[i].flag) { + *longopts[i].flag = longopts[i].val; + return 0; + } + return longopts[i].val; + } + if (argv[ws_optind][1] == '-') { + ws_optopt = 0; + if (!colon && ws_opterr) + __getopt_msg(argv[0], cnt ? + ": option is ambiguous: " : + ": unrecognized option: ", + argv[ws_optind]+2, + strlen(argv[ws_optind]+2)); + ws_optind++; + return '?'; + } + } + return ws_getopt(argc, argv, optstring); +} + +int ws_getopt_long(int argc, char *const *argv, const char *optstring, const struct ws_option *longopts, int *idx) +{ + return __getopt_long(argc, argv, optstring, longopts, idx, 0); +} + +int ws_getopt_long_only(int argc, char *const *argv, const char *optstring, const struct ws_option *longopts, int *idx) +{ + return __getopt_long(argc, argv, optstring, longopts, idx, 1); +} diff --git a/wsutil/ws_getopt.h b/wsutil/ws_getopt.h new file mode 100644 index 00000000..b32badce --- /dev/null +++ b/wsutil/ws_getopt.h @@ -0,0 +1,60 @@ +/** @file + * + * musl as a whole is licensed under the following standard MIT license: + * + * ---------------------------------------------------------------------- + * Copyright © 2005-2020 Rich Felker, et al. + * + * 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. + * ---------------------------------------------------------------------- + */ + +#ifndef _WS_GETOPT_H_ +#define _WS_GETOPT_H_ + +#include <ws_symbol_export.h> + +#ifdef __cplusplus +extern "C" { +#endif + +WS_DLL_PUBLIC int ws_getopt(int, char * const [], const char *); +WS_DLL_PUBLIC char *ws_optarg; +WS_DLL_PUBLIC int ws_optind, ws_opterr, ws_optopt, ws_optpos, ws_optreset; + +struct ws_option { + const char *name; + int has_arg; + int *flag; + int val; +}; + +WS_DLL_PUBLIC int ws_getopt_long(int, char *const *, const char *, const struct ws_option *, int *); +WS_DLL_PUBLIC int ws_getopt_long_only(int, char *const *, const char *, const struct ws_option *, int *); + +#define ws_no_argument 0 +#define ws_required_argument 1 +#define ws_optional_argument 2 + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wsutil/ws_mempbrk.c b/wsutil/ws_mempbrk.c new file mode 100644 index 00000000..f3f99b71 --- /dev/null +++ b/wsutil/ws_mempbrk.c @@ -0,0 +1,87 @@ +/* ws_mempbrk.c + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" + +/* see bug 10798: there is a bug in the compiler the buildbots use for Mac OSX + and SSE4.2, so we're not going to use SSE4.2 with Mac OSX right now, for + older Mac OSX compilers. + */ +#ifdef __APPLE__ +#if defined(__clang__) && (__clang_major__ >= 6) +/* allow HAVE_SSE4_2 to be used for clang 6.0+ case because we know it works */ +#else +/* don't allow it otherwise, for Mac OSX */ +#undef HAVE_SSE4_2 +#endif +#endif + +#include "ws_mempbrk.h" +#include "ws_mempbrk_int.h" + +#include <string.h> + +void +ws_mempbrk_compile(ws_mempbrk_pattern* pattern, const char *needles) +{ + const char *n = needles; + memset(pattern->patt, 0, 256); + while (*n) { + pattern->patt[(int)*n] = 1; + n++; + } + +#ifdef HAVE_SSE4_2 + ws_mempbrk_sse42_compile(pattern, needles); +#endif +} + + +const uint8_t * +ws_mempbrk_portable_exec(const uint8_t* haystack, size_t haystacklen, const ws_mempbrk_pattern* pattern, unsigned char *found_needle) +{ + const uint8_t *haystack_end = haystack + haystacklen; + + while (haystack < haystack_end) { + if (pattern->patt[*haystack]) { + if (found_needle) + *found_needle = *haystack; + return haystack; + } + haystack++; + } + + return NULL; +} + + +WS_DLL_PUBLIC const uint8_t * +ws_mempbrk_exec(const uint8_t* haystack, size_t haystacklen, const ws_mempbrk_pattern* pattern, unsigned char *found_needle) +{ +#ifdef HAVE_SSE4_2 + if (haystacklen >= 16 && pattern->use_sse42) + return ws_mempbrk_sse42_exec(haystack, haystacklen, pattern, found_needle); +#endif + + return ws_mempbrk_portable_exec(haystack, haystacklen, pattern, found_needle); +} + + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 8 + * tab-width: 8 + * indent-tabs-mode: t + * End: + * + * vi: set shiftwidth=8 tabstop=8 noexpandtab: + * :indentSize=8:tabSize=8:noTabs=false: + */ diff --git a/wsutil/ws_mempbrk.h b/wsutil/ws_mempbrk.h new file mode 100644 index 00000000..7aff505f --- /dev/null +++ b/wsutil/ws_mempbrk.h @@ -0,0 +1,37 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WS_MEMPBRK_H__ +#define __WS_MEMPBRK_H__ + +#include <wireshark.h> + +#ifdef HAVE_SSE4_2 +#include <emmintrin.h> +#endif + +/** The pattern object used for ws_mempbrk_exec(). + */ +typedef struct { + char patt[256]; +#ifdef HAVE_SSE4_2 + bool use_sse42; + __m128i mask; +#endif +} ws_mempbrk_pattern; + +/** Compile the pattern for the needles to find using ws_mempbrk_exec(). + */ +WS_DLL_PUBLIC void ws_mempbrk_compile(ws_mempbrk_pattern* pattern, const char *needles); + +/** Scan for the needles specified by the compiled pattern. + */ +WS_DLL_PUBLIC const uint8_t *ws_mempbrk_exec(const uint8_t* haystack, size_t haystacklen, const ws_mempbrk_pattern* pattern, unsigned char *found_needle); + +#endif /* __WS_MEMPBRK_H__ */ diff --git a/wsutil/ws_mempbrk_int.h b/wsutil/ws_mempbrk_int.h new file mode 100644 index 00000000..223cb64d --- /dev/null +++ b/wsutil/ws_mempbrk_int.h @@ -0,0 +1,20 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WS_MEMPBRK_INT_H__ +#define __WS_MEMPBRK_INT_H__ + +const uint8_t *ws_mempbrk_portable_exec(const uint8_t* haystack, size_t haystacklen, const ws_mempbrk_pattern* pattern, unsigned char *found_needle); + +#ifdef HAVE_SSE4_2 +void ws_mempbrk_sse42_compile(ws_mempbrk_pattern* pattern, const char *needles); +const char *ws_mempbrk_sse42_exec(const char* haystack, size_t haystacklen, const ws_mempbrk_pattern* pattern, unsigned char *found_needle); +#endif + +#endif /* __WS_MEMPBRK_INT_H__ */ diff --git a/wsutil/ws_mempbrk_sse42.c b/wsutil/ws_mempbrk_sse42.c new file mode 100644 index 00000000..9de75aa6 --- /dev/null +++ b/wsutil/ws_mempbrk_sse42.c @@ -0,0 +1,173 @@ +/* strcspn with SSE4.2 intrinsics + Copyright (C) 2009-2014 Free Software Foundation, Inc. + Contributed by Intel Corporation. + This file is part of the GNU C Library. + + SPDX-License-Identifier: LGPL-2.1-or-later +*/ + + +#include "config.h" + +#ifdef HAVE_SSE4_2 + +#include <glib.h> +#include "ws_cpuid.h" + +#ifdef _WIN32 + #include <tmmintrin.h> +#endif + +#include <nmmintrin.h> +#include <string.h> +#include "ws_mempbrk.h" +#include "ws_mempbrk_int.h" + +/* __has_feature(address_sanitizer) is used later for Clang, this is for + * compatibility with other compilers (such as GCC and MSVC) */ +#ifndef __has_feature +# define __has_feature(x) 0 +#endif + +#define cast_128aligned__m128i(p) ((const __m128i *) (const void *) (p)) + +/* Helper for variable shifts of SSE registers. + Copyright (C) 2010 Free Software Foundation, Inc. + */ + +static const int8_t ___m128i_shift_right[31] = + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 + }; + +static inline __m128i +__m128i_shift_right (__m128i value, unsigned long int offset) +{ + /* _mm_loadu_si128() works with unaligned data, cast safe */ + return _mm_shuffle_epi8 (value, + _mm_loadu_si128 (cast_128aligned__m128i(___m128i_shift_right + offset))); +} + + +void +ws_mempbrk_sse42_compile(ws_mempbrk_pattern* pattern, const char *needles) +{ + size_t length = strlen(needles); + + pattern->use_sse42 = ws_cpuid_sse42() && (length <= 16); + + if (pattern->use_sse42) { + pattern->mask = _mm_setzero_si128(); + memcpy(&(pattern->mask), needles, length); + } +} + +/* We use 0x2: + _SIDD_SBYTE_OPS + | _SIDD_CMP_EQUAL_ANY + | _SIDD_POSITIVE_POLARITY + | _SIDD_LEAST_SIGNIFICANT + on pcmpistri to compare xmm/mem128 + + 0 1 2 3 4 5 6 7 8 9 A B C D E F + X X X X X X X X X X X X X X X X + + against xmm + + 0 1 2 3 4 5 6 7 8 9 A B C D E F + A A A A A A A A A A A A A A A A + + to find out if the first 16byte data element has any byte A and + the offset of the first byte. There are 3 cases: + + 1. The first 16byte data element has the byte A at the offset X. + 2. The first 16byte data element has EOS and doesn't have the byte A. + 3. The first 16byte data element is valid and doesn't have the byte A. + + Here is the table of ECX, CFlag, ZFlag and SFlag for 2 cases: + + 1 X 1 0/1 0 + 2 16 0 1 0 + 3 16 0 0 0 + + We exit from the loop for cases 1 and 2 with jbe which branches + when either CFlag or ZFlag is 1. If CFlag == 1, ECX has the offset + X for case 1. */ + +const char * +ws_mempbrk_sse42_exec(const char *haystack, size_t haystacklen, const ws_mempbrk_pattern* pattern, unsigned char *found_needle) +{ + const char *aligned; + int offset; + + offset = (int) ((size_t) haystack & 15); + aligned = (const char *) ((size_t) haystack & -16L); + if (offset != 0) + { + /* Check partial string. cast safe it's 16B aligned */ + __m128i value = __m128i_shift_right (_mm_load_si128 (cast_128aligned__m128i(aligned)), offset); + + int length = _mm_cmpistri (pattern->mask, value, 0x2); + /* No need to check ZFlag since ZFlag is always 1. */ + int cflag = _mm_cmpistrc (pattern->mask, value, 0x2); + /* XXX: why does this compare value with value? */ + int idx = _mm_cmpistri (value, value, 0x3a); + + if (cflag) { + if (found_needle) + *found_needle = *(haystack + length); + return haystack + length; + } + + /* Find where the NULL terminator is. */ + if (idx < 16 - offset) + { + /* found NUL @ 'idx', need to switch to slower mempbrk */ + return ws_mempbrk_portable_exec(haystack + idx + 1, haystacklen - idx - 1, pattern, found_needle); /* haystacklen is bigger than 16 & idx < 16 so no underflow here */ + } + aligned += 16; + haystacklen -= (16 - offset); + } + else + aligned = haystack; + + while (haystacklen >= 16) + { + __m128i value = _mm_load_si128 (cast_128aligned__m128i(aligned)); + int idx = _mm_cmpistri (pattern->mask, value, 0x2); + int cflag = _mm_cmpistrc (pattern->mask, value, 0x2); + int zflag = _mm_cmpistrz (pattern->mask, value, 0x2); + + if (cflag) { + if (found_needle) + *found_needle = *(aligned + idx); + return aligned + idx; + } + + if (zflag) + { + /* found NUL, need to switch to slower mempbrk */ + return ws_mempbrk_portable_exec(aligned, haystacklen, pattern, found_needle); + } + aligned += 16; + haystacklen -= 16; + } + + /* XXX, use mempbrk_slow here? */ + return ws_mempbrk_portable_exec(aligned, haystacklen, pattern, found_needle); +} + +#endif /* HAVE_SSE4_2 */ +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 2 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=2 tabstop=8 expandtab: + * :indentSize=2:tabSize=8:noTabs=true: + */ diff --git a/wsutil/ws_pipe.c b/wsutil/ws_pipe.c new file mode 100644 index 00000000..7689f933 --- /dev/null +++ b/wsutil/ws_pipe.c @@ -0,0 +1,856 @@ +/* ws_pipe.c + * + * Routines for handling pipes. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <config.h> +#define WS_LOG_DOMAIN LOG_DOMAIN_CAPTURE +#include "wsutil/ws_pipe.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#ifdef _WIN32 +#include <windows.h> +#include <io.h> +#include <fcntl.h> /* for _O_BINARY */ +#include <wsutil/win32-utils.h> +#else +#include <unistd.h> +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif +#endif + +#ifdef __linux__ +#define HAS_G_SPAWN_LINUX_THREAD_SAFETY_BUG +#include <fcntl.h> +#include <sys/syscall.h> /* for syscall and SYS_getdents64 */ +#include <wsutil/file_util.h> /* for ws_open -> open to pacify checkAPIs.pl */ +#endif + +#include "wsutil/filesystem.h" +#include "wsutil/wslog.h" + +#ifdef HAS_G_SPAWN_LINUX_THREAD_SAFETY_BUG +struct linux_dirent64 { + uint64_t d_ino; /* 64-bit inode number */ + uint64_t d_off; /* 64-bit offset to next structure */ + unsigned short d_reclen; /* Size of this dirent */ + unsigned char d_type; /* File type */ + char d_name[]; /* Filename (null-terminated) */ +}; + +/* Async-signal-safe string to integer conversion. */ +static int +filename_to_fd(const char *p) +{ + char c; + int fd = 0; + const int cutoff = INT_MAX / 10; + const int cutlim = INT_MAX % 10; + + if (*p == '\0') + return -1; + + while ((c = *p++) != '\0') { + if (!g_ascii_isdigit(c)) + return -1; + c -= '0'; + + /* Check for overflow. */ + if (fd > cutoff || (fd == cutoff && c > cutlim)) + return -1; + + fd = fd * 10 + c; + } + + return fd; +} + +static void +close_non_standard_fds_linux(void * user_data _U_) +{ + /* + * GLib 2.14.2 and newer (up to at least GLib 2.58.1) on Linux with multiple + * threads can deadlock in the child process due to use of opendir (which + * is not async-signal-safe). To avoid this, disable the broken code path + * and manually close file descriptors using async-signal-safe code only. + * Use CLOEXEC to allow reporting of execve errors to the parent via a pipe. + * https://gitlab.gnome.org/GNOME/glib/issues/1014 + * https://gitlab.gnome.org/GNOME/glib/merge_requests/490 + */ + int dir_fd = ws_open("/proc/self/fd", O_RDONLY | O_DIRECTORY); + if (dir_fd >= 0) { + char buf[4096]; + int nread, fd; + struct linux_dirent64 *de; + + while ((nread = (int) syscall(SYS_getdents64, dir_fd, buf, sizeof(buf))) > 0) { + for (int pos = 0; pos < nread; pos += de->d_reclen) { + de = (struct linux_dirent64 *)(buf + pos); + fd = filename_to_fd(de->d_name); + if (fd > STDERR_FILENO && fd != dir_fd) { + /* Close all other (valid) file descriptors above stderr. */ + fcntl(fd, F_SETFD, FD_CLOEXEC); + } + } + } + + close(dir_fd); + } else { + /* Slow fallback in case /proc is not mounted */ + for (int fd = STDERR_FILENO + 1; fd < getdtablesize(); fd++) { + fcntl(fd, F_SETFD, FD_CLOEXEC); + } + } +} +#endif + +#ifdef _WIN32 +static ULONG pipe_serial_number; + +/* Alternative for CreatePipe() where read handle is opened with FILE_FLAG_OVERLAPPED */ +static bool +ws_pipe_create_overlapped_read(HANDLE *read_pipe_handle, HANDLE *write_pipe_handle, + SECURITY_ATTRIBUTES *sa, DWORD suggested_buffer_size) +{ + HANDLE read_pipe, write_pipe; + unsigned char *name = ws_strdup_printf("\\\\.\\Pipe\\WiresharkWsPipe.%08lx.%08lx", + GetCurrentProcessId(), + InterlockedIncrement(&pipe_serial_number)); + gunichar2 *wname = g_utf8_to_utf16(name, -1, NULL, NULL, NULL); + + g_free(name); + + read_pipe = CreateNamedPipe(wname, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, + PIPE_TYPE_BYTE | PIPE_WAIT, 1, + suggested_buffer_size, suggested_buffer_size, + 0, sa); + if (INVALID_HANDLE_VALUE == read_pipe) + { + g_free(wname); + return false; + } + + write_pipe = CreateFile(wname, GENERIC_WRITE, 0, sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (INVALID_HANDLE_VALUE == write_pipe) + { + DWORD error = GetLastError(); + CloseHandle(read_pipe); + SetLastError(error); + g_free(wname); + return false; + } + + *read_pipe_handle = read_pipe; + *write_pipe_handle = write_pipe; + g_free(wname); + return(true); +} +#endif + +/** + * Helper to convert a command and argument list to an NULL-terminated 'argv' + * array, suitable for g_spawn_sync and friends. Free with g_strfreev. + */ +static char ** +convert_to_argv(const char *command, int args_count, char *const *args) +{ + char **argv = g_new(char *, args_count + 2); + // The caller does not seem to modify this, but g_spawn_sync uses 'char **' + // as opposed to 'const char **', so just to be sure clone it. + argv[0] = g_strdup(command); + for (int i = 0; i < args_count; i++) { + // Empty arguments may indicate a bug in Wireshark. Extcap for example + // omits arguments when their string value is empty. On Windows, empty + // arguments would silently be ignored because protect_arg returns an + // empty string, therefore we print a warning here. + if (!*args[i]) { + ws_warning("Empty argument %d in arguments list", i); + } + argv[1 + i] = g_strdup(args[i]); + } + argv[args_count + 1] = NULL; + return argv; +} + +/** + * Convert a non-empty NULL-terminated array of command and arguments to a + * string for displaying purposes. On Windows, the returned string is properly + * escaped and can be executed directly. + */ +static char * +convert_to_command_line(char **argv) +{ + GString *command_line = g_string_sized_new(200); +#ifdef _WIN32 + // The first argument must always be quoted even if it does not contain + // special characters or else CreateProcess might consider arguments as part + // of the executable. + char *quoted_arg = protect_arg(argv[0]); + if (quoted_arg[0] != '"') { + g_string_append_c(command_line, '"'); + g_string_append(command_line, quoted_arg); + g_string_append_c(command_line, '"'); + } else { + g_string_append(command_line, quoted_arg); + } + g_free(quoted_arg); + + for (int i = 1; argv[i]; i++) { + quoted_arg = protect_arg(argv[i]); + g_string_append_c(command_line, ' '); + g_string_append(command_line, quoted_arg); + g_free(quoted_arg); + } +#else + for (int i = 0; argv[i]; i++) { + char *quoted_arg = g_shell_quote(argv[i]); + if (i != 0) { + g_string_append_c(command_line, ' '); + } + g_string_append(command_line, quoted_arg); + g_free(quoted_arg); + } +#endif + return g_string_free(command_line, false); +} + +bool ws_pipe_spawn_sync(const char *working_directory, const char *command, int argc, char **args, char **command_output) +{ + bool status = false; + bool result = false; + char *local_output = NULL; +#ifdef _WIN32 + +#define BUFFER_SIZE 16384 + + STARTUPINFO info; + PROCESS_INFORMATION processInfo; + + SECURITY_ATTRIBUTES sa; + HANDLE child_stdout_rd = NULL; + HANDLE child_stdout_wr = NULL; + HANDLE child_stderr_rd = NULL; + HANDLE child_stderr_wr = NULL; + HANDLE inherit_handles[2]; + + OVERLAPPED stdout_overlapped; + OVERLAPPED stderr_overlapped; +#else + int exit_status = 0; +#endif + + char **argv = convert_to_argv(command, argc, args); + char *command_line = convert_to_command_line(argv); + + ws_debug("command line: %s", command_line); + + uint64_t start_time = g_get_monotonic_time(); + +#ifdef _WIN32 + /* Setup overlapped structures. Create Manual Reset events, initially not signalled */ + memset(&stdout_overlapped, 0, sizeof(OVERLAPPED)); + memset(&stderr_overlapped, 0, sizeof(OVERLAPPED)); + stdout_overlapped.hEvent = CreateEvent(NULL, true, false, NULL); + if (!stdout_overlapped.hEvent) + { + g_free(command_line); + g_strfreev(argv); + ws_debug("Could not create stdout overlapped event"); + return false; + } + stderr_overlapped.hEvent = CreateEvent(NULL, true, false, NULL); + if (!stderr_overlapped.hEvent) + { + CloseHandle(stdout_overlapped.hEvent); + g_free(command_line); + g_strfreev(argv); + ws_debug("Could not create stderr overlapped event"); + return false; + } + + memset(&sa, 0, sizeof(SECURITY_ATTRIBUTES)); + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.bInheritHandle = false; + sa.lpSecurityDescriptor = NULL; + + if (!ws_pipe_create_overlapped_read(&child_stdout_rd, &child_stdout_wr, &sa, 0)) + { + CloseHandle(stdout_overlapped.hEvent); + CloseHandle(stderr_overlapped.hEvent); + g_free(command_line); + g_strfreev(argv); + ws_debug("Could not create stdout handle"); + return false; + } + + if (!ws_pipe_create_overlapped_read(&child_stderr_rd, &child_stderr_wr, &sa, 0)) + { + CloseHandle(stdout_overlapped.hEvent); + CloseHandle(stderr_overlapped.hEvent); + CloseHandle(child_stdout_rd); + CloseHandle(child_stdout_wr); + g_free(command_line); + g_strfreev(argv); + ws_debug("Could not create stderr handle"); + return false; + } + + inherit_handles[0] = child_stderr_wr; + inherit_handles[1] = child_stdout_wr; + + memset(&processInfo, 0, sizeof(PROCESS_INFORMATION)); + memset(&info, 0, sizeof(STARTUPINFO)); + + info.cb = sizeof(STARTUPINFO); + info.hStdError = child_stderr_wr; + info.hStdOutput = child_stdout_wr; + info.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; + info.wShowWindow = SW_HIDE; + + if (win32_create_process(NULL, command_line, NULL, NULL, G_N_ELEMENTS(inherit_handles), inherit_handles, + CREATE_NEW_CONSOLE, NULL, working_directory, &info, &processInfo)) + { + char* stdout_buffer = (char*)g_malloc(BUFFER_SIZE); + char* stderr_buffer = (char*)g_malloc(BUFFER_SIZE); + DWORD dw; + DWORD bytes_read; + GString *output_string = g_string_new(NULL); + bool process_finished = false; + bool pending_stdout = true; + bool pending_stderr = true; + + /* Start asynchronous reads from child process stdout and stderr */ + if (!ReadFile(child_stdout_rd, stdout_buffer, BUFFER_SIZE, NULL, &stdout_overlapped)) + { + if (GetLastError() != ERROR_IO_PENDING) + { + ws_debug("ReadFile on child stdout pipe failed. Error %ld", GetLastError()); + pending_stdout = false; + } + } + + if (!ReadFile(child_stderr_rd, stderr_buffer, BUFFER_SIZE, NULL, &stderr_overlapped)) + { + if (GetLastError() != ERROR_IO_PENDING) + { + ws_debug("ReadFile on child stderr pipe failed. Error %ld", GetLastError()); + pending_stderr = false; + } + } + + for (;;) + { + HANDLE handles[3]; + DWORD n_handles = 0; + if (!process_finished) + { + handles[n_handles++] = processInfo.hProcess; + } + if (pending_stdout) + { + handles[n_handles++] = stdout_overlapped.hEvent; + } + if (pending_stderr) + { + handles[n_handles++] = stderr_overlapped.hEvent; + } + + if (!n_handles) + { + /* No more things to wait */ + break; + } + + dw = WaitForMultipleObjects(n_handles, handles, false, INFINITE); + if (dw < (WAIT_OBJECT_0 + n_handles)) + { + int i = dw - WAIT_OBJECT_0; + if (handles[i] == processInfo.hProcess) + { + /* Process finished but there might still be unread data in the pipe. + * Close the write pipes, so ReadFile does not wait indefinitely. + */ + CloseHandle(child_stdout_wr); + CloseHandle(child_stderr_wr); + process_finished = true; + } + else if (handles[i] == stdout_overlapped.hEvent) + { + bytes_read = 0; + if (!GetOverlappedResult(child_stdout_rd, &stdout_overlapped, &bytes_read, true)) + { + if (GetLastError() == ERROR_BROKEN_PIPE) + { + pending_stdout = false; + continue; + } + ws_debug("GetOverlappedResult on stdout failed. Error %ld", GetLastError()); + } + if (process_finished && (bytes_read == 0)) + { + /* We have drained the pipe and there isn't any process that holds active write handle to the pipe. */ + pending_stdout = false; + continue; + } + g_string_append_len(output_string, stdout_buffer, bytes_read); + if (!ReadFile(child_stdout_rd, stdout_buffer, BUFFER_SIZE, NULL, &stdout_overlapped)) + { + if (GetLastError() != ERROR_IO_PENDING) + { + ws_debug("ReadFile on child stdout pipe failed. Error %ld", GetLastError()); + pending_stdout = false; + } + } + } + else if (handles[i] == stderr_overlapped.hEvent) + { + /* Discard the stderr data just like non-windows version of this function does. */ + bytes_read = 0; + if (!GetOverlappedResult(child_stderr_rd, &stderr_overlapped, &bytes_read, true)) + { + if (GetLastError() == ERROR_BROKEN_PIPE) + { + pending_stderr = false; + continue; + } + ws_debug("GetOverlappedResult on stderr failed. Error %ld", GetLastError()); + } + if (process_finished && (bytes_read == 0)) + { + pending_stderr = false; + continue; + } + if (!ReadFile(child_stderr_rd, stderr_buffer, BUFFER_SIZE, NULL, &stderr_overlapped)) + { + if (GetLastError() != ERROR_IO_PENDING) + { + ws_debug("ReadFile on child stderr pipe failed. Error %ld", GetLastError()); + pending_stderr = false; + } + } + } + } + else + { + ws_debug("WaitForMultipleObjects returned 0x%08lX. Error %ld", dw, GetLastError()); + } + } + + g_free(stdout_buffer); + g_free(stderr_buffer); + + status = GetExitCodeProcess(processInfo.hProcess, &dw); + if (status && dw != 0) + { + status = false; + } + + local_output = g_string_free(output_string, false); + + CloseHandle(child_stdout_rd); + CloseHandle(child_stderr_rd); + + CloseHandle(processInfo.hProcess); + CloseHandle(processInfo.hThread); + } + else + { + status = false; + + CloseHandle(child_stdout_rd); + CloseHandle(child_stdout_wr); + CloseHandle(child_stderr_rd); + CloseHandle(child_stderr_wr); + } + + CloseHandle(stdout_overlapped.hEvent); + CloseHandle(stderr_overlapped.hEvent); +#else + + GSpawnFlags flags = (GSpawnFlags)0; + GSpawnChildSetupFunc child_setup = NULL; +#ifdef HAS_G_SPAWN_LINUX_THREAD_SAFETY_BUG + flags = (GSpawnFlags)(flags | G_SPAWN_LEAVE_DESCRIPTORS_OPEN); + child_setup = close_non_standard_fds_linux; +#endif + status = g_spawn_sync(working_directory, argv, NULL, + flags, child_setup, NULL, &local_output, NULL, &exit_status, NULL); + + if (status && exit_status != 0) + status = false; +#endif + + ws_debug("%s finished in %.3fms", argv[0], (g_get_monotonic_time() - start_time) / 1000.0); + + if (status) + { + if (local_output != NULL) { + ws_noisy("spawn output: %s", local_output); + if (command_output != NULL) + *command_output = g_strdup(local_output); + } + result = true; + } + + g_free(local_output); + g_free(command_line); + g_strfreev(argv); + + return result; +} + +void ws_pipe_init(ws_pipe_t *ws_pipe) +{ + if (!ws_pipe) return; + memset(ws_pipe, 0, sizeof(ws_pipe_t)); + ws_pipe->pid = WS_INVALID_PID; +} + +GPid ws_pipe_spawn_async(ws_pipe_t *ws_pipe, GPtrArray *args) +{ + GPid pid = WS_INVALID_PID; + int stdin_fd, stdout_fd, stderr_fd; +#ifdef _WIN32 + STARTUPINFO info; + PROCESS_INFORMATION processInfo; + + SECURITY_ATTRIBUTES sa; + HANDLE child_stdin_rd = NULL; + HANDLE child_stdin_wr = NULL; + HANDLE child_stdout_rd = NULL; + HANDLE child_stdout_wr = NULL; + HANDLE child_stderr_rd = NULL; + HANDLE child_stderr_wr = NULL; + HANDLE inherit_handles[3]; +#endif + + // XXX harmonize handling of command arguments for the sync/async functions + // and make them const? This array ends with a trailing NULL by the way. + char **args_array = (char **)args->pdata; + char **argv = convert_to_argv(args_array[0], args->len - 2, args_array + 1); + char *command_line = convert_to_command_line(argv); + + ws_debug("command line: %s", command_line); + +#ifdef _WIN32 + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.bInheritHandle = false; + sa.lpSecurityDescriptor = NULL; + + if (!CreatePipe(&child_stdin_rd, &child_stdin_wr, &sa, 0)) + { + g_free(command_line); + g_strfreev(argv); + ws_debug("Could not create stdin handle"); + return WS_INVALID_PID; + } + + if (!CreatePipe(&child_stdout_rd, &child_stdout_wr, &sa, 0)) + { + CloseHandle(child_stdin_rd); + CloseHandle(child_stdin_wr); + g_free(command_line); + g_strfreev(argv); + ws_debug("Could not create stdout handle"); + return WS_INVALID_PID; + } + + if (!CreatePipe(&child_stderr_rd, &child_stderr_wr, &sa, 0)) + { + CloseHandle(child_stdin_rd); + CloseHandle(child_stdin_wr); + CloseHandle(child_stdout_rd); + CloseHandle(child_stdout_wr); + g_free(command_line); + g_strfreev(argv); + ws_debug("Could not create stderr handle"); + return WS_INVALID_PID; + } + + inherit_handles[0] = child_stdin_rd; + inherit_handles[1] = child_stderr_wr; + inherit_handles[2] = child_stdout_wr; + + memset(&processInfo, 0, sizeof(PROCESS_INFORMATION)); + memset(&info, 0, sizeof(STARTUPINFO)); + + info.cb = sizeof(STARTUPINFO); + info.hStdInput = child_stdin_rd; + info.hStdError = child_stderr_wr; + info.hStdOutput = child_stdout_wr; + info.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; + info.wShowWindow = SW_HIDE; + + if (win32_create_process(NULL, command_line, NULL, NULL, G_N_ELEMENTS(inherit_handles), inherit_handles, + CREATE_NEW_CONSOLE, NULL, NULL, &info, &processInfo)) + { + stdin_fd = _open_osfhandle((intptr_t)(child_stdin_wr), _O_BINARY); + stdout_fd = _open_osfhandle((intptr_t)(child_stdout_rd), _O_BINARY); + stderr_fd = _open_osfhandle((intptr_t)(child_stderr_rd), _O_BINARY); + pid = processInfo.hProcess; + CloseHandle(processInfo.hThread); + } + else + { + CloseHandle(child_stdin_wr); + CloseHandle(child_stdout_rd); + CloseHandle(child_stderr_rd); + } + + /* We no longer need other (child) end of pipes. The child process holds + * its own handles that will be closed on process exit. However, we have + * to close *our* handles as otherwise read() on stdout_fd and stderr_fd + * will block indefinitely after the process exits. + */ + CloseHandle(child_stdin_rd); + CloseHandle(child_stdout_wr); + CloseHandle(child_stderr_wr); +#else + + GError *error = NULL; + GSpawnFlags flags = G_SPAWN_DO_NOT_REAP_CHILD; + GSpawnChildSetupFunc child_setup = NULL; +#ifdef HAS_G_SPAWN_LINUX_THREAD_SAFETY_BUG + flags = (GSpawnFlags)(flags | G_SPAWN_LEAVE_DESCRIPTORS_OPEN); + child_setup = close_non_standard_fds_linux; +#endif + bool spawned = g_spawn_async_with_pipes(NULL, argv, NULL, + flags, child_setup, NULL, + &pid, &stdin_fd, &stdout_fd, &stderr_fd, &error); + if (!spawned) { + ws_debug("Error creating async pipe: %s", error->message); + g_free(error->message); + } +#endif + + g_free(command_line); + g_strfreev(argv); + + ws_pipe->pid = pid; + + if (pid != WS_INVALID_PID) { +#ifdef _WIN32 + ws_pipe->stdin_io = g_io_channel_win32_new_fd(stdin_fd); + ws_pipe->stdout_io = g_io_channel_win32_new_fd(stdout_fd); + ws_pipe->stderr_io = g_io_channel_win32_new_fd(stderr_fd); +#else + ws_pipe->stdin_io = g_io_channel_unix_new(stdin_fd); + ws_pipe->stdout_io = g_io_channel_unix_new(stdout_fd); + ws_pipe->stderr_io = g_io_channel_unix_new(stderr_fd); +#endif + g_io_channel_set_encoding(ws_pipe->stdin_io, NULL, NULL); + g_io_channel_set_encoding(ws_pipe->stdout_io, NULL, NULL); + g_io_channel_set_encoding(ws_pipe->stderr_io, NULL, NULL); + g_io_channel_set_buffered(ws_pipe->stdin_io, false); + g_io_channel_set_buffered(ws_pipe->stdout_io, false); + g_io_channel_set_buffered(ws_pipe->stderr_io, false); + g_io_channel_set_close_on_unref(ws_pipe->stdin_io, true); + g_io_channel_set_close_on_unref(ws_pipe->stdout_io, true); + g_io_channel_set_close_on_unref(ws_pipe->stderr_io, true); + } + + return pid; +} + +#ifdef _WIN32 + +typedef struct +{ + HANDLE pipeHandle; + OVERLAPPED ol; + BOOL pendingIO; +} PIPEINTS; + +bool +ws_pipe_wait_for_pipe(HANDLE * pipe_handles, int num_pipe_handles, HANDLE pid) +{ + PIPEINTS pipeinsts[3]; + HANDLE handles[4]; + bool result = true; + + SecureZeroMemory(pipeinsts, sizeof(pipeinsts)); + + if (num_pipe_handles == 0 || num_pipe_handles > 3) + { + ws_debug("Invalid number of pipes given as argument."); + return false; + } + + for (int i = 0; i < num_pipe_handles; ++i) + { + pipeinsts[i].ol.hEvent = CreateEvent(NULL, true, false, NULL); + if (!pipeinsts[i].ol.hEvent) + { + ws_debug("Could not create overlapped event"); + for (int j = 0; j < i; j++) + { + CloseHandle(pipeinsts[j].ol.hEvent); + } + return false; + } + } + + for (int i = 0; i < num_pipe_handles; ++i) + { + pipeinsts[i].pipeHandle = pipe_handles[i]; + pipeinsts[i].ol.Pointer = 0; + pipeinsts[i].pendingIO = false; + if (!ConnectNamedPipe(pipeinsts[i].pipeHandle, &pipeinsts[i].ol)) + { + DWORD error = GetLastError(); + switch (error) + { + case ERROR_IO_PENDING: + pipeinsts[i].pendingIO = true; + break; + + case ERROR_PIPE_CONNECTED: + SetEvent(pipeinsts[i].ol.hEvent); + break; + + default: + ws_debug("ConnectNamedPipe failed with %ld\n.", error); + result = false; + } + } + } + + while (result) + { + DWORD dw; + int num_handles = 0; + for (int i = 0; i < num_pipe_handles; ++i) + { + if (pipeinsts[i].pendingIO) + { + handles[num_handles] = pipeinsts[i].ol.hEvent; + num_handles++; + } + } + if (num_handles == 0) + { + /* All pipes have been successfully connected */ + break; + } + /* Wait for process in case it exits before the pipes have connected */ + handles[num_handles] = pid; + num_handles++; + + dw = WaitForMultipleObjects(num_handles, handles, false, 30000); + int handle_idx = dw - WAIT_OBJECT_0; + if (dw == WAIT_TIMEOUT) + { + ws_debug("extcap didn't connect to pipe within 30 seconds."); + result = false; + break; + } + // If index points to our handles array + else if (handle_idx >= 0 && handle_idx < num_handles) + { + if (handles[handle_idx] == pid) + { + ws_debug("extcap terminated without connecting to pipe."); + result = false; + } + for (int i = 0; i < num_pipe_handles; ++i) + { + if (handles[handle_idx] == pipeinsts[i].ol.hEvent) + { + DWORD cbRet; + BOOL success = GetOverlappedResult( + pipeinsts[i].pipeHandle, // handle to pipe + &pipeinsts[i].ol, // OVERLAPPED structure + &cbRet, // bytes transferred + true); // wait + if (!success) + { + ws_debug("Error %ld \n.", GetLastError()); + result = false; + } + pipeinsts[i].pendingIO = false; + } + } + } + else + { + ws_debug("WaitForMultipleObjects returned 0x%08lX. Error %ld", dw, GetLastError()); + result = false; + } + } + + for (int i = 0; i < num_pipe_handles; ++i) + { + if (pipeinsts[i].pendingIO) + { + CancelIoEx(pipeinsts[i].pipeHandle, &pipeinsts[i].ol); + WaitForSingleObject(pipeinsts[i].ol.hEvent, INFINITE); + } + CloseHandle(pipeinsts[i].ol.hEvent); + } + + return result; +} +#endif + +bool +ws_pipe_data_available(int pipe_fd) +{ +#ifdef _WIN32 /* PeekNamedPipe */ + HANDLE hPipe = (HANDLE) _get_osfhandle(pipe_fd); + DWORD bytes_avail; + + if (hPipe == INVALID_HANDLE_VALUE) + { + return false; + } + + if (! PeekNamedPipe(hPipe, NULL, 0, NULL, &bytes_avail, NULL)) + { + return false; + } + + if (bytes_avail > 0) + { + return true; + } + return false; +#else /* select */ + fd_set rfds; + struct timeval timeout; + + FD_ZERO(&rfds); + FD_SET(pipe_fd, &rfds); + timeout.tv_sec = 0; + timeout.tv_usec = 0; + + if (select(pipe_fd + 1, &rfds, NULL, NULL, &timeout) > 0) + { + return true; + } + + return false; +#endif +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/ws_pipe.h b/wsutil/ws_pipe.h new file mode 100644 index 00000000..968e0579 --- /dev/null +++ b/wsutil/ws_pipe.h @@ -0,0 +1,105 @@ +/** @file + * + * Routines for handling pipes. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WS_PIPE_H__ +#define __WS_PIPE_H__ + +#include <stdbool.h> + +// ws_symbol_export and WS_INVALID_PID +#include "wsutil/processes.h" + +#include <glib.h> + +#ifdef _WIN32 +#include <windows.h> +#include <io.h> +#define ws_pipe_handle HANDLE +#define ws_get_pipe_handle(pipe_fd) ((HANDLE)_get_osfhandle(pipe_fd)) +#else +#define ws_pipe_handle int +#define ws_get_pipe_handle(pipe_fd) (pipe_fd) +#endif + +typedef struct _ws_pipe_t { + GPid pid; + GIOChannel *stdin_io; + GIOChannel *stdout_io; + GIOChannel *stderr_io; +} ws_pipe_t; + +/** + * @brief Run a process using g_spawn_sync on UNIX and Linux, and + * CreateProcess on Windows. Wait for it to finish. + * @param [IN] working_directory Initial working directory. + * @param [IN] command Command to run. + * @param [IN] argc Number of arguments for the command, not including the command itself. + * @param [IN] args Arguments for the command, not including the command itself. + * The last element must be NULL. + * @param [OUT] command_output If not NULL, receives a copy of the command output. Must be g_freed. + * @return true on success or false on failure. + */ +WS_DLL_PUBLIC bool ws_pipe_spawn_sync(const char * working_directory, const char * command, int argc, char ** args, char ** command_output); + +/** + * @brief Initialize a ws_pipe_t struct. Sets .pid to WS_INVALID_PID and all other members to 0 or NULL. + * @param ws_pipe [IN] The pipe to initialize. + */ +WS_DLL_PUBLIC void ws_pipe_init(ws_pipe_t *ws_pipe); + +/** + * @brief Checks whether a pipe is valid (for reading or writing). + */ +static inline bool ws_pipe_valid(ws_pipe_t *ws_pipe) +{ + return ws_pipe && ws_pipe->pid && ws_pipe->pid != WS_INVALID_PID; +} + +/** + * @brief Start a process using g_spawn_sync on UNIX and Linux, and CreateProcess on Windows. + * @param ws_pipe The process PID, stdio descriptors, etc. + * @param args The command to run along with its arguments. + * @return A valid PID on success, otherwise WS_INVALID_PID. + */ +WS_DLL_PUBLIC GPid ws_pipe_spawn_async (ws_pipe_t * ws_pipe, GPtrArray * args ); + +#ifdef _WIN32 +/** + * @brief Wait for a set of handles using WaitForMultipleObjects. Windows only. + * @param pipe_handles An array of handles + * @param num_pipe_handles The size of the array. + * @param pid Child process PID. + * @return true on success or false on failure. + */ +WS_DLL_PUBLIC bool ws_pipe_wait_for_pipe(HANDLE * pipe_handles, int num_pipe_handles, HANDLE pid); +#endif + +/** + * @brief Check to see if a file descriptor has data available. + * @param pipe_fd File descriptor. + * @return true if data is available or false otherwise. + */ +WS_DLL_PUBLIC bool ws_pipe_data_available(int pipe_fd); + +#endif /* __WS_PIPE_H__ */ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/ws_roundup.h b/wsutil/ws_roundup.h new file mode 100644 index 00000000..f7177a8a --- /dev/null +++ b/wsutil/ws_roundup.h @@ -0,0 +1,22 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WS_ROUNDUP_H__ +#define __WS_ROUNDUP_H__ + +/* + * Round up to various powers of 2. + */ +#define WS_ROUNDUP_2(n) (((n) + ((unsigned)(2U-1U))) & (~((unsigned)(2U-1U)))) +#define WS_ROUNDUP_4(n) (((n) + ((unsigned)(4U-1U))) & (~((unsigned)(4U-1U)))) +#define WS_ROUNDUP_8(n) (((n) + ((unsigned)(8U-1U))) & (~((unsigned)(8U-1U)))) +#define WS_ROUNDUP_16(n) (((n) + ((unsigned)(16U-1U))) & (~((unsigned)(16U-1U)))) +#define WS_ROUNDUP_32(n) (((n) + ((unsigned)(32U-1U))) & (~((unsigned)(32U-1U)))) + +#endif /* __WS_ROUNDUP_H__ */ diff --git a/wsutil/ws_strptime.c b/wsutil/ws_strptime.c new file mode 100644 index 00000000..3c55d93e --- /dev/null +++ b/wsutil/ws_strptime.c @@ -0,0 +1,906 @@ +/*- + * Copyright (c) 1997, 1998, 2005, 2008 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code was contributed to The NetBSD Foundation by Klaus Klein. + * Heavily optimised by David Laight + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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. + */ +#define _GNU_SOURCE +#include "config.h" +#include "ws_strptime.h" +#include <time.h> +#include <wsutil/time_util.h> /* For ws_localtime_r() */ +#include <wsutil/strtoi.h> + +#ifdef _WIN32 +#define tzset _tzset +#define tzname _tzname +#define timezone _timezone +#define daylight _daylight +#endif + +static const unsigned char *conv_num(const unsigned char *, int *, unsigned, unsigned); +static const unsigned char *find_string(const unsigned char *, int *, const char * const *, + const char * const *, int); + +#define SECSPERMIN 60 +#define MINSPERHOUR 60 +#define HOURSPERDAY 24 +#define DAYSPERWEEK 7 +#define DAYSPERNYEAR 365 +#define DAYSPERLYEAR 366 +#define SECSPERHOUR (SECSPERMIN * MINSPERHOUR) +#define SECSPERDAY ((int_fast32_t) SECSPERHOUR * HOURSPERDAY) +#define MONSPERYEAR 12 + +#define TM_SUNDAY 0 +#define TM_MONDAY 1 +#define TM_TUESDAY 2 +#define TM_WEDNESDAY 3 +#define TM_THURSDAY 4 +#define TM_FRIDAY 5 +#define TM_SATURDAY 6 + +#define TM_JANUARY 0 +#define TM_FEBRUARY 1 +#define TM_MARCH 2 +#define TM_APRIL 3 +#define TM_MAY 4 +#define TM_JUNE 5 +#define TM_JULY 6 +#define TM_AUGUST 7 +#define TM_SEPTEMBER 8 +#define TM_OCTOBER 9 +#define TM_NOVEMBER 10 +#define TM_DECEMBER 11 + +#define TM_YEAR_BASE 1900 + +#define EPOCH_YEAR 1970 +#define EPOCH_WDAY TM_THURSDAY + +#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0)) + +/* +** Since everything in isleap is modulo 400 (or a factor of 400), we know that +** isleap(y) == isleap(y % 400) +** and so +** isleap(a + b) == isleap((a + b) % 400) +** or +** isleap(a + b) == isleap(a % 400 + b % 400) +** This is true even if % means modulo rather than Fortran remainder +** (which is allowed by C89 but not C99). +** We use this to avoid addition overflow problems. +*/ + +#define isleap_sum(a, b) isleap((a) % 400 + (b) % 400) + +/* + * We do not implement alternate representations. However, we always + * check whether a given modifier is allowed for a certain conversion. + */ +#define ALT_E 0x01 +#define ALT_O 0x02 +#define LEGAL_ALT(x) { if (alt_format & ~(x)) return NULL; } + +#define S_YEAR (1 << 0) +#define S_MON (1 << 1) +#define S_YDAY (1 << 2) +#define S_MDAY (1 << 3) +#define S_WDAY (1 << 4) +#define S_HOUR (1 << 5) + +#define HAVE_MDAY(s) (s & S_MDAY) +#define HAVE_MON(s) (s & S_MON) +#define HAVE_WDAY(s) (s & S_WDAY) +#define HAVE_YDAY(s) (s & S_YDAY) +#define HAVE_YEAR(s) (s & S_YEAR) +#define HAVE_HOUR(s) (s & S_HOUR) + +static char utc[] = { "UTC" }; +/* RFC-822/RFC-2822 */ +static const char * const nast[5] = { + "EST", "CST", "MST", "PST", "\0\0\0" +}; +static const char * const nadt[5] = { + "EDT", "CDT", "MDT", "PDT", "\0\0\0" +}; + +static const char * const cloc_am_pm[] = {"AM", "PM", NULL}; + +static const char * const cloc_abday[] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL +}; + +static const char * const cloc_day[] = { + "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", + "Saturday", NULL +}; + +static const char * const cloc_abmon[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", + "Oct", "Nov", "Dec", NULL +}; + +static const char * const cloc_mon[] = { + "January", "February", "March", "April", "May", "June", "July", + "August", "September", "October", "November", "December", NULL +}; + +/* + * Table to determine the ordinal date for the start of a month. + * Ref: http://en.wikipedia.org/wiki/ISO_week_date + */ +static const int start_of_month[2][13] = { + /* non-leap year */ + { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }, + /* leap year */ + { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 } +}; + +/* + * Calculate the week day of the first day of a year. Valid for + * the Gregorian calendar, which began Sept 14, 1752 in the UK + * and its colonies. Ref: + * http://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week + */ + +static int +first_wday_of(int yr) +{ + return ((2 * (3 - (yr / 100) % 4)) + (yr % 100) + ((yr % 100) / 4) + + (isleap(yr) ? 6 : 0) + 1) % 7; +} + +#define delim(p) ((p) == '\0' || g_ascii_isspace((unsigned char)(p))) + +#define SET_ZONEP(p, off, zone) \ + do { if (p) { p->tm_gmtoff = off; p->tm_zone = zone; } } while (0) + +/* + * This is spectacularly ugly. + * + * POSIX require that there be a variable named "timezone", which contains + * "the difference, in seconds, between Coordinated Universal Time (UTC) + * and local standard time.". + * + * Most of the platforms on which we run have this. + * + * FreeBSD, however, does not. Instead, it provides a function named + * "timezone", which takes two integer arguments, "zone" and "dst", + * and "returns a pointer to a time zone abbreviation for the specified + * zone and dst values. The zone argument is the number of minutes west + * of GMT and dst is non-zero if daylight savings time is in effect." + * + * So we need a way to get "the difference, in seconds, between Coordinated + * Universal Time (UTC) and local standard time." + * + * The FreeBSD Wireshark port, as of 2023-12-05, does so by handing + * a time_t value of 0, meaning 1970-01-01 00:00:00 UTC (the Unix Epoch), + * to localtime() and using the tm_gmtoff value from the resulting + * struct tm. That works in countries that were in standard time + * then, but doesn't work in countries that were not in standard time + * then, meaning it doesn't work correctly in countries in the Southern + * Hemisphere that were in Daylight Saving Tie at that point, and may or + * may not work correctly in Ireland, depending on how "standard time" + * is defined (don't ask). + * + * For now, we use a similar mechanism to the one above, but we check + * whether tm_isdst is greater than 0 in the resulting struct tm and, + * if it is, use a time_t value of 86400*(365/2), in the hopes that, + * halfway through 1970, the location in question was in standard + * time. + * + * Also, for now, we test for FreeBSD rather than doing a configure- + * time check; checking whether the symbol "timezone" is defined + * won't work, as it's defined in FreeBSD as a function, so we'd + * have to check *how* it's defined. + * + * So we have a function to return the difference in question. It + * returns a long because timezone is defined to be a long in POSIX + * and because the tm_gmtoff member of a struct tm, if such a member + * is present, is also a long. + */ +static long +utc_offset(void) +{ +#if defined(__FreeBSD__) + /* + * We only calculate the standard time UTC offset once, under the + * assumption that we won't change what time zone we're in. + * + * XXX - that assumption is violated if: + * + * you're running on an OS where you can set the current + * time zone and that will affect all running programs, + * or where the OS tries to determine where you're located + * and changes the time zone to match (for example, macOS, + * in which both of those are the case); + * + * you're in a location that has moved between time zones + * since 1970-01-01 00:00:00 UTC (there are some, and the + * IANA time zone database, at least, takes that into + * account); + * + * we add support for the if_iana_tzname Interface + * Description Block option, so that, when looking + * at a file with that option for one or more + * interfaces, and using the timezone from that + * option rather than the local timezone, the + * offset from UTC may change from file to file. + * + * This *probably* won't make much of a difference, as + * we have to do this sort of hackery only when parsing + * a date that doesn't use the "Obsolete Date and Time", + * as it's called in RFC 2822. + */ + static bool got_utcoffset = false; + static struct tm *gtm; + time_t then = 0; + + if (got_utcoffset) { + if (gtm != NULL) + return gtm->tm_gmtoff; + else + return 0; /* localtime() failed on us */ + } + + gtm = localtime(&then); + got_utcoffset = true; + if (gtm == NULL) { + /* + * Oh, heck, it can't convert the Epoch. Just + * return 0 and say to hell with it. + */ + return 0; + } + if (gtm->tm_isdst > 0) { + /* + * Sorry, we were in Daylight Saving Time on + * 1970-01-01 at 00:00:00 UTC. Try the middle + * of the year. (We don't bother making sure + * we weren't in DST then.) + */ + then = 86400*(365/2); + gtm = localtime(&then); + if (gtm == NULL) { + /* See above. */ + return 0; + } + } + return gtm->tm_gmtoff; +#else + return timezone; +#endif +} + +char * +ws_strptime_p(const char *buf, const char *format, struct tm *tm) +{ +#ifdef HAVE_STRPTIME + return strptime(buf, format, tm); +#else + return ws_strptime(buf, format, tm, NULL); +#endif +} + +char * +ws_strptime(const char *buf, const char *fmt, struct tm *tm, struct ws_timezone *zonep) +{ + unsigned char c; + const unsigned char *bp, *ep, *zname; + int alt_format, i, split_year = 0, neg = 0, state = 0, + day_offset = -1, week_offset = 0, offs, mandatory; + const char *new_fmt; + long tm_gmtoff; + const char *tm_zone; + + bp = (const unsigned char *)buf; + + while (bp != NULL && (c = *fmt++) != '\0') { + /* Clear `alternate' modifier prior to new conversion. */ + alt_format = 0; + i = 0; + + /* Eat up white-space. */ + if (g_ascii_isspace(c)) { + while (g_ascii_isspace(*bp)) + bp++; + continue; + } + + if (c != '%') + goto literal; + + +again: switch (c = *fmt++) { + case '%': /* "%%" is converted to "%". */ +literal: + if (c != *bp++) + return NULL; + LEGAL_ALT(0); + continue; + + /* + * "Alternative" modifiers. Just set the appropriate flag + * and start over again. + */ + case 'E': /* "%E?" alternative conversion modifier. */ + LEGAL_ALT(0); + alt_format |= ALT_E; + goto again; + + case 'O': /* "%O?" alternative conversion modifier. */ + LEGAL_ALT(0); + alt_format |= ALT_O; + goto again; + + /* + * "Complex" conversion rules, implemented through recursion. + */ + case 'c': /* Date and time, using the locale's format. */ + new_fmt = "%a %b %e %H:%M:%S %Y"; + state |= S_WDAY | S_MON | S_MDAY | S_YEAR; + goto recurse; + + case 'D': /* The date as "%m/%d/%y". */ + new_fmt = "%m/%d/%y"; + LEGAL_ALT(0); + state |= S_MON | S_MDAY | S_YEAR; + goto recurse; + + case 'F': /* The date as "%Y-%m-%d". */ + new_fmt = "%Y-%m-%d"; + LEGAL_ALT(0); + state |= S_MON | S_MDAY | S_YEAR; + goto recurse; + + case 'R': /* The time as "%H:%M". */ + new_fmt = "%H:%M"; + LEGAL_ALT(0); + goto recurse; + + case 'r': /* The time in 12-hour clock representation. */ + new_fmt = "%I:%M:%S %p"; + LEGAL_ALT(0); + goto recurse; + + case 'T': /* The time as "%H:%M:%S". */ + new_fmt = "%H:%M:%S"; + LEGAL_ALT(0); + goto recurse; + + case 'X': /* The time, using the locale's format. */ + new_fmt = "%H:%M:%S"; + goto recurse; + + case 'x': /* The date, using the locale's format. */ + new_fmt = "%m/%d/%y"; + state |= S_MON | S_MDAY | S_YEAR; + recurse: + bp = (const unsigned char *)ws_strptime((const char *)bp, + new_fmt, tm, zonep); + LEGAL_ALT(ALT_E); + continue; + + /* + * "Elementary" conversion rules. + */ + case 'A': /* The day of week, using the locale's form. */ + case 'a': + bp = find_string(bp, &tm->tm_wday, cloc_day, cloc_abday, 7); + LEGAL_ALT(0); + state |= S_WDAY; + continue; + + case 'B': /* The month, using the locale's form. */ + case 'b': + case 'h': + bp = find_string(bp, &tm->tm_mon, cloc_mon, cloc_abmon, 12); + LEGAL_ALT(0); + state |= S_MON; + continue; + + case 'C': /* The century number. */ + i = 20; + bp = conv_num(bp, &i, 0, 99); + + i = i * 100 - TM_YEAR_BASE; + if (split_year) + i += tm->tm_year % 100; + split_year = 1; + tm->tm_year = i; + LEGAL_ALT(ALT_E); + state |= S_YEAR; + continue; + + case 'd': /* The day of month. */ + case 'e': + bp = conv_num(bp, &tm->tm_mday, 1, 31); + LEGAL_ALT(ALT_O); + state |= S_MDAY; + continue; + + case 'k': /* The hour (24-hour clock representation). */ + LEGAL_ALT(0); + /* FALLTHROUGH */ + case 'H': + bp = conv_num(bp, &tm->tm_hour, 0, 23); + LEGAL_ALT(ALT_O); + state |= S_HOUR; + continue; + + case 'l': /* The hour (12-hour clock representation). */ + LEGAL_ALT(0); + /* FALLTHROUGH */ + case 'I': + bp = conv_num(bp, &tm->tm_hour, 1, 12); + if (tm->tm_hour == 12) + tm->tm_hour = 0; + LEGAL_ALT(ALT_O); + state |= S_HOUR; + continue; + + case 'j': /* The day of year. */ + i = 1; + bp = conv_num(bp, &i, 1, 366); + tm->tm_yday = i - 1; + LEGAL_ALT(0); + state |= S_YDAY; + continue; + + case 'M': /* The minute. */ + bp = conv_num(bp, &tm->tm_min, 0, 59); + LEGAL_ALT(ALT_O); + continue; + + case 'm': /* The month. */ + i = 1; + bp = conv_num(bp, &i, 1, 12); + tm->tm_mon = i - 1; + LEGAL_ALT(ALT_O); + state |= S_MON; + continue; + + case 'p': /* The locale's equivalent of AM/PM. */ + bp = find_string(bp, &i, cloc_am_pm, + NULL, 2); + if (HAVE_HOUR(state) && tm->tm_hour > 11) + return NULL; + tm->tm_hour += i * 12; + LEGAL_ALT(0); + continue; + + case 'S': /* The seconds. */ + bp = conv_num(bp, &tm->tm_sec, 0, 61); + LEGAL_ALT(ALT_O); + continue; + + case 's': /* seconds since the epoch */ + { + int64_t secs; + const char *endptr; + time_t sse; + + /* Extract the seconds as a 64-bit signed number. */ + if (!ws_strtoi64(bp, &endptr, &secs)) { + bp = NULL; + continue; + } + bp = endptr; + + /* For now, reject times before the Epoch. */ + if (secs < 0) { + bp = NULL; + continue; + } + + /* Make sure it fits. */ + sse = (time_t)secs; + if (sse != secs) { + bp = NULL; + continue; + } + + if (ws_localtime_r(&sse, tm) == NULL) + bp = NULL; + else + state |= S_YDAY | S_WDAY | + S_MON | S_MDAY | S_YEAR; + } + continue; + + case 'U': /* The week of year, beginning on sunday. */ + case 'W': /* The week of year, beginning on monday. */ + /* + * This is bogus, as we can not assume any valid + * information present in the tm structure at this + * point to calculate a real value, so save the + * week for now in case it can be used later. + */ + bp = conv_num(bp, &i, 0, 53); + LEGAL_ALT(ALT_O); + if (c == 'U') + day_offset = TM_SUNDAY; + else + day_offset = TM_MONDAY; + week_offset = i; + continue; + + case 'w': /* The day of week, beginning on sunday. */ + bp = conv_num(bp, &tm->tm_wday, 0, 6); + LEGAL_ALT(ALT_O); + state |= S_WDAY; + continue; + + case 'u': /* The day of week, monday = 1. */ + bp = conv_num(bp, &i, 1, 7); + tm->tm_wday = i % 7; + LEGAL_ALT(ALT_O); + state |= S_WDAY; + continue; + + case 'g': /* The year corresponding to the ISO week + * number but without the century. + */ + bp = conv_num(bp, &i, 0, 99); + continue; + + case 'G': /* The year corresponding to the ISO week + * number with century. + */ + do + bp++; + while (g_ascii_isdigit(*bp)); + continue; + + case 'V': /* The ISO 8601:1988 week number as decimal */ + bp = conv_num(bp, &i, 1, 53); + continue; + + case 'Y': /* The year. */ + i = TM_YEAR_BASE; /* just for data sanity... */ + bp = conv_num(bp, &i, 0, 9999); + tm->tm_year = i - TM_YEAR_BASE; + LEGAL_ALT(ALT_E); + state |= S_YEAR; + continue; + + case 'y': /* The year within 100 years of the epoch. */ + /* LEGAL_ALT(ALT_E | ALT_O); */ + bp = conv_num(bp, &i, 0, 99); + + if (split_year) + /* preserve century */ + i += (tm->tm_year / 100) * 100; + else { + split_year = 1; + if (i <= 68) + i = i + 2000 - TM_YEAR_BASE; + else + i = i + 1900 - TM_YEAR_BASE; + } + tm->tm_year = i; + state |= S_YEAR; + continue; + + case 'Z': + case 'z': + tzset(); + mandatory = c == 'z'; + /* + * We recognize all ISO 8601 formats: + * Z = Zulu time/UTC + * [+-]hhmm + * [+-]hh:mm + * [+-]hh + * We recognize all RFC-822/RFC-2822 formats: + * UT|GMT + * North American : UTC offsets + * E[DS]T = Eastern : -4 | -5 + * C[DS]T = Central : -5 | -6 + * M[DS]T = Mountain: -6 | -7 + * P[DS]T = Pacific : -7 | -8 + * Nautical/Military + * [A-IL-M] = -1 ... -9 (J not used) + * [N-Y] = +1 ... +12 + * Note: J maybe used to denote non-nautical + * local time + */ + if (mandatory) + while (g_ascii_isspace(*bp)) + bp++; + + zname = bp; + switch (*bp++) { + case 'G': + if (*bp++ != 'M') + goto namedzone; + /*FALLTHROUGH*/ + case 'U': + if (*bp++ != 'T') + goto namedzone; + else if (!delim(*bp) && *bp++ != 'C') + goto namedzone; + /*FALLTHROUGH*/ + case 'Z': + if (!delim(*bp)) + goto namedzone; + tm->tm_isdst = 0; + tm_gmtoff = 0; + tm_zone = utc; + SET_ZONEP(zonep, tm_gmtoff, tm_zone); + continue; + case '+': + neg = 0; + break; + case '-': + neg = 1; + break; + default: +namedzone: + bp = zname; + + /* Nautical / Military style */ + if (delim(bp[1]) && + ((*bp >= 'A' && *bp <= 'I') || + (*bp >= 'L' && *bp <= 'Y'))) { + /* Argh! No 'J'! */ + if (*bp >= 'A' && *bp <= 'I') + tm_gmtoff = + (int)*bp - ('A' - 1); + else if (*bp >= 'L' && *bp <= 'M') + tm_gmtoff = (int)*bp - 'A'; + else if (*bp >= 'N' && *bp <= 'Y') + tm_gmtoff = 'M' - (int)*bp; + else { + /* Not reached. */ + ws_critical("Not reached!"); + goto out; + } + tm_gmtoff *= SECSPERHOUR; + tm_zone = NULL; /* XXX */ + SET_ZONEP(zonep, tm_gmtoff, tm_zone); + bp++; + continue; + } + /* 'J' is local time */ + if (delim(bp[1]) && *bp == 'J') { + tm_gmtoff = -utc_offset(); + tm_zone = NULL; /* XXX */ + SET_ZONEP(zonep, tm_gmtoff, tm_zone); + bp++; + continue; + } + + /* + * From our 3 letter hard-coded table + */ + ep = find_string(bp, &i, nast, NULL, 4); + if (ep != NULL) { + tm_gmtoff = (-5 - i) * SECSPERHOUR; + tm_zone = nast[i]; + SET_ZONEP(zonep, tm_gmtoff, tm_zone); + bp = ep; + continue; + } + ep = find_string(bp, &i, nadt, NULL, 4); + if (ep != NULL) { + tm->tm_isdst = 1; + tm_gmtoff = (-4 - i) * SECSPERHOUR; + tm_zone = nadt[i]; + SET_ZONEP(zonep, tm_gmtoff, tm_zone); + bp = ep; + continue; + } + /* + * Our current timezone + */ + ep = find_string(bp, &i, + (const char * const *)tzname, + NULL, 2); + if (ep != NULL) { + tm->tm_isdst = i; + tm_gmtoff = -utc_offset(); + tm_zone = tzname[i]; + SET_ZONEP(zonep, tm_gmtoff, tm_zone); + bp = ep; + continue; + } + goto out; + } + offs = 0; + for (i = 0; i < 4; ) { + if (g_ascii_isdigit(*bp)) { + offs = offs * 10 + (*bp++ - '0'); + i++; + continue; + } + if (i == 2 && *bp == ':') { + bp++; + continue; + } + break; + } + if (g_ascii_isdigit(*bp)) + goto out; + switch (i) { + case 2: + offs *= SECSPERHOUR; + break; + case 4: + i = offs % 100; + offs /= 100; + if (i >= SECSPERMIN) + goto out; + /* Convert minutes into decimal */ + offs = offs * SECSPERHOUR + i * SECSPERMIN; + break; + default: + out: + if (mandatory) + return NULL; + bp = zname; + continue; + } + /* ISO 8601 & RFC 3339 limit to 23:59 max */ + if (offs >= (HOURSPERDAY * SECSPERHOUR)) + goto out; + if (neg) + offs = -offs; + tm->tm_isdst = 0; /* XXX */ + tm_gmtoff = offs; + tm_zone = NULL; /* XXX */ + SET_ZONEP(zonep, tm_gmtoff, tm_zone); + continue; + + /* + * Miscellaneous conversions. + */ + case 'n': /* Any kind of white-space. */ + case 't': + while (g_ascii_isspace(*bp)) + bp++; + LEGAL_ALT(0); + continue; + + + default: /* Unknown/unsupported conversion. */ + return NULL; + } + } + + if (!HAVE_YDAY(state) && HAVE_YEAR(state)) { + if (HAVE_MON(state) && HAVE_MDAY(state)) { + /* calculate day of year (ordinal date) */ + tm->tm_yday = start_of_month[isleap_sum(tm->tm_year, + TM_YEAR_BASE)][tm->tm_mon] + (tm->tm_mday - 1); + state |= S_YDAY; + } else if (day_offset != -1) { + /* + * Set the date to the first Sunday (or Monday) + * of the specified week of the year. + */ + if (!HAVE_WDAY(state)) { + tm->tm_wday = day_offset; + state |= S_WDAY; + } + tm->tm_yday = (7 - + first_wday_of(tm->tm_year + TM_YEAR_BASE) + + day_offset) % 7 + (week_offset - 1) * 7 + + tm->tm_wday - day_offset; + state |= S_YDAY; + } + } + + if (HAVE_YDAY(state) && HAVE_YEAR(state)) { + int isleap; + + if (!HAVE_MON(state)) { + /* calculate month of day of year */ + i = 0; + isleap = isleap_sum(tm->tm_year, TM_YEAR_BASE); + while (tm->tm_yday >= start_of_month[isleap][i]) + i++; + if (i > 12) { + i = 1; + tm->tm_yday -= start_of_month[isleap][12]; + tm->tm_year++; + } + tm->tm_mon = i - 1; + state |= S_MON; + } + + if (!HAVE_MDAY(state)) { + /* calculate day of month */ + isleap = isleap_sum(tm->tm_year, TM_YEAR_BASE); + tm->tm_mday = tm->tm_yday - + start_of_month[isleap][tm->tm_mon] + 1; + state |= S_MDAY; + } + + if (!HAVE_WDAY(state)) { + /* calculate day of week */ + i = 0; + week_offset = first_wday_of(tm->tm_year); + while (i++ <= tm->tm_yday) { + if (week_offset++ >= 6) + week_offset = 0; + } + tm->tm_wday = week_offset; + } + } + + return (char *)bp; +} + + +static const unsigned char * +conv_num(const unsigned char *buf, int *dest, unsigned llim, unsigned ulim) +{ + unsigned result = 0; + unsigned char ch; + + /* The limit also determines the number of valid digits. */ + unsigned rulim = ulim; + + ch = *buf; + if (ch < '0' || ch > '9') + return NULL; + + do { + result *= 10; + result += ch - '0'; + rulim /= 10; + ch = *++buf; + } while ((result * 10 <= ulim) && rulim && ch >= '0' && ch <= '9'); + + if (result < llim || result > ulim) + return NULL; + + *dest = result; + return buf; +} + +static const unsigned char * +find_string(const unsigned char *bp, int *tgt, const char * const *n1, + const char * const *n2, int c) +{ + int i; + size_t len; + + /* check full name - then abbreviated ones */ + for (; n1 != NULL; n1 = n2, n2 = NULL) { + for (i = 0; i < c; i++, n1++) { + len = strlen(*n1); + if (g_ascii_strncasecmp(*n1, (const char *)bp, len) == 0) { + *tgt = i; + return bp + len; + } + } + } + + /* Nothing matched */ + return NULL; +} diff --git a/wsutil/ws_strptime.h b/wsutil/ws_strptime.h new file mode 100644 index 00000000..0eb0c56a --- /dev/null +++ b/wsutil/ws_strptime.h @@ -0,0 +1,46 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WS_STRPTIME_H__ +#define __WS_STRPTIME_H__ + +#include <wireshark.h> +#include <time.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* Struct to pass the "tm_gmtoff" and "tm_zone" fields, for systems whose + * libc struct tm type lacks these non-standard extensions. */ +struct ws_timezone { + long tm_gmtoff; + const char *tm_zone; +}; + +/* + * This is the NetBSD strptime(), modified to always use the "C" locale. + */ +WS_DLL_PUBLIC +char * +ws_strptime(const char *buf, const char *format, struct tm *tm, + struct ws_timezone *zonep); + +/* + * Portability wrapper around the system's strptime(). + */ +WS_DLL_PUBLIC +char * +ws_strptime_p(const char *buf, const char *format, struct tm *tm); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __WS_STRPTIME_H__ */ diff --git a/wsutil/wsgcrypt.c b/wsutil/wsgcrypt.c new file mode 100644 index 00000000..3fbf9e58 --- /dev/null +++ b/wsutil/wsgcrypt.c @@ -0,0 +1,214 @@ +/* wsgcrypt.c + * Helper functions for libgcrypt + * By Erik de Jong <erikdejong@gmail.com> + * Copyright 2017 Erik de Jong + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "wsgcrypt.h" + +gcry_error_t ws_hmac_buffer(int algo, void *digest, const void *buffer, size_t length, const void *key, size_t keylen) +{ + gcry_md_hd_t hmac_handle; + gcry_error_t result = gcry_md_open(&hmac_handle, algo, GCRY_MD_FLAG_HMAC); + if (result) { + return result; + } + result = gcry_md_setkey(hmac_handle, key, keylen); + if (result) { + gcry_md_close(hmac_handle); + return result; + } + gcry_md_write(hmac_handle, buffer, length); + memcpy(digest, gcry_md_read(hmac_handle, 0), gcry_md_get_algo_dlen(algo)); + gcry_md_close(hmac_handle); + return GPG_ERR_NO_ERROR; +} + +gcry_error_t ws_cmac_buffer(int algo, void *digest, const void *buffer, size_t length, const void *key, size_t keylen) +{ + gcry_mac_hd_t cmac_handle; + gcry_error_t result = gcry_mac_open(&cmac_handle, algo, 0, NULL); + if (result) { + return result; + } + result = gcry_mac_setkey(cmac_handle, key, keylen); + if (result) { + gcry_mac_close(cmac_handle); + return result; + } + gcry_mac_write(cmac_handle, buffer, length); + result = gcry_mac_read(cmac_handle, digest, &keylen); + gcry_mac_close(cmac_handle); + return result; +} + +void crypt_des_ecb(uint8_t *output, const uint8_t *buffer, const uint8_t *key56) +{ + uint8_t key64[8]; + gcry_cipher_hd_t handle; + + memset(output, 0x00, 8); + + /* Transform 56 bits key into 64 bits DES key */ + key64[0] = key56[0]; + key64[1] = (key56[0] << 7) | (key56[1] >> 1); + key64[2] = (key56[1] << 6) | (key56[2] >> 2); + key64[3] = (key56[2] << 5) | (key56[3] >> 3); + key64[4] = (key56[3] << 4) | (key56[4] >> 4); + key64[5] = (key56[4] << 3) | (key56[5] >> 5); + key64[6] = (key56[5] << 2) | (key56[6] >> 6); + key64[7] = (key56[6] << 1); + + if (gcry_cipher_open(&handle, GCRY_CIPHER_DES, GCRY_CIPHER_MODE_ECB, 0)) { + return; + } + if (gcry_cipher_setkey(handle, key64, 8)) { + gcry_cipher_close(handle); + return; + } + gcry_cipher_encrypt(handle, output, 8, buffer, 8); + gcry_cipher_close(handle); +} + +size_t rsa_decrypt_inplace(const unsigned len, unsigned char* data, gcry_sexp_t pk, bool pkcs1_padding, char **err) +{ + int rc = 0; + size_t decr_len = 0, i = 0; + gcry_sexp_t s_data = NULL, s_plain = NULL; + gcry_mpi_t encr_mpi = NULL, text = NULL; + + *err = NULL; + + /* create mpi representation of encrypted data */ + rc = gcry_mpi_scan(&encr_mpi, GCRYMPI_FMT_USG, data, len, NULL); + if (rc != 0 ) { + *err = ws_strdup_printf("can't convert data to mpi (size %d):%s", len, gcry_strerror(rc)); + return 0; + } + + /* put the data into a simple list */ + rc = gcry_sexp_build(&s_data, NULL, "(enc-val(rsa(a%m)))", encr_mpi); + if (rc != 0) { + *err = ws_strdup_printf("can't build encr_sexp:%s", gcry_strerror(rc)); + decr_len = 0; + goto out; + } + + /* pass it to libgcrypt */ + rc = gcry_pk_decrypt(&s_plain, s_data, pk); + if (rc != 0) + { + *err = ws_strdup_printf("can't decrypt key:%s", gcry_strerror(rc)); + decr_len = 0; + goto out; + } + + /* convert plain text sexp to mpi format */ + text = gcry_sexp_nth_mpi(s_plain, 0, 0); + if (! text) { + *err = g_strdup("can't convert sexp to mpi"); + decr_len = 0; + goto out; + } + + /* compute size requested for plaintext buffer */ + rc = gcry_mpi_print(GCRYMPI_FMT_USG, NULL, 0, &decr_len, text); + if (rc != 0) { + *err = ws_strdup_printf("can't compute decr size:%s", gcry_strerror(rc)); + decr_len = 0; + goto out; + } + + /* sanity check on out buffer */ + if (decr_len > len) { + *err = ws_strdup_printf("decrypted data is too long ?!? (%zu max %d)", decr_len, len); + decr_len = 0; + goto out; + } + + /* write plain text to newly allocated buffer */ + rc = gcry_mpi_print(GCRYMPI_FMT_USG, data, len, &decr_len, text); + if (rc != 0) { + *err = ws_strdup_printf("can't print decr data to mpi (size %zu):%s", decr_len, gcry_strerror(rc)); + decr_len = 0; + goto out; + } + + if (pkcs1_padding) { + /* strip the padding*/ + rc = 0; + for (i = 1; i < decr_len; i++) { + if (data[i] == 0) { + rc = (int) i+1; + break; + } + } + + decr_len -= rc; + memmove(data, data+rc, decr_len); + } + +out: + gcry_sexp_release(s_data); + gcry_sexp_release(s_plain); + gcry_mpi_release(encr_mpi); + gcry_mpi_release(text); + return decr_len; +} + +gcry_error_t +hkdf_expand(int hashalgo, const uint8_t *prk, unsigned prk_len, const uint8_t *info, unsigned info_len, + uint8_t *out, unsigned out_len) +{ + // Current maximum hash output size: 48 bytes for SHA-384. + unsigned char lastoutput[48]; + gcry_md_hd_t h; + gcry_error_t err; + const unsigned hash_len = gcry_md_get_algo_dlen(hashalgo); + + /* Some sanity checks */ + if (!(out_len > 0 && out_len <= 255 * hash_len) || + !(hash_len > 0 && hash_len <= sizeof(lastoutput))) { + return GPG_ERR_INV_ARG; + } + + err = gcry_md_open(&h, hashalgo, GCRY_MD_FLAG_HMAC); + if (err) { + return err; + } + + for (unsigned offset = 0; offset < out_len; offset += hash_len) { + gcry_md_reset(h); + gcry_md_setkey(h, prk, prk_len); /* Set PRK */ + if (offset > 0) { + gcry_md_write(h, lastoutput, hash_len); /* T(1..N) */ + } + gcry_md_write(h, info, info_len); /* info */ + gcry_md_putc(h, (uint8_t) (offset / hash_len + 1)); /* constant 0x01..N */ + + memcpy(lastoutput, gcry_md_read(h, hashalgo), hash_len); + memcpy(out + offset, lastoutput, MIN(hash_len, out_len - offset)); + } + + gcry_md_close(h); + return 0; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 8 + * tab-width: 8 + * indent-tabs-mode: t + * End: + * + * vi: set shiftwidth=8 tabstop=8 noexpandtab: + * :indentSize=8:tabSize=8:noTabs=false: + */ diff --git a/wsutil/wsgcrypt.h b/wsutil/wsgcrypt.h new file mode 100644 index 00000000..0f09f3cd --- /dev/null +++ b/wsutil/wsgcrypt.h @@ -0,0 +1,74 @@ +/** @file + * + * Wrapper around libgcrypt's include file gcrypt.h. + * For libgcrypt 1.5.0, including gcrypt.h directly brings up lots of + * compiler warnings about deprecated definitions. + * Try to work around these warnings to ensure a clean build with -Werror. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 2007 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WSGCRYPT_H__ +#define __WSGCRYPT_H__ + +#include <wireshark.h> +#include <gcrypt.h> + +#define HASH_MD5_LENGTH 16 +#define HASH_SHA1_LENGTH 20 +#define HASH_SHA2_224_LENGTH 28 +#define HASH_SHA2_256_LENGTH 32 +#define HASH_SHA2_384_LENGTH 48 +#define HASH_SHA2_512_LENGTH 64 + +/* Convenience function to calculate the HMAC from the data in BUFFER + of size LENGTH with key KEY of size KEYLEN using the algorithm ALGO avoiding the creating of a + hash object. The hash is returned in the caller provided buffer + DIGEST which must be large enough to hold the digest of the given + algorithm. */ +WS_DLL_PUBLIC gcry_error_t ws_hmac_buffer(int algo, void *digest, const void *buffer, size_t length, const void *key, size_t keylen); + +WS_DLL_PUBLIC gcry_error_t ws_cmac_buffer(int algo, void *digest, const void *buffer, size_t length, const void *key, size_t keylen); + +/* Convenience function to encrypt 8 bytes in BUFFER with DES using the 56 bits KEY expanded to + 64 bits as key, encrypted data is returned in OUTPUT which must be at least 8 bytes large */ +WS_DLL_PUBLIC void crypt_des_ecb(uint8_t *output, const uint8_t *buffer, const uint8_t *key56); + +/* Convenience function for RSA decryption. Returns decrypted length on success, 0 on failure */ +WS_DLL_PUBLIC size_t rsa_decrypt_inplace(const unsigned len, unsigned char* data, gcry_sexp_t pk, bool pkcs1_padding, char **err); + +/** + * RFC 5869 HMAC-based Extract-and-Expand Key Derivation Function (HKDF): + * HKDF-Expand(PRK, info, L) -> OKM + * + * @param hashalgo [in] Libgcrypt hash algorithm identifier. + * @param prk [in] Pseudo-random key. + * @param prk_len [in] Length of prk. + * @param info [in] Optional context (can be NULL if info_len is zero). + * @param info_len [in] Length of info. + * @param out [out] Output keying material. + * @param out_len [in] Size of output keying material. + * @return 0 on success and an error code otherwise. + */ +WS_DLL_PUBLIC gcry_error_t +hkdf_expand(int hashalgo, const uint8_t *prk, unsigned prk_len, const uint8_t *info, unsigned info_len, + uint8_t *out, unsigned out_len); + +/* + * Calculate HKDF-Extract(salt, IKM) -> PRK according to RFC 5869. + * Caller MUST ensure that 'prk' is large enough to store the digest from hash + * algorithm 'hashalgo' (e.g. 32 bytes for SHA-256). + */ +static inline gcry_error_t +hkdf_extract(int hashalgo, const uint8_t *salt, size_t salt_len, const uint8_t *ikm, size_t ikm_len, uint8_t *prk) +{ + /* PRK = HMAC-Hash(salt, IKM) where salt is key, and IKM is input. */ + return ws_hmac_buffer(hashalgo, prk, ikm, ikm_len, salt, salt_len); +} + + +#endif /* __WSGCRYPT_H__ */ diff --git a/wsutil/wsjson.c b/wsutil/wsjson.c new file mode 100644 index 00000000..d2e55771 --- /dev/null +++ b/wsutil/wsjson.c @@ -0,0 +1,312 @@ +/* wsjson.c + * JSON parsing functions. + * + * Copyright 2016, Dario Lombardo + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" +#define WS_LOG_DOMAIN LOG_DOMAIN_MAIN + +#include "wsjson.h" + +#include <string.h> +#include <errno.h> +#include <wsutil/jsmn.h> +#include <wsutil/str_util.h> +#include <wsutil/unicode-utils.h> +#include <wsutil/wslog.h> + +bool +json_validate(const uint8_t *buf, const size_t len) +{ + bool ret = true; + /* We expect no more than 1024 tokens */ + unsigned max_tokens = 1024; + jsmntok_t* t; + jsmn_parser p; + int rcode; + + /* + * Make sure the buffer isn't empty and the first octet isn't a NUL; + * otherwise, the parser will immediately stop parsing and not validate + * anything after that, so it'll just think it was handed an empty string. + * + * XXX - should we check for NULs anywhere in the buffer? + */ + if (len == 0) { + ws_debug("JSON string is empty"); + return false; + } + if (buf[0] == '\0') { + ws_debug("invalid character inside JSON string"); + return false; + } + + t = g_new0(jsmntok_t, max_tokens); + + if (!t) + return false; + + jsmn_init(&p); + rcode = jsmn_parse(&p, buf, len, t, max_tokens); + if (rcode < 0) { + switch (rcode) { + case JSMN_ERROR_NOMEM: + ws_debug("not enough tokens were provided"); + break; + case JSMN_ERROR_INVAL: + ws_debug("invalid character inside JSON string"); + break; + case JSMN_ERROR_PART: + ws_debug("the string is not a full JSON packet, " + "more bytes expected"); + break; + default: + ws_debug("unexpected error"); + break; + } + ret = false; + } + + g_free(t); + return ret; +} + +int +json_parse(const char *buf, jsmntok_t *tokens, unsigned int max_tokens) +{ + jsmn_parser p; + + jsmn_init(&p); + return jsmn_parse(&p, buf, strlen(buf), tokens, max_tokens); +} + +static +jsmntok_t *json_get_next_object(jsmntok_t *cur) +{ + int i; + jsmntok_t *next = cur+1; + + for (i = 0; i < cur->size; i++) { + next = json_get_next_object(next); + } + return next; +} + +jsmntok_t *json_get_object(const char *buf, jsmntok_t *parent, const char *name) +{ + int i; + jsmntok_t *cur = parent+1; + + for (i = 0; i < parent->size; i++) { + if (cur->type == JSMN_STRING && + !strncmp(&buf[cur->start], name, cur->end - cur->start) + && strlen(name) == (size_t)(cur->end - cur->start) && + cur->size == 1 && (cur+1)->type == JSMN_OBJECT) { + return cur+1; + } + cur = json_get_next_object(cur); + } + return NULL; +} + +jsmntok_t *json_get_array(const char *buf, jsmntok_t *parent, const char *name) +{ + int i; + jsmntok_t *cur = parent+1; + + for (i = 0; i < parent->size; i++) { + if (cur->type == JSMN_STRING && + !strncmp(&buf[cur->start], name, cur->end - cur->start) + && strlen(name) == (size_t)(cur->end - cur->start) && + cur->size == 1 && (cur+1)->type == JSMN_ARRAY) { + return cur+1; + } + cur = json_get_next_object(cur); + } + return NULL; +} + +int json_get_array_len(jsmntok_t *array) +{ + if (array->type != JSMN_ARRAY) + return -1; + return array->size; +} + +jsmntok_t *json_get_array_index(jsmntok_t *array, int idx) +{ + int i; + jsmntok_t *cur = array+1; + + + if (array->type != JSMN_ARRAY || idx < 0 || idx >= array->size) + return NULL; + for (i = 0; i < idx; i++) + cur = json_get_next_object(cur); + return cur; +} + +char *json_get_string(char *buf, jsmntok_t *parent, const char *name) +{ + int i; + jsmntok_t *cur = parent+1; + + for (i = 0; i < parent->size; i++) { + if (cur->type == JSMN_STRING && + !strncmp(&buf[cur->start], name, cur->end - cur->start) + && strlen(name) == (size_t)(cur->end - cur->start) && + cur->size == 1 && (cur+1)->type == JSMN_STRING) { + buf[(cur+1)->end] = '\0'; + if (!json_decode_string_inplace(&buf[(cur+1)->start])) + return NULL; + return &buf[(cur+1)->start]; + } + cur = json_get_next_object(cur); + } + return NULL; +} + +bool json_get_double(char *buf, jsmntok_t *parent, const char *name, double *val) +{ + int i; + jsmntok_t *cur = parent+1; + + for (i = 0; i < parent->size; i++) { + if (cur->type == JSMN_STRING && + !strncmp(&buf[cur->start], name, cur->end - cur->start) + && strlen(name) == (size_t)(cur->end - cur->start) && + cur->size == 1 && (cur+1)->type == JSMN_PRIMITIVE) { + buf[(cur+1)->end] = '\0'; + *val = g_ascii_strtod(&buf[(cur+1)->start], NULL); + if (errno != 0) + return false; + return true; + } + cur = json_get_next_object(cur); + } + return false; +} + +bool +json_decode_string_inplace(char *text) +{ + const char *input = text; + char *output = text; + while (*input) { + char ch = *input++; + + if (ch == '\\') { + ch = *input++; + + switch (ch) { + case '\"': + case '\\': + case '/': + *output++ = ch; + break; + + case 'b': + *output++ = '\b'; + break; + case 'f': + *output++ = '\f'; + break; + case 'n': + *output++ = '\n'; + break; + case 'r': + *output++ = '\r'; + break; + case 't': + *output++ = '\t'; + break; + + case 'u': + { + uint32_t unicode_hex = 0; + int k; + int bin; + + for (k = 0; k < 4; k++) { + unicode_hex <<= 4; + + ch = *input++; + bin = ws_xton(ch); + if (bin == -1) + return false; + unicode_hex |= bin; + } + + if ((IS_LEAD_SURROGATE(unicode_hex))) { + uint16_t lead_surrogate = unicode_hex; + uint16_t trail_surrogate = 0; + + if (input[0] != '\\' || input[1] != 'u') + return false; + input += 2; + + for (k = 0; k < 4; k++) { + trail_surrogate <<= 4; + + ch = *input++; + bin = ws_xton(ch); + if (bin == -1) + return false; + trail_surrogate |= bin; + } + + if ((!IS_TRAIL_SURROGATE(trail_surrogate))) + return false; + + unicode_hex = SURROGATE_VALUE(lead_surrogate,trail_surrogate); + + } else if ((IS_TRAIL_SURROGATE(unicode_hex))) { + return false; + } + + if (!g_unichar_validate(unicode_hex)) + return false; + + /* Don't allow NUL byte injection. */ + if (unicode_hex == 0) + return false; + + /* \uXXXX => 6 bytes, and g_unichar_to_utf8() requires to have output buffer at least 6 bytes -> OK. */ + k = g_unichar_to_utf8(unicode_hex, output); + output += k; + break; + } + + default: + return false; + } + + } else { + *output = ch; + output++; + } + } + + *output = '\0'; + return true; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wsjson.h b/wsutil/wsjson.h new file mode 100644 index 00000000..be232767 --- /dev/null +++ b/wsutil/wsjson.h @@ -0,0 +1,95 @@ +/** @file + * + * JSON parsing functions. + * + * Copyright 2016, Dario Lombardo + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WSJSON_H__ +#define __WSJSON_H__ + +#include "ws_symbol_export.h" + +#include <inttypes.h> +#include <stdbool.h> + +#include "jsmn.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Check if a buffer is json an returns true if it is. + */ +WS_DLL_PUBLIC bool json_validate(const uint8_t *buf, const size_t len); + +WS_DLL_PUBLIC int json_parse(const char *buf, jsmntok_t *tokens, unsigned int max_tokens); + +/** + * Get the pointer to an object belonging to parent object and named as the name variable. + * Returns NULL if not found. + */ +WS_DLL_PUBLIC jsmntok_t *json_get_object(const char *buf, jsmntok_t *parent, const char *name); + +/** + * Get the pointer to an array belonging to parent object and named as the name variable. + * Returns NULL if not found. + */ +WS_DLL_PUBLIC jsmntok_t *json_get_array(const char *buf, jsmntok_t *parent, const char *name); + +/** + * Get the number of elements of an array. + * Returns -1 if the JSON objecct is not an array. + */ +WS_DLL_PUBLIC int json_get_array_len(jsmntok_t *array); + +/** + * Get the pointer to idx element of an array. + * Returns NULL if not found. + */ +WS_DLL_PUBLIC jsmntok_t *json_get_array_index(jsmntok_t *parent, int idx); + +/** + * Get the unescaped value of a string object belonging to parent object and named as the name variable. + * Returns NULL if not found. Caution: it modifies input buffer. + */ +WS_DLL_PUBLIC char *json_get_string(char *buf, jsmntok_t *parent, const char *name); + +/** + * Get the value of a number object belonging to parent object and named as the name variable. + * Returns false if not found. Caution: it modifies input buffer. + * Scientific notation not supported yet. + */ +WS_DLL_PUBLIC bool json_get_double(char *buf, jsmntok_t *parent, const char *name, double *val); + +/** + * Decode the contents of a JSON string value by overwriting the input data. + * Returns true on success and false if invalid characters were encountered. + */ +WS_DLL_PUBLIC bool json_decode_string_inplace(char *text); + +#ifdef __cplusplus +} +#endif + +#endif + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/wslog.c b/wsutil/wslog.c new file mode 100644 index 00000000..e181e5ed --- /dev/null +++ b/wsutil/wslog.c @@ -0,0 +1,1442 @@ +/* + * Copyright 2021, João Valverde <j@v6e.pt> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" + +#include "wslog.h" + +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <time.h> +/* Because ws_assert() dependes on ws_error() we do not use it + * here and fall back on assert() instead. */ +#include <assert.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef _WIN32 +#include <process.h> +#include <windows.h> +#include <conio.h> +#endif + +#include "file_util.h" +#include "time_util.h" +#include "to_str.h" +#include "strtoi.h" +#ifdef _WIN32 +#include "console_win32.h" +#endif + +#define ASSERT(expr) assert(expr) + +/* Runtime log level. */ +#define ENV_VAR_LEVEL "WIRESHARK_LOG_LEVEL" + +/* Log domains enabled/disabled. */ +#define ENV_VAR_DOMAIN "WIRESHARK_LOG_DOMAIN" + +/* Alias "domain" and "domains". */ +#define ENV_VAR_DOMAIN_S "WIRESHARK_LOG_DOMAINS" + +/* Log level that generates a trap and aborts. Can be "critical" + * or "warning". */ +#define ENV_VAR_FATAL "WIRESHARK_LOG_FATAL" + +/* Log domains that are fatal. */ +#define ENV_VAR_FATAL_DOMAIN "WIRESHARK_LOG_FATAL_DOMAIN" + +/* Alias "domain" and "domains". */ +#define ENV_VAR_FATAL_DOMAIN_S "WIRESHARK_LOG_FATAL_DOMAINS" + +/* Domains that will produce debug output, regardless of log level or + * domain filter. */ +#define ENV_VAR_DEBUG "WIRESHARK_LOG_DEBUG" + +/* Domains that will produce noisy output, regardless of log level or + * domain filter. */ +#define ENV_VAR_NOISY "WIRESHARK_LOG_NOISY" + +#define DEFAULT_LOG_LEVEL LOG_LEVEL_MESSAGE + +#define DEFAULT_PROGNAME "PID" + +#define DOMAIN_UNDEFED(domain) ((domain) == NULL || *(domain) == '\0') +#define DOMAIN_DEFINED(domain) (!DOMAIN_UNDEFED(domain)) + +/* + * Note: I didn't measure it but I assume using a string array is faster than + * a GHashTable for small number N of domains. + */ +typedef struct { + char **domainv; + bool positive; /* positive or negative match */ + enum ws_log_level min_level; /* for level filters */ +} log_filter_t; + + +/* If the module is not initialized by calling ws_log_init() all messages + * will be printed regardless of log level. This is a feature, not a bug. */ +static enum ws_log_level current_log_level = LOG_LEVEL_NONE; + +static bool stdout_color_enabled = false; + +static bool stderr_color_enabled = false; + +/* Use stdout for levels "info" and below, for backward compatibility + * with GLib. */ +static bool stdout_logging_enabled = false; + +static const char *registered_progname = DEFAULT_PROGNAME; + +/* List of domains to filter. */ +static log_filter_t *domain_filter = NULL; + +/* List of domains to output debug level unconditionally. */ +static log_filter_t *debug_filter = NULL; + +/* List of domains to output noisy level unconditionally. */ +static log_filter_t *noisy_filter = NULL; + +/* List of domains that are fatal. */ +static log_filter_t *fatal_filter = NULL; + +static ws_log_writer_cb *registered_log_writer = NULL; + +static void *registered_log_writer_data = NULL; + +static ws_log_writer_free_data_cb *registered_log_writer_data_free = NULL; + +static FILE *custom_log = NULL; + +static enum ws_log_level fatal_log_level = LOG_LEVEL_ERROR; + +#ifdef WS_DEBUG +static bool init_complete = false; +#endif + +ws_log_console_open_pref ws_log_console_open = LOG_CONSOLE_OPEN_NEVER; + + +static void print_err(void (*vcmdarg_err)(const char *, va_list ap), + int exit_failure, + const char *fmt, ...) G_GNUC_PRINTF(3,4); + +static void ws_log_cleanup(void); + + +const char *ws_log_level_to_string(enum ws_log_level level) +{ + switch (level) { + case LOG_LEVEL_NONE: + return "(zero)"; + case LOG_LEVEL_ECHO: + return "ECHO"; + case LOG_LEVEL_ERROR: + return "ERROR"; + case LOG_LEVEL_CRITICAL: + return "CRITICAL"; + case LOG_LEVEL_WARNING: + return "WARNING"; + case LOG_LEVEL_MESSAGE: + return "MESSAGE"; + case LOG_LEVEL_INFO: + return "INFO"; + case LOG_LEVEL_DEBUG: + return "DEBUG"; + case LOG_LEVEL_NOISY: + return "NOISY"; + default: + return "(BOGUS LOG LEVEL)"; + } +} + + +static enum ws_log_level string_to_log_level(const char *str_level) +{ + if (!str_level) + return LOG_LEVEL_NONE; + + if (g_ascii_strcasecmp(str_level, "noisy") == 0) + return LOG_LEVEL_NOISY; + else if (g_ascii_strcasecmp(str_level, "debug") == 0) + return LOG_LEVEL_DEBUG; + else if (g_ascii_strcasecmp(str_level, "info") == 0) + return LOG_LEVEL_INFO; + else if (g_ascii_strcasecmp(str_level, "message") == 0) + return LOG_LEVEL_MESSAGE; + else if (g_ascii_strcasecmp(str_level, "warning") == 0) + return LOG_LEVEL_WARNING; + else if (g_ascii_strcasecmp(str_level, "critical") == 0) + return LOG_LEVEL_CRITICAL; + else if (g_ascii_strcasecmp(str_level, "error") == 0) + return LOG_LEVEL_ERROR; + else if (g_ascii_strcasecmp(str_level, "echo") == 0) + return LOG_LEVEL_ECHO; + else + return LOG_LEVEL_NONE; +} + + +WS_RETNONNULL +static inline const char *domain_to_string(const char *domain) +{ + return DOMAIN_UNDEFED(domain) ? "(none)" : domain; +} + + +static inline bool filter_contains(log_filter_t *filter, + const char *domain) +{ + if (filter == NULL || DOMAIN_UNDEFED(domain)) + return false; + + for (char **domv = filter->domainv; *domv != NULL; domv++) { + if (g_ascii_strcasecmp(*domv, domain) == 0) { + return true; + } + } + return false; +} + + +static inline bool level_filter_matches(log_filter_t *filter, + const char *domain, + enum ws_log_level level, + bool *active_ptr) +{ + if (filter == NULL || DOMAIN_UNDEFED(domain)) + return false; + + if (!filter_contains(filter, domain)) + return false; + + if (filter->positive) { + if (active_ptr) + *active_ptr = level >= filter->min_level; + return true; + } + + /* negative match */ + if (level <= filter->min_level) { + if (active_ptr) + *active_ptr = false; + return true; + } + + return false; +} + + +static inline void +get_timestamp(struct timespec *ts) +{ + bool ok = false; + +#if defined(HAVE_CLOCK_GETTIME) + ok = (clock_gettime(CLOCK_REALTIME, ts) == 0); +#elif defined(HAVE_TIMESPEC_GET) + ok = (timespec_get(ts, TIME_UTC) == TIME_UTC); +#endif + if (ok) + return; + + /* Fall back on time(). */ + ts->tv_sec = time(NULL); + ts->tv_nsec = -1; +} + + +static inline void fill_manifest(ws_log_manifest_t *mft) +{ + struct timespec ts; + get_timestamp(&ts); + ws_localtime_r(&ts.tv_sec, &mft->tstamp_secs); + mft->nanosecs = ts.tv_nsec; + mft->pid = getpid(); +} + + +static inline bool msg_is_active(const char *domain, enum ws_log_level level, + ws_log_manifest_t *mft) +{ + bool is_active = ws_log_msg_is_active(domain, level); + if (is_active) + fill_manifest(mft); + return is_active; +} + + +bool ws_log_msg_is_active(const char *domain, enum ws_log_level level) +{ + /* + * Higher numerical levels have higher priority. Critical and above + * are always enabled. + */ + if (level >= LOG_LEVEL_CRITICAL) + return true; + + /* + * Check if the level has been configured as fatal. + */ + if (level >= fatal_log_level) + return true; + + /* + * Check if the domain has been configured as fatal. + */ + if (DOMAIN_DEFINED(domain) && fatal_filter != NULL) { + if (filter_contains(fatal_filter, domain) && fatal_filter->positive) { + return true; + } + } + + /* + * The debug/noisy filter overrides the other parameters. + */ + if (DOMAIN_DEFINED(domain)) { + bool active; + + if (level_filter_matches(noisy_filter, domain, level, &active)) + return active; + if (level_filter_matches(debug_filter, domain, level, &active)) + return active; + } + + /* + * If the priority is lower than the current minimum drop the + * message. + */ + if (level < current_log_level) + return false; + + /* + * If we don't have domain filtering enabled we are done. + */ + if (domain_filter == NULL) + return true; + + /* + * We have a filter but we don't use it with the undefined domain, + * pretty much every permanent call to ws_log should be using a + * chosen domain. + */ + if (DOMAIN_UNDEFED(domain)) + return true; + + /* Check if the domain filter matches. */ + if (filter_contains(domain_filter, domain)) + return domain_filter->positive; + + /* We have a domain filter but it didn't match. */ + return !domain_filter->positive; +} + + +enum ws_log_level ws_log_get_level(void) +{ + return current_log_level; +} + + +enum ws_log_level ws_log_set_level(enum ws_log_level level) +{ + if (level <= LOG_LEVEL_NONE || level >= _LOG_LEVEL_LAST) + return LOG_LEVEL_NONE; + if (level > LOG_LEVEL_CRITICAL) + level = LOG_LEVEL_CRITICAL; + + current_log_level = level; + return current_log_level; +} + + +enum ws_log_level ws_log_set_level_str(const char *str_level) +{ + enum ws_log_level level; + + level = string_to_log_level(str_level); + return ws_log_set_level(level); +} + + +static const char *opt_level = "--log-level"; +static const char *opt_domain = "--log-domain"; +/* Alias "domain" and "domains". */ +static const char *opt_domain_s = "--log-domains"; +static const char *opt_file = "--log-file"; +static const char *opt_fatal = "--log-fatal"; +static const char *opt_fatal_domain = "--log-fatal-domain"; +/* Alias "domain" and "domains". */ +static const char *opt_fatal_domain_s = "--log-fatal-domains"; +static const char *opt_debug = "--log-debug"; +static const char *opt_noisy = "--log-noisy"; + + +static void print_err(void (*vcmdarg_err)(const char *, va_list ap), + int exit_failure, + const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + if (vcmdarg_err) + vcmdarg_err(fmt, ap); + else + vfprintf(stderr, fmt, ap); + va_end(ap); + if (exit_failure != LOG_ARGS_NOEXIT) + exit(exit_failure); +} + + +/* + * This tries to convert old log level preference to a wslog + * configuration. The string must start with "console.log.level:" + * It receives an argv for { '-o', 'console.log.level:nnn', ...} or + * { '-oconsole.log.level:nnn', ...}. + */ +static void +parse_console_compat_option(char *argv[], + void (*vcmdarg_err)(const char *, va_list ap), + int exit_failure) +{ + const char *mask_str; + uint32_t mask; + enum ws_log_level level; + + ASSERT(argv != NULL); + + if (argv[0] == NULL) + return; + + if (strcmp(argv[0], "-o") == 0) { + if (argv[1] == NULL || + !g_str_has_prefix(argv[1], "console.log.level:")) { + /* Not what we were looking for. */ + return; + } + mask_str = argv[1] + strlen("console.log.level:"); + } + else if (g_str_has_prefix(argv[0], "-oconsole.log.level:")) { + mask_str = argv[0] + strlen("-oconsole.log.level:"); + } + else { + /* Not what we were looking for. */ + return; + } + + print_err(vcmdarg_err, LOG_ARGS_NOEXIT, + "Option 'console.log.level' is deprecated, consult '--help' " + "for diagnostic message options."); + + if (*mask_str == '\0') { + print_err(vcmdarg_err, exit_failure, + "Missing value to 'console.log.level' option."); + return; + } + + if (!ws_basestrtou32(mask_str, NULL, &mask, 10)) { + print_err(vcmdarg_err, exit_failure, + "%s is not a valid decimal number.", mask_str); + return; + } + + /* + * The lowest priority bit in the mask defines the level. + */ + if (mask & G_LOG_LEVEL_DEBUG) + level = LOG_LEVEL_DEBUG; + else if (mask & G_LOG_LEVEL_INFO) + level = LOG_LEVEL_INFO; + else if (mask & G_LOG_LEVEL_MESSAGE) + level = LOG_LEVEL_MESSAGE; + else if (mask & G_LOG_LEVEL_WARNING) + level = LOG_LEVEL_WARNING; + else if (mask & G_LOG_LEVEL_CRITICAL) + level = LOG_LEVEL_CRITICAL; + else if (mask & G_LOG_LEVEL_ERROR) + level = LOG_LEVEL_ERROR; + else + level = LOG_LEVEL_NONE; + + if (level == LOG_LEVEL_NONE) { + /* Some values (like zero) might not contain any meaningful bits. + * Throwing an error in that case seems appropriate. */ + print_err(vcmdarg_err, exit_failure, + "Value %s is not a valid log mask.", mask_str); + return; + } + + ws_log_set_level(level); +} + +/* Match "arg_name=value" or "arg_name value" to opt_name. */ +static bool optequal(const char *arg, const char *opt) +{ + ASSERT(arg); + ASSERT(opt); +#define ARGEND(arg) (*(arg) == '\0' || *(arg) == ' ' || *(arg) == '=') + + while (!ARGEND(arg) && *opt != '\0') { + if (*arg != *opt) { + return false; + } + arg += 1; + opt += 1; + } + if (ARGEND(arg) && *opt == '\0') { + return true; + } + return false; +} + +int ws_log_parse_args(int *argc_ptr, char *argv[], + void (*vcmdarg_err)(const char *, va_list ap), + int exit_failure) +{ + char **ptr = argv; + int count = *argc_ptr; + int ret = 0; + size_t optlen; + const char *option, *value; + int extra; + + if (argc_ptr == NULL || argv == NULL) + return -1; + +#ifdef WS_DEBUG + /* Assert ws_log_init() was called before ws_log_parse_args(). */ + ASSERT(init_complete); +#endif + + /* Configure from command line. */ + + while (*ptr != NULL) { + if (optequal(*ptr, opt_level)) { + option = opt_level; + optlen = strlen(opt_level); + } + else if (optequal(*ptr, opt_domain)) { + option = opt_domain; + optlen = strlen(opt_domain); + } + else if (optequal(*ptr, opt_domain_s)) { + option = opt_domain; /* Alias */ + optlen = strlen(opt_domain_s); + } + else if (optequal(*ptr, opt_fatal_domain)) { + option = opt_fatal_domain; + optlen = strlen(opt_fatal_domain); + } + else if (optequal(*ptr, opt_fatal_domain_s)) { + option = opt_fatal_domain; /* Alias */ + optlen = strlen(opt_fatal_domain_s); + } + else if (optequal(*ptr, opt_file)) { + option = opt_file; + optlen = strlen(opt_file); + } + else if (optequal(*ptr, opt_fatal)) { + option = opt_fatal; + optlen = strlen(opt_fatal); + } + else if (optequal(*ptr, opt_debug)) { + option = opt_debug; + optlen = strlen(opt_debug); + } + else if (optequal(*ptr, opt_noisy)) { + option = opt_noisy; + optlen = strlen(opt_noisy); + } + else { + /* Check is we have the old '-o console.log.level' flag, + * or '-oconsole.log.level', for backward compatibility. + * Then if we do ignore it after processing and let the + * preferences module handle it later. */ + if (*(*ptr + 0) == '-' && *(*ptr + 1) == 'o') { + parse_console_compat_option(ptr, vcmdarg_err, exit_failure); + } + ptr += 1; + count -= 1; + continue; + } + + value = *ptr + optlen; + /* Two possibilities: + * --<option> <value> + * or + * --<option>=<value> + */ + if (value[0] == '\0') { + /* value is separated with blank space */ + value = *(ptr + 1); + extra = 1; + + if (value == NULL || !*value || *value == '-') { + /* If the option value after the blank starts with '-' assume + * it is another option. */ + print_err(vcmdarg_err, exit_failure, + "Option \"%s\" requires a value.\n", *ptr); + option = NULL; + extra = 0; + ret += 1; + } + } + else if (value[0] == '=') { + /* value is after equals */ + value += 1; + extra = 0; + } + else { + /* Option isn't known. */ + ptr += 1; + count -= 1; + continue; + } + + if (option == opt_level) { + if (ws_log_set_level_str(value) == LOG_LEVEL_NONE) { + print_err(vcmdarg_err, exit_failure, + "Invalid log level \"%s\".\n", value); + ret += 1; + } + } + else if (option == opt_domain) { + ws_log_set_domain_filter(value); + } + else if (option == opt_fatal_domain) { + ws_log_set_fatal_domain_filter(value); + } + else if (option == opt_file) { + if (value == NULL) { + print_err(vcmdarg_err, exit_failure, + "Option '%s' requires an argument.\n", + option); + ret += 1; + } + else { + FILE *fp = ws_fopen(value, "w"); + if (fp == NULL) { + print_err(vcmdarg_err, exit_failure, + "Error opening file '%s' for writing: %s.\n", + value, g_strerror(errno)); + ret += 1; + } + else { + ws_log_add_custom_file(fp); + } + } + } + else if (option == opt_fatal) { + if (ws_log_set_fatal_level_str(value) == LOG_LEVEL_NONE) { + print_err(vcmdarg_err, exit_failure, + "Fatal log level must be \"critical\" or " + "\"warning\", not \"%s\".\n", value); + ret += 1; + } + } + else if (option == opt_debug) { + ws_log_set_debug_filter(value); + } + else if (option == opt_noisy) { + ws_log_set_noisy_filter(value); + } + else { + /* Option value missing or invalid, do nothing. */ + } + + /* + * We found a log option. We will remove it from + * the argv by moving up the other strings in the array. This is + * so that it doesn't generate an unrecognized option + * error further along in the initialization process. + */ + /* Include the terminating NULL in the memmove. */ + memmove(ptr, ptr + 1 + extra, (count - extra) * sizeof(*ptr)); + /* No need to increment ptr here. */ + count -= (1 + extra); + *argc_ptr -= (1 + extra); + } + + return ret; +} + + +static void free_log_filter(log_filter_t **filter_ptr) +{ + if (filter_ptr == NULL || *filter_ptr == NULL) + return; + g_strfreev((*filter_ptr)->domainv); + g_free(*filter_ptr); + *filter_ptr = NULL; +} + + +static void tokenize_filter_str(log_filter_t **filter_ptr, + const char *str_filter, + enum ws_log_level min_level) +{ + const char *sep = ",;"; + bool negated = false; + log_filter_t *filter; + + ASSERT(filter_ptr); + ASSERT(*filter_ptr == NULL); + + if (str_filter == NULL) + return; + + if (str_filter[0] == '!') { + negated = true; + str_filter += 1; + } + if (*str_filter == '\0') + return; + + filter = g_new(log_filter_t, 1); + filter->domainv = g_strsplit_set(str_filter, sep, -1); + filter->positive = !negated; + filter->min_level = min_level; + *filter_ptr = filter; +} + + +void ws_log_set_domain_filter(const char *str_filter) +{ + free_log_filter(&domain_filter); + tokenize_filter_str(&domain_filter, str_filter, LOG_LEVEL_NONE); +} + + +void ws_log_set_fatal_domain_filter(const char *str_filter) +{ + free_log_filter(&fatal_filter); + tokenize_filter_str(&fatal_filter, str_filter, LOG_LEVEL_NONE); +} + + +void ws_log_set_debug_filter(const char *str_filter) +{ + free_log_filter(&debug_filter); + tokenize_filter_str(&debug_filter, str_filter, LOG_LEVEL_DEBUG); +} + + +void ws_log_set_noisy_filter(const char *str_filter) +{ + free_log_filter(&noisy_filter); + tokenize_filter_str(&noisy_filter, str_filter, LOG_LEVEL_NOISY); +} + + +enum ws_log_level ws_log_set_fatal_level(enum ws_log_level level) +{ + if (level <= LOG_LEVEL_NONE || level >= _LOG_LEVEL_LAST) + return LOG_LEVEL_NONE; + if (level > LOG_LEVEL_ERROR) + level = LOG_LEVEL_ERROR; + if (level < LOG_LEVEL_WARNING) + level = LOG_LEVEL_WARNING; + + fatal_log_level = level; + return fatal_log_level; +} + + +enum ws_log_level ws_log_set_fatal_level_str(const char *str_level) +{ + enum ws_log_level level; + + level = string_to_log_level(str_level); + return ws_log_set_fatal_level(level); +} + + +void ws_log_set_writer(ws_log_writer_cb *writer) +{ + if (registered_log_writer_data_free) + registered_log_writer_data_free(registered_log_writer_data); + + registered_log_writer = writer; + registered_log_writer_data = NULL; + registered_log_writer_data_free = NULL; +} + + +void ws_log_set_writer_with_data(ws_log_writer_cb *writer, + void *user_data, + ws_log_writer_free_data_cb *free_user_data) +{ + if (registered_log_writer_data_free) + registered_log_writer_data_free(registered_log_writer_data); + + registered_log_writer = writer; + registered_log_writer_data = user_data; + registered_log_writer_data_free = free_user_data; +} + + +static void glib_log_handler(const char *domain, GLogLevelFlags flags, + const char *message, void * user_data _U_) +{ + enum ws_log_level level; + + /* + * The highest priority bit in the mask defines the level. We + * ignore the GLib fatal log level mask and use our own fatal + * log level setting instead. + */ + + if (flags & G_LOG_LEVEL_ERROR) + level = LOG_LEVEL_ERROR; + else if (flags & G_LOG_LEVEL_CRITICAL) + level = LOG_LEVEL_CRITICAL; + else if (flags & G_LOG_LEVEL_WARNING) + level = LOG_LEVEL_WARNING; + else if (flags & G_LOG_LEVEL_MESSAGE) + level = LOG_LEVEL_MESSAGE; + else if (flags & G_LOG_LEVEL_INFO) + level = LOG_LEVEL_INFO; + else if (flags & G_LOG_LEVEL_DEBUG) + level = LOG_LEVEL_DEBUG; + else + level = LOG_LEVEL_NONE; /* Should not happen. */ + + ws_log(domain, level, "%s", message); +} + + +#ifdef _WIN32 +static void load_registry(void) +{ + LONG lResult; + DWORD ptype; + DWORD data; + DWORD data_size = sizeof(DWORD); + + lResult = RegGetValueA(HKEY_CURRENT_USER, + "Software\\Wireshark", + LOG_HKCU_CONSOLE_OPEN, + RRF_RT_REG_DWORD, + &ptype, + &data, + &data_size); + if (lResult != ERROR_SUCCESS || ptype != REG_DWORD) { + return; + } + + ws_log_console_open = (ws_log_console_open_pref)data; +} +#endif + + +/* + * We can't write to stderr in ws_log_init() because dumpcap uses stderr + * to communicate with the parent and it will block. We have to use + * vcmdarg_err to report errors. + */ +void ws_log_init(const char *progname, + void (*vcmdarg_err)(const char *, va_list ap)) +{ + const char *env; + int fd; + + if (progname != NULL) { + registered_progname = progname; + g_set_prgname(progname); + } + + ws_tzset(); + + current_log_level = DEFAULT_LOG_LEVEL; + + if ((fd = fileno(stdout)) >= 0) + stdout_color_enabled = g_log_writer_supports_color(fd); + if ((fd = fileno(stderr)) >= 0) + stderr_color_enabled = g_log_writer_supports_color(fd); + + /* Set the GLib log handler for the default domain. */ + g_log_set_handler(NULL, G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL, + glib_log_handler, NULL); + + /* Set the GLib log handler for GLib itself. */ + g_log_set_handler("GLib", G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL, + glib_log_handler, NULL); + +#ifdef _WIN32 + load_registry(); + + /* if the user wants a console to be always there, well, we should open one for him */ + if (ws_log_console_open == LOG_CONSOLE_OPEN_ALWAYS) { + create_console(); + } +#endif + + atexit(ws_log_cleanup); + + /* Configure from environment. */ + + env = g_getenv(ENV_VAR_LEVEL); + if (env != NULL) { + if (ws_log_set_level_str(env) == LOG_LEVEL_NONE) { + print_err(vcmdarg_err, LOG_ARGS_NOEXIT, + "Ignoring invalid environment value %s=\"%s\"", + ENV_VAR_LEVEL, env); + } + } + + env = g_getenv(ENV_VAR_FATAL); + if (env != NULL) { + if (ws_log_set_fatal_level_str(env) == LOG_LEVEL_NONE) { + print_err(vcmdarg_err, LOG_ARGS_NOEXIT, + "Ignoring invalid environment value %s=\"%s\"", + ENV_VAR_FATAL, env); + } + } + + /* Alias "domain" and "domains". The plural form wins. */ + if ((env = g_getenv(ENV_VAR_DOMAIN_S)) != NULL) + ws_log_set_domain_filter(env); + else if ((env = g_getenv(ENV_VAR_DOMAIN)) != NULL) + ws_log_set_domain_filter(env); + + /* Alias "domain" and "domains". The plural form wins. */ + if ((env = g_getenv(ENV_VAR_FATAL_DOMAIN_S)) != NULL) + ws_log_set_fatal_domain_filter(env); + else if ((env = g_getenv(ENV_VAR_FATAL_DOMAIN)) != NULL) + ws_log_set_fatal_domain_filter(env); + + env = g_getenv(ENV_VAR_DEBUG); + if (env != NULL) + ws_log_set_debug_filter(env); + + env = g_getenv(ENV_VAR_NOISY); + if (env != NULL) + ws_log_set_noisy_filter(env); + +#ifdef WS_DEBUG + init_complete = true; +#endif +} + + +void ws_log_init_with_writer(const char *progname, + ws_log_writer_cb *writer, + void (*vcmdarg_err)(const char *, va_list ap)) +{ + registered_log_writer = writer; + ws_log_init(progname, vcmdarg_err); +} + + +void ws_log_init_with_writer_and_data(const char *progname, + ws_log_writer_cb *writer, + void *user_data, + ws_log_writer_free_data_cb *free_user_data, + void (*vcmdarg_err)(const char *, va_list ap)) +{ + registered_log_writer_data = user_data; + registered_log_writer_data_free = free_user_data; + ws_log_init_with_writer(progname, writer, vcmdarg_err); +} + + +#define MAGENTA "\033[35m" +#define BLUE "\033[34m" +#define CYAN "\033[36m" +#define GREEN "\033[32m" +#define YELLOW "\033[33m" +#define RED "\033[31m" +#define RESET "\033[0m" + +static inline const char *level_color_on(bool enable, enum ws_log_level level) +{ + if (!enable) + return ""; + + switch (level) { + case LOG_LEVEL_NOISY: + case LOG_LEVEL_DEBUG: + return GREEN; + case LOG_LEVEL_INFO: + case LOG_LEVEL_MESSAGE: + return CYAN; + case LOG_LEVEL_WARNING: + return YELLOW; + case LOG_LEVEL_CRITICAL: + return MAGENTA; + case LOG_LEVEL_ERROR: + return RED; + case LOG_LEVEL_ECHO: + return YELLOW; + default: + break; + } + return ""; +} + +static inline const char *color_off(bool enable) +{ + return enable ? RESET : ""; +} + +#define NANOSECS_IN_MICROSEC 1000 + +/* + * We must not call anything that might log a message + * in the log handler context (GLib might log a message if we register + * our own handler for the GLib domain). + */ +static void log_write_do_work(FILE *fp, bool use_color, + struct tm *when, long nanosecs, intmax_t pid, + const char *domain, enum ws_log_level level, + const char *file, long line, const char *func, + const char *user_format, va_list user_ap) +{ + fputs(" **", fp); + +#ifdef WS_DEBUG + if (!init_complete) + fputs(" no init!", fp); +#endif + + /* Process */ + fprintf(fp, " (%s:%"PRIdMAX")", registered_progname, pid); + + /* Timestamp */ + if (when != NULL) { + fprintf(fp, " %02d:%02d:%02d", + when->tm_hour, when->tm_min, when->tm_sec); + if (nanosecs >= 0) { + fprintf(fp, ".%06ld", nanosecs / NANOSECS_IN_MICROSEC); + } + } + + /* Domain/level */ + fprintf(fp, " [%s %s%s%s]", domain_to_string(domain), + level_color_on(use_color, level), + ws_log_level_to_string(level), + color_off(use_color)); + + /* File/line */ + if (file != NULL) { + fprintf(fp, " %s", file); + if (line >= 0) { + fprintf(fp, ":%ld", line); + } + } + + /* Any formatting changes here need to be synced with ui/capture.c:capture_input_closed. */ + fputs(" --", fp); + + /* Function name */ + if (func != NULL) + fprintf(fp, " %s():", func); + + /* User message */ + fputc(' ', fp); + vfprintf(fp, user_format, user_ap); + fputc('\n', fp); + fflush(fp); +} + + +static inline FILE *console_file(enum ws_log_level level) +{ + if (level <= LOG_LEVEL_INFO && stdout_logging_enabled) + return stdout; + return stderr; +} + + +static inline bool console_color_enabled(enum ws_log_level level) +{ + if (level <= LOG_LEVEL_INFO && stdout_logging_enabled) + return stdout_color_enabled; + return stderr_color_enabled; +} + + +static void log_write_fatal_msg(FILE *fp, intmax_t pid, const char *msg) +{ + /* Process */ + fprintf(fp, " ** (%s:%"PRIdMAX") %s", registered_progname, pid, msg); +} + + +/* + * We must not call anything that might log a message + * in the log handler context (GLib might log a message if we register + * our own handler for the GLib domain). + */ +static void log_write_dispatch(const char *domain, enum ws_log_level level, + const char *file, long line, const char *func, + ws_log_manifest_t *mft, + const char *user_format, va_list user_ap) +{ + bool fatal_event = false; + const char *fatal_msg = NULL; + va_list user_ap_copy; + + if (level >= fatal_log_level && level != LOG_LEVEL_ECHO) { + fatal_event = true; + fatal_msg = "Aborting on fatal log level exception\n"; + } + else if (fatal_filter != NULL) { + if (filter_contains(fatal_filter, domain) && fatal_filter->positive) { + fatal_event = true; + fatal_msg = "Aborting on fatal log domain exception\n"; + } + } + +#ifdef _WIN32 + if (ws_log_console_open != LOG_CONSOLE_OPEN_NEVER) { + create_console(); + } +#endif /* _WIN32 */ + + if (custom_log) { + va_copy(user_ap_copy, user_ap); + log_write_do_work(custom_log, false, + &mft->tstamp_secs, mft->nanosecs, mft->pid, + domain, level, file, line, func, + user_format, user_ap_copy); + va_end(user_ap_copy); + if (fatal_msg) { + log_write_fatal_msg(custom_log, mft->pid, fatal_msg); + } + } + + if (registered_log_writer) { + registered_log_writer(domain, level, file, line, func, fatal_msg, mft, + user_format, user_ap, registered_log_writer_data); + } + else { + log_write_do_work(console_file(level), console_color_enabled(level), + &mft->tstamp_secs, mft->nanosecs, mft->pid, + domain, level, file, line, func, + user_format, user_ap); + if (fatal_msg) { + log_write_fatal_msg(console_file(level), mft->pid, fatal_msg); + } + } + +#ifdef _WIN32 + if (fatal_event && ws_log_console_open != LOG_CONSOLE_OPEN_NEVER) { + /* wait for a key press before the following error handler will terminate the program + this way the user at least can read the error message */ + printf("\n\nPress any key to exit\n"); + _getch(); + } +#endif /* _WIN32 */ + + if (fatal_event) { + abort(); + } +} + + +void ws_logv(const char *domain, enum ws_log_level level, + const char *format, va_list ap) +{ + ws_log_manifest_t mft; + if (!msg_is_active(domain, level, &mft)) + return; + + log_write_dispatch(domain, level, NULL, -1, NULL, &mft, format, ap); +} + + +void ws_logv_full(const char *domain, enum ws_log_level level, + const char *file, long line, const char *func, + const char *format, va_list ap) +{ + ws_log_manifest_t mft; + if (!msg_is_active(domain, level, &mft)) + return; + + log_write_dispatch(domain, level, file, line, func, &mft, format, ap); +} + + +void ws_log(const char *domain, enum ws_log_level level, + const char *format, ...) +{ + ws_log_manifest_t mft; + if (!msg_is_active(domain, level, &mft)) + return; + + va_list ap; + + va_start(ap, format); + log_write_dispatch(domain, level, NULL, -1, NULL, &mft, format, ap); + va_end(ap); +} + + +void ws_log_full(const char *domain, enum ws_log_level level, + const char *file, long line, const char *func, + const char *format, ...) +{ + ws_log_manifest_t mft; + if (!msg_is_active(domain, level, &mft)) + return; + + va_list ap; + + va_start(ap, format); + log_write_dispatch(domain, level, file, line, func, &mft, format, ap); + va_end(ap); +} + + +void ws_log_fatal_full(const char *domain, enum ws_log_level level, + const char *file, long line, const char *func, + const char *format, ...) +{ + ws_log_manifest_t mft; + va_list ap; + + fill_manifest(&mft); + va_start(ap, format); + log_write_dispatch(domain, level, file, line, func, &mft, format, ap); + va_end(ap); + abort(); +} + + +void ws_log_write_always_full(const char *domain, enum ws_log_level level, + const char *file, long line, const char *func, + const char *format, ...) +{ + ws_log_manifest_t mft; + va_list ap; + + fill_manifest(&mft); + va_start(ap, format); + log_write_dispatch(domain, level, file, line, func, &mft, format, ap); + va_end(ap); +} + + +static void +append_trailer(const char *src, size_t src_length, wmem_strbuf_t *display, wmem_strbuf_t *underline) +{ + gunichar ch; + size_t hex_len; + + while (src_length > 0) { + ch = g_utf8_get_char_validated(src, src_length); + if (ch == (gunichar)-1 || ch == (gunichar)-2) { + wmem_strbuf_append_hex(display, *src); + wmem_strbuf_append_c_count(underline, '^', 4); + src += 1; + src_length -= 1; + } + else { + if (g_unichar_isprint(ch)) { + wmem_strbuf_append_unichar(display, ch); + wmem_strbuf_append_c_count(underline, ' ', 1); + } + else { + hex_len = wmem_strbuf_append_hex_unichar(display, ch); + wmem_strbuf_append_c_count(underline, ' ', hex_len); + } + const char *tmp = g_utf8_next_char(src); + src_length -= tmp - src; + src = tmp; + } + } +} + + +static char * +make_utf8_display(const char *src, size_t src_length, size_t good_length) +{ + wmem_strbuf_t *display; + wmem_strbuf_t *underline; + gunichar ch; + size_t hex_len; + + display = wmem_strbuf_create(NULL); + underline = wmem_strbuf_create(NULL); + + for (const char *s = src; s < src + good_length; s = g_utf8_next_char(s)) { + ch = g_utf8_get_char(s); + + if (g_unichar_isprint(ch)) { + wmem_strbuf_append_unichar(display, ch); + wmem_strbuf_append_c(underline, ' '); + } + else { + hex_len = wmem_strbuf_append_hex_unichar(display, ch); + wmem_strbuf_append_c_count(underline, ' ', hex_len); + } + } + + append_trailer(&src[good_length], src_length - good_length, display, underline); + + wmem_strbuf_append_c(display, '\n'); + wmem_strbuf_append(display, underline->str); + wmem_strbuf_destroy(underline); + + return wmem_strbuf_finalize(display); +} + + +void ws_log_utf8_full(const char *domain, enum ws_log_level level, + const char *file, long line, const char *func, + const char *string, ssize_t _length, const char *endptr) +{ + if (!ws_log_msg_is_active(domain, level)) + return; + + char *display; + size_t length; + size_t good_length; + + if (_length < 0) + length = strlen(string); + else + length = _length; + + if (endptr == NULL || endptr < string) { + /* Find the pointer to the first invalid byte. */ + if (g_utf8_validate(string, length, &endptr)) { + /* Valid string - should not happen. */ + return; + } + } + good_length = endptr - string; + + display = make_utf8_display(string, length, good_length); + + ws_log_write_always_full(domain, level, file, line, func, + "Invalid UTF-8 at address %p offset %zu (length = %zu):\n%s", + string, good_length, length, display); + + g_free(display); +} + + +void ws_log_buffer_full(const char *domain, enum ws_log_level level, + const char *file, long line, const char *func, + const uint8_t *ptr, size_t size, size_t max_bytes_len, + const char *msg) +{ + if (!ws_log_msg_is_active(domain, level)) + return; + + char *bufstr = bytes_to_str_maxlen(NULL, ptr, size, max_bytes_len); + + if (G_UNLIKELY(msg == NULL)) + ws_log_write_always_full(domain, level, file, line, func, + "<buffer:%p>: %s (%zu bytes)", + ptr, bufstr, size); + else + ws_log_write_always_full(domain, level, file, line, func, + "%s: %s (%zu bytes)", + msg, bufstr, size); + wmem_free(NULL, bufstr); +} + + +void ws_log_file_writer(FILE *fp, const char *domain, enum ws_log_level level, + const char *file, long line, const char *func, + ws_log_manifest_t *mft, + const char *user_format, va_list user_ap) +{ + log_write_do_work(fp, false, + &mft->tstamp_secs, mft->nanosecs, mft->pid, + domain, level, file, line, func, + user_format, user_ap); +} + + +void ws_log_console_writer(const char *domain, enum ws_log_level level, + const char *file, long line, const char *func, + ws_log_manifest_t *mft, + const char *user_format, va_list user_ap) +{ + log_write_do_work(console_file(level), console_color_enabled(level), + &mft->tstamp_secs, mft->nanosecs, mft->pid, + domain, level, file, line, func, + user_format, user_ap); +} + + +WS_DLL_PUBLIC +void ws_log_console_writer_set_use_stdout(bool use_stdout) +{ + stdout_logging_enabled = use_stdout; +} + + +static void ws_log_cleanup(void) +{ + if (registered_log_writer_data_free) { + registered_log_writer_data_free(registered_log_writer_data); + registered_log_writer_data = NULL; + } + if (custom_log) { + fclose(custom_log); + custom_log = NULL; + } + free_log_filter(&domain_filter); + free_log_filter(&debug_filter); + free_log_filter(&noisy_filter); + free_log_filter(&fatal_filter); +} + + +void ws_log_add_custom_file(FILE *fp) +{ + if (custom_log != NULL) { + fclose(custom_log); + } + custom_log = fp; +} + + +#define USAGE_LEVEL \ + "sets the active log level (\"critical\", \"warning\", etc.)" + +#define USAGE_FATAL \ + "sets level to abort the program (\"critical\" or \"warning\")" + +#define USAGE_DOMAINS \ + "comma-separated list of the active log domains" + +#define USAGE_FATAL_DOMAINS \ + "list of domains that cause the program to abort" + +#define USAGE_DEBUG \ + "list of domains with \"debug\" level" + +#define USAGE_NOISY \ + "list of domains with \"noisy\" level" + +#define USAGE_FILE \ + "file to output messages to (in addition to stderr)" + +void ws_log_print_usage(FILE *fp) +{ + fprintf(fp, "Diagnostic output:\n"); + fprintf(fp, " --log-level <level> " USAGE_LEVEL "\n"); + fprintf(fp, " --log-fatal <level> " USAGE_FATAL "\n"); + fprintf(fp, " --log-domains <[!]list> " USAGE_DOMAINS "\n"); + fprintf(fp, " --log-fatal-domains <list>\n"); + fprintf(fp, " " USAGE_FATAL_DOMAINS "\n"); + fprintf(fp, " --log-debug <[!]list> " USAGE_DEBUG "\n"); + fprintf(fp, " --log-noisy <[!]list> " USAGE_NOISY "\n"); + fprintf(fp, " --log-file <path> " USAGE_FILE "\n"); +} diff --git a/wsutil/wslog.h b/wsutil/wslog.h new file mode 100644 index 00000000..8044ad10 --- /dev/null +++ b/wsutil/wslog.h @@ -0,0 +1,458 @@ +/** @file + * + * Copyright 2021, João Valverde <j@v6e.pt> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __WSLOG_H__ +#define __WSLOG_H__ + +#include <inttypes.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdarg.h> +#include <time.h> +#include <glib.h> + +#include <ws_symbol_export.h> +#include <ws_attributes.h> +#include <ws_log_defs.h> +#include <ws_posix_compat.h> + +#ifdef WS_LOG_DOMAIN +#define _LOG_DOMAIN WS_LOG_DOMAIN +#else +#define _LOG_DOMAIN "" +#endif + +#ifdef WS_DEBUG +#define _LOG_DEBUG_ENABLED true +#else +#define _LOG_DEBUG_ENABLED false +#endif + +/* + * Define the macro WS_LOG_DOMAIN *before* including this header, + * for example: + * #define WS_LOG_DOMAIN LOG_DOMAIN_MAIN + */ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * Console open preference is stored in the Windows registry. + * HKEY_CURRENT_USER\Software\Wireshark\ConsoleOpen + */ +#define LOG_HKCU_CONSOLE_OPEN "ConsoleOpen" + +typedef enum { + LOG_CONSOLE_OPEN_NEVER, + LOG_CONSOLE_OPEN_AUTO, /* On demand. */ + LOG_CONSOLE_OPEN_ALWAYS, /* Open during startup. */ +} ws_log_console_open_pref; + +WSUTIL_EXPORT +ws_log_console_open_pref ws_log_console_open; + +typedef struct { + struct tm tstamp_secs; + long nanosecs; + intmax_t pid; +} ws_log_manifest_t; + +/** Callback for registering a log writer. */ +typedef void (ws_log_writer_cb)(const char *domain, enum ws_log_level level, + const char *file, long line, const char *func, + const char *fatal_msg, ws_log_manifest_t *mft, + const char *user_format, va_list user_ap, + void *user_data); + + +/** Callback for freeing a user data pointer. */ +typedef void (ws_log_writer_free_data_cb)(void *user_data); + + +WS_DLL_PUBLIC +void ws_log_file_writer(FILE *fp, const char *domain, enum ws_log_level level, + const char *file, long line, const char *func, + ws_log_manifest_t *mft, + const char *user_format, va_list user_ap); + + +WS_DLL_PUBLIC +void ws_log_console_writer(const char *domain, enum ws_log_level level, + const char *file, long line, const char *func, + ws_log_manifest_t *mft, + const char *user_format, va_list user_ap); + + +/** Configure log levels "info" and below to use stdout. + * + * Normally all log messages are written to stderr. For backward compatibility + * with GLib calling this function with true configures log levels "info", + * "debug" and "noisy" to be written to stdout. + */ +WS_DLL_PUBLIC +void ws_log_console_writer_set_use_stdout(bool use_stdout); + + +/** Convert a numerical level to its string representation. */ +WS_DLL_PUBLIC +WS_RETNONNULL +const char *ws_log_level_to_string(enum ws_log_level level); + + +/** Checks if a domain and level combination generate output. + * + * Returns true if a message will be printed for the domain/level combo. + */ +WS_DLL_PUBLIC +bool ws_log_msg_is_active(const char *domain, enum ws_log_level level); + + +/** Return the currently active log level. */ +WS_DLL_PUBLIC +enum ws_log_level ws_log_get_level(void); + + +/** Set the active log level. Returns the active level or LOG_LEVEL_NONE + * if level is invalid. */ +WS_DLL_PUBLIC +enum ws_log_level ws_log_set_level(enum ws_log_level level); + + +/** Set the active log level from a string. + * + * String levels are "error", "critical", "warning", "message", "info", + * "debug" and "noisy" (case insensitive). + * Returns the new log level or LOG_LEVEL NONE if the string representation + * is invalid. + */ +WS_DLL_PUBLIC +enum ws_log_level ws_log_set_level_str(const char *str_level); + + +/** Set a domain filter from a string. + * + * Domain filter is a case insensitive list separated by ',' or ';'. Only + * the domains in the filter will generate output; the others will be muted. + * Filter expressions can be preceded by '!' to invert the sense of the match. + * In this case only non-matching domains will generate output. + */ +WS_DLL_PUBLIC +void ws_log_set_domain_filter(const char *domain_filter); + +/** Set a fatal domain filter from a string. + * + * Domain filter is a case insensitive list separated by ',' or ';'. Domains + * in the filter will cause the program to abort. + */ +WS_DLL_PUBLIC +void ws_log_set_fatal_domain_filter(const char *domain_filter); + + +/** Set a debug filter from a string. + * + * A debug filter lists all domains that should have debug level output turned + * on, regardless of the global log level and domain filter. If negated + * then debug (and below) will be disabled and the others unaffected by + * the filter. + */ +WS_DLL_PUBLIC +void ws_log_set_debug_filter(const char *str_filter); + + +/** Set a noisy filter from a string. + * + * Same as ws_log_set_debug_filter() for "noisy" level. + */ +WS_DLL_PUBLIC +void ws_log_set_noisy_filter(const char *str_filter); + + +/** Set the fatal log level. + * + * Sets the log level at which calls to ws_log() will abort the program. The + * argument can be LOG_LEVEL_ERROR, LOG_LEVEL_CRITICAL or LOG_LEVEL_WARNING. + * Level LOG_LEVEL_ERROR is always fatal. + */ +WS_DLL_PUBLIC +enum ws_log_level ws_log_set_fatal_level(enum ws_log_level level); + + +/** Set the fatal log level from a string. + * + * Same as ws_log_set_fatal(), but accepts the strings "error", critical" or + * "warning" instead as arguments. + */ +WS_DLL_PUBLIC +enum ws_log_level ws_log_set_fatal_level_str(const char *str_level); + + +/** Set the active log writer. + * + * The parameter 'writer' can be NULL to use the default writer. + */ +WS_DLL_PUBLIC +void ws_log_set_writer(ws_log_writer_cb *writer); + + +/** Set the active log writer. + * + * The parameter 'writer' can be NULL to use the default writer. + * Accepts an extra user_data parameter that will be passed to + * the log writer. + */ +WS_DLL_PUBLIC +void ws_log_set_writer_with_data(ws_log_writer_cb *writer, + void *user_data, + ws_log_writer_free_data_cb *free_user_data); + + +#define LOG_ARGS_NOEXIT -1 + +/** Parses the command line arguments for log options. + * + * Returns zero for no error, non-zero for one or more invalid options. + */ +WS_DLL_PUBLIC +int ws_log_parse_args(int *argc_ptr, char *argv[], + void (*vcmdarg_err)(const char *, va_list ap), + int exit_failure); + + +/** Initializes the logging code. + * + * Must be called at startup before using the log API. If provided + * vcmdarg_err is used to print initialization errors. This usually means + * a misconfigured environment variable. + */ +WS_DLL_PUBLIC +void ws_log_init(const char *progname, + void (*vcmdarg_err)(const char *, va_list ap)); + + +/** Initializes the logging code. + * + * Can be used instead of wslog_init(). Takes an extra writer argument. + * If provided this callback will be used instead of the default writer. + */ +WS_DLL_PUBLIC +void ws_log_init_with_writer(const char *progname, + ws_log_writer_cb *writer, + void (*vcmdarg_err)(const char *, va_list ap)); + + +/** Initializes the logging code. + * + * Accepts a user data pointer in addition to the writer. This pointer will + * be provided to the writer with every invocation. If provided + * free_user_data will be called during cleanup. + */ +WS_DLL_PUBLIC +void ws_log_init_with_writer_and_data(const char *progname, + ws_log_writer_cb *writer, + void *user_data, + ws_log_writer_free_data_cb *free_user_data, + void (*vcmdarg_err)(const char *, va_list ap)); + + +/** This function is called to output a message to the log. + * + * Takes a format string and a variable number of arguments. + */ +WS_DLL_PUBLIC +void ws_log(const char *domain, enum ws_log_level level, + const char *format, ...) G_GNUC_PRINTF(3,4); + + +/** This function is called to output a message to the log. + * + * Takes a format string and a 'va_list'. + */ +WS_DLL_PUBLIC +void ws_logv(const char *domain, enum ws_log_level level, + const char *format, va_list ap); + + +/** This function is called to output a message to the log. + * + * In addition to the message this function accepts file/line/function + * information. + */ +WS_DLL_PUBLIC +void ws_log_full(const char *domain, enum ws_log_level level, + const char *file, long line, const char *func, + const char *format, ...) G_GNUC_PRINTF(6,7); + + +/** This function is called to output a message to the log. + * + * In addition to the message this function accepts file/line/function + * information. + */ +WS_DLL_PUBLIC +void ws_logv_full(const char *domain, enum ws_log_level level, + const char *file, long line, const char *func, + const char *format, va_list ap); + + +WS_DLL_PUBLIC +WS_NORETURN +void ws_log_fatal_full(const char *domain, enum ws_log_level level, + const char *file, long line, const char *func, + const char *format, ...) G_GNUC_PRINTF(6,7); + + +/* + * The if condition avoids -Wunused warnings for variables used only with + * WS_DEBUG, typically inside a ws_debug() call. The compiler will + * optimize away the dead execution branch. + */ +#define _LOG_IF_ACTIVE(active, level, file, line, func, ...) \ + do { \ + if (active) { \ + ws_log_full(_LOG_DOMAIN, level, \ + file, line, func, \ + __VA_ARGS__); \ + } \ + } while (0) + +#define _LOG_FULL(active, level, ...) \ + _LOG_IF_ACTIVE(active, level, __FILE__, __LINE__, __func__, __VA_ARGS__) + +#define _LOG_SIMPLE(active, level, ...) \ + _LOG_IF_ACTIVE(active, level, NULL, -1, NULL, __VA_ARGS__) + +/** Logs with "error" level. + * + * Accepts a format string and includes the file and function name. + * + * "error" is always fatal and terminates the program with a coredump. + */ +#define ws_error(...) \ + ws_log_fatal_full(_LOG_DOMAIN, LOG_LEVEL_ERROR, \ + __FILE__, __LINE__, __func__, __VA_ARGS__) + +/** Logs with "critical" level. + * + * Accepts a format string and includes the file and function name. + */ +#define ws_critical(...) \ + _LOG_FULL(true, LOG_LEVEL_CRITICAL, __VA_ARGS__) + +/** Logs with "warning" level. + * + * Accepts a format string and includes the file and function name. + */ +#define ws_warning(...) \ + _LOG_FULL(true, LOG_LEVEL_WARNING, __VA_ARGS__) + +/** Logs with "message" level. + * + * Accepts a format string and *does not* include the file and function + * name. This is the default log level. + */ +#define ws_message(...) \ + _LOG_SIMPLE(true, LOG_LEVEL_MESSAGE, __VA_ARGS__) + +/** Logs with "info" level. + * + * Accepts a format string and includes the file and function name. + */ +#define ws_info(...) \ + _LOG_FULL(true, LOG_LEVEL_INFO, __VA_ARGS__) + +/** Logs with "debug" level. + * + * Accepts a format string and includes the file and function name. + */ +#define ws_debug(...) \ + _LOG_FULL(_LOG_DEBUG_ENABLED, LOG_LEVEL_DEBUG, __VA_ARGS__) + +/** Logs with "noisy" level. + * + * Accepts a format string and includes the file and function name. + */ +#define ws_noisy(...) \ + _LOG_FULL(_LOG_DEBUG_ENABLED, LOG_LEVEL_NOISY, __VA_ARGS__) + +/** Used for temporary debug print outs, always active. */ +#define WS_DEBUG_HERE(...) \ + _LOG_FULL(true, LOG_LEVEL_ECHO, __VA_ARGS__) + + +WS_DLL_PUBLIC +void ws_log_utf8_full(const char *domain, enum ws_log_level level, + const char *file, long line, const char *func, + const char *string, ssize_t length, const char *endptr); + + +#define ws_log_utf8(str, len, endptr) \ + do { \ + if (_LOG_DEBUG_ENABLED) { \ + ws_log_utf8_full(LOG_DOMAIN_UTF_8, LOG_LEVEL_DEBUG, \ + __FILE__, __LINE__, __func__, \ + str, len, endptr); \ + } \ + } while (0) + + +/** This function is called to log a buffer (bytes array). + * + * Accepts an optional 'msg' argument to provide a description. + */ +WS_DLL_PUBLIC +void ws_log_buffer_full(const char *domain, enum ws_log_level level, + const char *file, long line, const char *func, + const uint8_t *buffer, size_t size, + size_t max_bytes_len, const char *msg); + + +#define ws_log_buffer(buf, size) \ + do { \ + if (_LOG_DEBUG_ENABLED) { \ + ws_log_buffer_full(_LOG_DOMAIN, LOG_LEVEL_DEBUG, \ + __FILE__, __LINE__, __func__, \ + buf, size, 36, #buf); \ + } \ + } while (0) + + +/** Auxiliary function to write custom logging functions. + * + * This function is the same as ws_log_full() but does not perform any + * domain/level filtering to avoid a useless double activation check. + * It should only be used in conjunction with a pre-check using + * ws_log_msg_is_active(). + */ +WS_DLL_PUBLIC +void ws_log_write_always_full(const char *domain, enum ws_log_level level, + const char *file, long line, const char *func, + const char *format, ...) G_GNUC_PRINTF(6,7); + + +/** Define an auxiliary file pointer where messages should be written. + * + * This file, if set, functions in addition to the registered or + * default log writer. + */ +WS_DLL_PUBLIC +void ws_log_add_custom_file(FILE *fp); + + +WS_DLL_PUBLIC +void ws_log_print_usage(FILE *fp); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __WSLOG_H__ */ diff --git a/wsutil/xtea.c b/wsutil/xtea.c new file mode 100644 index 00000000..b98de5f5 --- /dev/null +++ b/wsutil/xtea.c @@ -0,0 +1,70 @@ +/* xtea.c + * Implementation of XTEA cipher + * By Ahmad Fatoum <ahmad[AT]a3f.at> + * Copyright 2017 Ahmad Fatoum + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "xtea.h" + +#include <string.h> + +#include "pint.h" + +void decrypt_xtea_ecb(uint8_t plaintext[8], const uint8_t ciphertext[8], const uint32_t key[4], unsigned num_rounds) +{ + unsigned i; + uint32_t v[2], delta = 0x9E3779B9, sum = delta * num_rounds; + + v[0] = pntoh32(&ciphertext[0]); + v[1] = pntoh32(&ciphertext[4]); + + for (i = 0; i < num_rounds; i++) { + v[1] -= (((v[0] << 4) ^ (v[0] >> 5)) + v[0]) ^ (sum + key[(sum >> 11) & 3]); + sum -= delta; + v[0] -= (((v[1] << 4) ^ (v[1] >> 5)) + v[1]) ^ (sum + key[sum & 3]); + } + + v[0] = GUINT32_TO_BE(v[0]); + v[1] = GUINT32_TO_BE(v[1]); + + memcpy(plaintext, v, sizeof v); +} + +void decrypt_xtea_le_ecb(uint8_t plaintext[8], const uint8_t ciphertext[8], const uint32_t key[4], unsigned num_rounds) +{ + unsigned i; + uint32_t v[2], delta = 0x9E3779B9, sum = delta * num_rounds; + + v[0] = pletoh32(&ciphertext[0]); + v[1] = pletoh32(&ciphertext[4]); + + for (i = 0; i < num_rounds; i++) { + v[1] -= (((v[0] << 4) ^ (v[0] >> 5)) + v[0]) ^ (sum + key[(sum >> 11) & 3]); + sum -= delta; + v[0] -= (((v[1] << 4) ^ (v[1] >> 5)) + v[1]) ^ (sum + key[sum & 3]); + } + + v[0] = GUINT32_TO_LE(v[0]); + v[1] = GUINT32_TO_LE(v[1]); + + memcpy(plaintext, v, sizeof v); +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wsutil/xtea.h b/wsutil/xtea.h new file mode 100644 index 00000000..e166160c --- /dev/null +++ b/wsutil/xtea.h @@ -0,0 +1,39 @@ +/** @file + * + * Implementation of XTEA cipher + * By Ahmad Fatoum <ahmad[AT]a3f.at> + * Copyright 2017 Ahmad Fatoum + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __XTEA_H__ +#define __XTEA_H__ + +/* Actual XTEA is big-endian, nevertheless there exist protocols that treat every block + * as little endian, so we provide both + */ +#include "wireshark.h" + +WS_DLL_PUBLIC void decrypt_xtea_ecb(uint8_t plaintext[8], const uint8_t ciphertext[8], const uint32_t key[4], unsigned num_rounds); + +WS_DLL_PUBLIC void decrypt_xtea_le_ecb(uint8_t plaintext[8], const uint8_t ciphertext[8], const uint32_t key[4], unsigned num_rounds); + +#endif /* __XTEA_H__ */ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ |