diff --git a/.appveyor.yml b/.appveyor.yml index 637995508e..1173a7dc1c 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,25 +1,41 @@ version: '{branch}.{build}' configuration: RelWithDebInfo environment: - PYTHON: C:\Python35\python.exe - matrix: + # https://www.appveyor.com/docs/build-environment/ + # https://www.appveyor.com/docs/windows-images-software + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + CMAKE_GENERATOR: Visual Studio 16 2019 + # TODO (jdanek) upgrade to Python 38 when it can be made to work; or whichever is latest at the time + PYTHON: "C:\\Python37-x64" + QPID_PROTON_CMAKE_ARGS: "-A x64" + VCPKG_INTEGRATION: '-DCMAKE_TOOLCHAIN_FILE=C:/Tools/vcpkg/scripts/buildsystems/vcpkg.cmake' + VCPKG_DEFAULT_TRIPLET: x64-windows - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - CMAKE_GENERATOR: Visual Studio 15 + CMAKE_GENERATOR: Visual Studio 15 2017 + PYTHON: "C:\\Python37-x64" + QPID_PROTON_CMAKE_ARGS: "-A x64" VCPKG_INTEGRATION: '-DCMAKE_TOOLCHAIN_FILE=C:/Tools/vcpkg/scripts/buildsystems/vcpkg.cmake' - - CMAKE_GENERATOR: Visual Studio 12 - - CMAKE_GENERATOR: Visual Studio 10 + VCPKG_DEFAULT_TRIPLET: x64-windows + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + CMAKE_GENERATOR: Visual Studio 14 2015 + PYTHON: "C:\\Python36-x64" + QPID_PROTON_CMAKE_ARGS: "-A x64" + # vcpkg is supported on VS2015, we are just not enabling the CMake integration + # https://docs.microsoft.com/en-us/cpp/build/vcpkg?view=vs-2015 + install: - cinst -y swig +# https://www.appveyor.com/docs/lang/cpp/#vc-packaging-tool - cd C:\Tools\vcpkg - git pull - .\bootstrap-vcpkg.bat - cd %APPVEYOR_BUILD_FOLDER% -- vcpkg install jsoncpp:x86-windows -- vcpkg install jsoncpp:x64-windows +- vcpkg install jsoncpp - vcpkg integrate install -- "%PYTHON% -m pip install --user --upgrade pip" -- "%PYTHON% -m pip install --user --upgrade setuptools wheel tox" +# https://pythonhosted.org/CodeChat/appveyor.yml.html +- "%PYTHON%\\python.exe -m pip install --user --upgrade pip" +- "%PYTHON%\\python.exe -m pip install --user --upgrade setuptools wheel tox" cache: - C:\ProgramData\chocolatey\bin -> .appveyor.yml - C:\ProgramData\chocolatey\lib -> .appveyor.yml @@ -27,7 +43,7 @@ cache: before_build: - mkdir BLD - cd BLD -- cmake %VCPKG_INTEGRATION% -G "%CMAKE_GENERATOR%" -DPYTHON_EXECUTABLE=%PYTHON% %QPID_PROTON_CMAKE_ARGS% .. +- cmake %VCPKG_INTEGRATION% -G "%CMAKE_GENERATOR%" -DPYTHON_EXECUTABLE=%PYTHON%\\python.exe %QPID_PROTON_CMAKE_ARGS% .. - cd .. build: project: BLD/Proton.sln diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000000..0d0a96e5ac --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,95 @@ +name: Build + +on: [push, pull_request] + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macOS-latest] + buildType: [RelWithDebInfo] + include: + - os: windows-latest + cmake_extra: '-A x64 -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake' + cmake_generator: '-G "Visual Studio 16 2019"' + - os: macOS-latest + pkg_config_path: '/usr/local/opt/openssl@1.1/lib/pkgconfig' + cmake_extra: '-DBUILD_RUBY=no' + env: + BuildType: ${{matrix.buildType}} + BuildDir: ${{github.workspace}}/BLD + InstallPrefix: ${{github.workspace}}/INSTALL + PKG_CONFIG_PATH: ${{matrix.pkg_config_path}} + VCPKG_DEFAULT_TRIPLET: x64-windows + steps: + - uses: actions/checkout@v2 + - name: Create Build and Install directories + run: mkdir -p "${BuildDir}" "{InstallPrefix}" + shell: bash + - name: Setup python + uses: actions/setup-python@v2 + with: + python-version: 3.6 + architecture: x64 + - name: Install python dependencies + run: | + python -m pip install --upgrade pip + python -m pip install setuptools wheel tox unittest2 + - name: Install Linux dependencies + if: ${{ runner.os == 'Linux' }} + run: | + sudo apt install -y swig libpython3-dev libsasl2-dev libjsoncpp-dev + - name: Install Windows dependencies + if: ${{ runner.os == 'Windows' }} + run: | + choco install -y swig --version=4.0.1 + vcpkg install jsoncpp + vcpkg integrate install + - name: Install MacOS dependencies + if: ${{ runner.os == 'macOS' }} + run: | + brew install libuv swig pkgconfig jsoncpp + - name: cmake configure + working-directory: ${{env.BuildDir}} + run: cmake "${{github.workspace}}" "-DCMAKE_BUILD_TYPE=${BuildType}" "-DCMAKE_INSTALL_PREFIX=${InstallPrefix}" ${{matrix.cmake_extra}} + shell: bash + - name: cmake build/install + run: cmake --build "${BuildDir}" --config ${BuildType} -t install + shell: bash + - name: Upload Install + uses: actions/upload-artifact@v2 + with: + name: qpid_proton_pkg_${{matrix.os}}_${{matrix.buildType}} + path: ${{env.InstallPrefix}} + - name: Upload python packages + uses: actions/upload-artifact@v2 + with: + name: python-pkgs + path: ${{env.BuildDir}}/python/pkgs + - name: ctest + continue-on-error: true + working-directory: ${{env.BuildDir}} + run: ctest -C ${BuildType} -V -T Test --no-compress-output + shell: bash + - name: Upload Test results + uses: actions/upload-artifact@v2 + with: + name: Test_Results_${{matrix.os}}_${{matrix.buildType}} + path: ${{env.BuildDir}}/Testing/**/*.xml + - name: Upload Python & C build directories on failure + uses: actions/upload-artifact@v2 + if: failure() + with: + name: Debug-python-C-BLD_${{matrix.os}}_${{matrix.buildType}} + path: | + ${{env.BuildDir}}/c + ${{env.BuildDir}}/python + - name: Environment (Linux/Windows) + if: ${{ always() && runner.os != 'macOS' }} + run: env -0 | sort -z | tr '\0' '\n' + shell: bash + - name: Environment (macOS) + if: ${{ always() && runner.os == 'macOS' }} + run: env | sort + shell: bash diff --git a/.travis.yml b/.travis.yml index 30ef2609bb..f75c49331d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,15 +17,10 @@ # under the License # -sudo: false -language: cpp - -matrix: +jobs: include: - os: linux - dist: bionic dist: xenial - sudo: true language: cpp compiler: gcc env: @@ -37,37 +32,67 @@ matrix: env: - OPENSSL_ia32cap='0x00000000' # c-threaderciser test hangs on older clang + - QPID_PROTON_CMAKE_ARGS='-DENABLE_LINKTIME_OPTIMIZATION=OFF' - QPID_PROTON_CTEST_ARGS="--exclude-regex 'c-threaderciser'" - - os: linux - dist: bionic - sudo: true - compiler: - - gcc + - name: static libs + os: linux + dist: focal + language: cpp + compiler: gcc + env: + - PYTHON=python3 + - QPID_PROTON_CMAKE_ARGS='-DBUILD_STATIC_LIBS=ON' + - name: benchmarks + os: linux + dist: focal + language: cpp + compiler: gcc env: + - PYTHON=python3 - QPID_PROTON_CMAKE_ARGS='-DENABLE_BENCHMARKS=ON -DRUNTIME_CHECK=OFF' before_install: - sudo apt-get install -y libbenchmark-dev - - os: linux - dist: bionic - compiler: gcc + - name: gcc asan + os: linux + dist: focal + before_install: + - sudo apt-get install -y gcc-10 g++-10 env: + - CC=gcc-10 + - CXX=g++-10 + - PYTHON=python3 # python-tox-test fails and ruby tests segfault - QPID_PROTON_CMAKE_ARGS='-DRUNTIME_CHECK=asan -DENABLE_TOX_TEST=OFF' - QPID_PROTON_CTEST_ARGS='-E ^ruby.*' - - os: linux - dist: bionic - compiler: clang + - name: clang asan + os: linux + dist: focal + before_install: + - sudo apt-get install -y clang-10 env: - - QPID_PROTON_CMAKE_ARGS='-DRUNTIME_CHECK=asan -DENABLE_TOX_TEST=OFF' + - CC=clang-10 + - CXX=clang++-10 + - PYTHON=python3 + - QPID_PROTON_CMAKE_ARGS='-DRUNTIME_CHECK=asan -DENABLE_LINKTIME_OPTIMIZATION=OFF -DENABLE_TOX_TEST=OFF' # otherwise, on Travis ldd gives `libclang_rt.asan-x86_64.so => not found` and binaries don't work - - LD_LIBRARY_PATH=/usr/local/clang-7.0.0/lib/clang/7.0.0/lib/linux/ - - os: linux - dist: bionic + - LD_LIBRARY_PATH=/usr/lib/llvm-10/lib/clang/10.0.0/lib/linux/ + - name: gcc tsan + os: linux + dist: focal + before_install: + - sudo apt-get install -y gcc-10 g++-10 env: + - CC=gcc-10 + - CXX=g++-10 + - PYTHON=python3 # python-test, python-integration-test, and python-tox-test segfault - QPID_PROTON_CMAKE_ARGS='-DRUNTIME_CHECK=tsan -DENABLE_TOX_TEST=OFF' - QPID_PROTON_CTEST_ARGS="-E 'python-test|python-integration-test'" - - os: linux + - name: coverage + os: linux + dist: bionic + language: cpp + compiler: gcc env: - QPID_PROTON_CMAKE_ARGS='-DCMAKE_BUILD_TYPE=Coverage' after_success: @@ -75,6 +100,7 @@ matrix: - os: osx osx_image: xcode9.4 + language: cpp compiler: clang env: - PATH="/usr/local/opt/python/libexec/bin:/usr/local/bin:$PATH" @@ -86,6 +112,7 @@ matrix: - os: osx osx_image: xcode11.3 + language: cpp compiler: clang env: - PATH="/usr/local/opt/python/libexec/bin:/usr/local/bin:$PATH" @@ -96,7 +123,7 @@ matrix: - QPID_PROTON_CTEST_ARGS="--exclude-regex 'c-threaderciser|python-tox-test|ruby.*'" addons: - # Ubuntu 16.04 APT dependencies, https://packages.ubuntu.com/ + # Ubuntu APT dependencies, https://packages.ubuntu.com/ apt: packages: - cmake @@ -123,14 +150,17 @@ addons: update: true install: -- python -m pip install --user --upgrade pip -- python -m pip install --user coverage setuptools wheel tox -- gem install minitest simplecov codecov +- echo "Using PYTHON=${PYTHON:=python}" +# use older version of virtualenv to workaround https://github.com/pypa/virtualenv/issues/1873 +- ${PYTHON} -m pip install --user --upgrade pip +- ${PYTHON} -m pip install --user coverage setuptools wheel tox virtualenv==20.0.23 +# PROTON-2125 suppress annoying deprecation warning from Minitest in Ruby tests +- gem install minitest:4.3.2 simplecov codecov before_script: - mkdir build - cd build -- cmake .. -DCMAKE_INSTALL_PREFIX=$PWD/install ${QPID_PROTON_CMAKE_ARGS} +- cmake .. -DCMAKE_INSTALL_PREFIX=$PWD/install -DPYTHON_EXECUTABLE="$(which ${PYTHON})" ${QPID_PROTON_CMAKE_ARGS} script: - cmake --build . --target install -- -j$(nproc) && eval ctest -V ${QPID_PROTON_CTEST_ARGS} diff --git a/CMakeLists.txt b/CMakeLists.txt index c5962192bf..d75dfdb651 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,22 +49,43 @@ set (PN_C_INCLUDE_DIR "${CMAKE_BINARY_DIR}/c/include") set (PN_C_LIBRARY_DIR "${CMAKE_BINARY_DIR}/c") set (PN_C_SOURCE_DIR "${CMAKE_BINARY_DIR}/c/src") -## C++ +## C +if (DEFINED CMAKE_C_COMPILE_FEATURES) + set(CMAKE_C_STANDARD 99) + set(CMAKE_C_EXTENSIONS OFF) + set(CMAKE_C_STANDARD_REQUIRED ON) +else() + set (C_STANDARD_GNU -std=c99) + set (C_EXTENDED_GNU -std=gnu99) + set (C_STANDARD_Clang -std=c99) + set (C_EXTENDED_Clang -std=gnu99) +endif() -check_language (CXX) +set (C_STANDARD_FLAGS ${C_STANDARD_${CMAKE_C_COMPILER_ID}}) +set (C_EXTENDED_FLAGS ${C_EXTENDED_${CMAKE_C_COMPILER_ID}}) +## C++ +check_language (CXX) if (CMAKE_CXX_COMPILER) enable_language(CXX) -endif() -# TODO - Should change this test to take account of recent MSVC that does support C99 -if (MSVC) - # No C99 capability, use C++ - set(DEFAULT_BUILD_WITH_CXX ON) -endif (MSVC) - -if (CMAKE_CXX_COMPILER) - option(BUILD_WITH_CXX "Compile Proton using C++" ${DEFAULT_BUILD_WITH_CXX}) + # This effectively checks for cmake version 3.1 or later + if (DEFINED CMAKE_CXX_COMPILE_FEATURES) + set(CMAKE_CXX_STANDARD 11) + set(CMAKE_CXX_EXTENSIONS OFF) + # Every supported version of Visual Studio (2015 onwards) supports enough C++11 that we + # no longer need to run the individual feature tests + else () + include(CheckCXXCompilerFlag) + # These flags work with GCC/Clang/SunPro compilers + check_cxx_compiler_flag("-std=c++11" ACCEPTS_CXX11) + check_cxx_compiler_flag("-std=c++0x" ACCEPTS_CXX0X) + if (ACCEPTS_CXX11) + set(CXX_STANDARD "-std=c++11") + elseif(ACCEPTS_CXX0X) + set(CXX_STANDARD "-std=c++0x") + endif() + endif () endif() # Build static C and C++ libraries in addition to shared libraries. @@ -138,30 +159,31 @@ option(ENABLE_FUZZ_TESTING "Enable building fuzzers and regression testing with option(ENABLE_BENCHMARKS "Enable building and running benchmarks with Google Benchmark" ${DEFAULT_BENCHMARKS}) # Set any additional compiler specific flags +set (LTO_GNU "-flto -fno-fat-lto-objects") +set (LTO_Clang "-flto=thin") +set (LTO_MSVC "/GL") +set (LINK_LTO_GNU "-flto -fno-fat-lto-objects") +set (LINK_LTO_Clang "-flto=thin") +set (LINK_LTO_MSVC "/LTCG") + +if (ENABLE_LINKTIME_OPTIMIZATION) + set (LTO ${LTO_${CMAKE_C_COMPILER_ID}}) + set (LINK_LTO ${LINK_LTO_${CMAKE_C_COMPILER_ID}}) +endif (ENABLE_LINKTIME_OPTIMIZATION) + if (CMAKE_COMPILER_IS_GNUCC) if (ENABLE_WARNING_ERROR) set (WERROR "-Werror") endif (ENABLE_WARNING_ERROR) set (COMPILE_WARNING_FLAGS "${WERROR} -Wall -pedantic-errors") - # C++ allow "%z" format specifier and variadic macros - set (CXX_WARNING_FLAGS "${COMPILE_WARNING_FLAGS} -Wno-format -Wno-variadic-macros") - if (NOT BUILD_WITH_CXX) - set (COMPILE_WARNING_FLAGS "${COMPILE_WARNING_FLAGS} -Wstrict-prototypes -Wc++-compat -Wvla -Wsign-compare -Wwrite-strings") - set (COMPILE_LANGUAGE_FLAGS "-std=c99") - set (COMPILE_PLATFORM_FLAGS "-std=gnu99") - else (NOT BUILD_WITH_CXX) - set (COMPILE_WARNING_FLAGS "${CXX_WARNING_FLAGS}") - endif (NOT BUILD_WITH_CXX) + set (CXX_WARNING_FLAGS "${COMPILE_WARNING_FLAGS}") + set (COMPILE_WARNING_FLAGS "${COMPILE_WARNING_FLAGS} -Wstrict-prototypes -Wvla -Wsign-compare -Wwrite-strings") if (ENABLE_UNDEFINED_ERROR) set (CATCH_UNDEFINED "-Wl,--no-undefined") set (ALLOW_UNDEFINED "-Wl,--allow-shlib-undefined") endif (ENABLE_UNDEFINED_ERROR) - if (ENABLE_LINKTIME_OPTIMIZATION) - set (LTO "-flto") - endif (ENABLE_LINKTIME_OPTIMIZATION) - if (ENABLE_HIDE_UNEXPORTED_SYMBOLS) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden") @@ -175,7 +197,6 @@ endif (CMAKE_COMPILER_IS_GNUCC) if (CMAKE_C_COMPILER_ID MATCHES "Clang") set (COMPILE_WARNING_FLAGS "-Wall -pedantic") - set (COMPILE_LANGUAGE_FLAGS "-std=c99") if (ENABLE_WARNING_ERROR) set (COMPILE_WARNING_FLAGS "-Werror ${COMPILE_WARNING_FLAGS}") endif (ENABLE_WARNING_ERROR) diff --git a/INSTALL.md b/INSTALL.md index 76f56f64e5..38fcc797a9 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -10,8 +10,8 @@ Dependencies Cross-platform dependencies - CMake 2.8.12+ + - Python (required to build core C library, minimum version depends on platform) - Swig 1.3+ (for the bindings) - - Python 2.6+ (for the Python binding) - Ruby 1.9+ (for the Ruby binding) - Go 1.11+ (for the Go binding) @@ -22,10 +22,12 @@ Linux dependencies - Cyrus SASL 2.1+ (for SASL support) - OpenSSL 1.0+ (for SSL support) - JsonCpp 1.8+ for C++ connection configuration file support + - Python 2.6+ (for the Python binding) Windows dependencies - - Visual Studio 2013 or newer (regular or C++ Express) + - Visual Studio 2015 or newer (regular or C++ Express) + - Python 3.5+ (for the Python binding) CMake (Linux) ------------- @@ -107,7 +109,7 @@ Studio and used to build the Proton library. The following packages must be installed: - - Visual Studio 2013 or newer (regular or C++ Express) + - Visual Studio 2015 or newer (regular or C++ Express) - Python (www.python.org) - CMake (www.cmake.org) @@ -117,9 +119,10 @@ Additional packages are required for the language bindings: - Development headers and libraries for the language of choice Notes: - - Be sure to install relevant Microsoft Service Packs and updates - python.exe, cmake.exe and swig.exe _must_ all be added to your PATH + - Chocolatey is a useful tool that can be used to install cmake, python + and swig (see https://chocolatey.org/) To generate the Visual Studio project files, from the directory where you found this `INSTALL.md` file: diff --git a/azure-pipelines/azure-pipelines.yml b/azure-pipelines/azure-pipelines.yml index 4ba1f47a5b..67b3429d1c 100644 --- a/azure-pipelines/azure-pipelines.yml +++ b/azure-pipelines/azure-pipelines.yml @@ -8,30 +8,44 @@ # variables: Config: 'RelWithDebInfo' - PythonVersion: '3.6' + PythonVersion: '3.7' PythonArch: 'x64' CmakeConfigExtraArgs: '' + StaticLibs: yes jobs: -- job: Windows +- job: Windows_VS2019 + variables: + CmakeConfigExtraArgs: '-A x64 -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake' + CmakeGenerator: '-G "Visual Studio 16 2019"' + VCPKG_DEFAULT_TRIPLET: x64-windows + pool: + vmImage: 'windows-2019' + steps: + - script: | + choco install -y swig --version=4.0.1 + vcpkg install jsoncpp + vcpkg integrate install + name: InstallExtraStuff + - template: steps.yml +- job: Windows_VS2017 variables: PythonArch: 'x86' CmakeConfigExtraArgs: '-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake' + CmakeGenerator: '-G "Visual Studio 15 2017"' + VCPKG_DEFAULT_TRIPLET: x86-windows pool: vmImage: 'vs2017-win2016' steps: - - task: UsePythonVersion@0 - inputs: - versionSpec: $(PythonVersion) - addToPath: true - architecture: $(PythonArch) - script: | - choco install swig + choco install -y swig --version=4.0.1 vcpkg install jsoncpp vcpkg integrate install name: InstallExtraStuff - template: steps.yml - job: Ubuntu + variables: + PythonVersion: '3.8' pool: vmImage: 'ubuntu-18.04' steps: @@ -40,14 +54,14 @@ jobs: - template: steps.yml - job: MacOS variables: + PythonVersion: '3.6' PKG_CONFIG_PATH: '/usr/local/opt/openssl@1.1/lib/pkgconfig' - CmakeConfigExtraArgs: '-DPYTHON_EXECUTABLE=/usr/local/bin/python3 -DBUILD_RUBY=no' + CmakeConfigExtraArgs: '-DBUILD_RUBY=no' pool: vmImage: 'macOS-10.15' steps: - script: | brew update brew install libuv swig pkgconfig openssl@1.1 jsoncpp - brew upgrade python@3 python@2 || true name: InstallExtraStuff - template: steps.yml diff --git a/azure-pipelines/steps.yml b/azure-pipelines/steps.yml index ec481d64b0..79447278d7 100644 --- a/azure-pipelines/steps.yml +++ b/azure-pipelines/steps.yml @@ -1,6 +1,5 @@ steps: - task: UsePythonVersion@0 - enabled: false inputs: versionSpec: $(PythonVersion) addToPath: true @@ -13,7 +12,7 @@ steps: name: CMakeConfigure inputs: workingDirectory: 'BLD' - cmakeArgs: $(Build.SourcesDirectory) $(CmakeConfigExtraArgs) + cmakeArgs: $(CmakeGenerator) $(Build.SourcesDirectory) -DBUILD_STATIC_LIBS=$(StaticLibs) $(CmakeConfigExtraArgs) - task: CMake@1 name: CMakeBuild inputs: diff --git a/c/CMakeLists.txt b/c/CMakeLists.txt index 44a97458b6..9e224deb54 100644 --- a/c/CMakeLists.txt +++ b/c/CMakeLists.txt @@ -80,7 +80,7 @@ add_custom_target( DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/src/protocol.h ${CMAKE_CURRENT_BINARY_DIR}/src/encodings.h ) -file (GLOB_RECURSE source_files "src/*.[ch]") +file (GLOB_RECURSE source_files "src/*.h" "src/*.c" "src/*.cpp") foreach (sfile ${source_files}) file (RELATIVE_PATH rfile ${CMAKE_CURRENT_SOURCE_DIR} ${sfile}) @@ -118,7 +118,7 @@ if (SSL_IMPL STREQUAL openssl) set (pn_ssl_impl src/ssl/openssl.c) set (SSL_LIB OpenSSL::SSL Threads::Threads) elseif (SSL_IMPL STREQUAL schannel) - set (pn_ssl_impl src/ssl/schannel.c) + set (pn_ssl_impl src/ssl/schannel.cpp) set (SSL_LIB Crypt32.lib Secur32.lib) else () set (pn_ssl_impl src/ssl/ssl_stub.c) @@ -160,18 +160,6 @@ else (PN_WINAPI) endif (STRERROR_R_IN_LIBC) endif (PN_WINAPI) -CHECK_SYMBOL_EXISTS(atoll "stdlib.h" C99_ATOLL) -if (C99_ATOLL) - list(APPEND PLATFORM_DEFINITIONS "USE_ATOLL") -else (C99_ATOLL) - CHECK_SYMBOL_EXISTS(_atoi64 "stdlib.h" WINAPI_ATOI64) - if (WINAPI_ATOI64) - list(APPEND PLATFORM_DEFINITIONS "USE_ATOI64") - else (WINAPI_ATOI64) - message(FATAL_ERROR "No atoll API found") - endif (WINAPI_ATOI64) -endif (C99_ATOLL) - if (PN_WINAPI) set (PLATFORM_LIBS ws2_32 Rpcrt4) list(APPEND PLATFORM_DEFINITIONS "PN_WINAPI") @@ -187,17 +175,17 @@ if (MSVC) ) endif (MSVC) -# Ensure that examples build with c90 on gcc/clang, to deal with older c++03-as-c compilers. -set(C_EXAMPLE_FLAGS_GNU "-std=iso9899:1990 -pedantic") -set(C_EXAMPLE_FLAGS_Clang "-std=iso9899:1990 -pedantic") +# Ensure that examples build with c99 on gcc/clang +set(C_EXAMPLE_FLAGS_GNU "-pedantic") +set(C_EXAMPLE_FLAGS_Clang "-pedantic") # Flags for example self-test build -set(C_EXAMPLE_FLAGS "${COMPILE_WARNING_FLAGS} ${C_EXAMPLE_FLAGS_${CMAKE_C_COMPILER_ID}}") +set(C_EXAMPLE_FLAGS "${C_STANDARD_FLAGS} ${COMPILE_WARNING_FLAGS} ${C_EXAMPLE_FLAGS_${CMAKE_C_COMPILER_ID}}") set(C_EXAMPLE_LINK_FLAGS "${SANITIZE_FLAGS}") set(qpid-proton-platform_GNU src/compiler/gcc/start.c) set(qpid-proton-platform_Clang src/compiler/gcc/start.c) -set(qpid-proton-platform_MSVC src/compiler/msvc/snprintf.c src/compiler/msvc/start.c) +set(qpid-proton-platform_MSVC src/compiler/msvc/start.c) set(qpid-proton-platform ${qpid-proton-platform_${CMAKE_C_COMPILER_ID}}) @@ -226,7 +214,7 @@ set (qpid-proton-layers-all src/sasl/cyrus_sasl.c src/sasl/cyrus_stub.c src/ssl/openssl.c - src/ssl/schannel.c + src/ssl/schannel.cpp src/ssl/ssl_stub.c ) @@ -314,6 +302,7 @@ set (qpid-proton-include include/proton/netaddr.h include/proton/object.h include/proton/proactor.h + include/proton/raw_connection.h include/proton/sasl.h include/proton/sasl-plugin.h include/proton/session.h @@ -341,26 +330,25 @@ set (qpid-proton-include-extra set(PROACTOR "" CACHE STRING "Override default proactor, one of: epoll, libuv, iocp, none") string(TOLOWER "${PROACTOR}" PROACTOR) +set (qpid-proton-proactor-common + src/proactor/proactor-internal.c + src/proactor/netaddr-internal.c + src/proactor/raw_connection.c + ) + if (PROACTOR STREQUAL "epoll" OR (NOT PROACTOR AND NOT BUILD_PROACTOR)) check_symbol_exists(epoll_wait "sys/epoll.h" HAVE_EPOLL) if (HAVE_EPOLL) set (PROACTOR_OK epoll) - set (qpid-proton-proactor src/proactor/epoll.c src/proactor/proactor-internal.c) - set (PROACTOR_LIBS Threads::Threads) - set_source_files_properties (${qpid-proton-proactor} PROPERTIES - COMPILE_FLAGS "${COMPILE_WARNING_FLAGS} ${COMPILE_LANGUAGE_FLAGS} ${LTO}" - ) + set (qpid-proton-proactor src/proactor/epoll.c src/proactor/epoll_raw_connection.c ${qpid-proton-proactor-common}) + set (PROACTOR_LIBS Threads::Threads ${TIME_LIB}) endif() endif() if (PROACTOR STREQUAL "iocp" OR (NOT PROACTOR AND NOT PROACTOR_OK)) if(WIN32 AND NOT CYGWIN) set (PROACTOR_OK iocp) - set (qpid-proton-proactor src/proactor/win_iocp.c src/proactor/proactor-internal.c) - set_source_files_properties (${qpid-proton-proactor} PROPERTIES - COMPILE_FLAGS "${COMPILE_WARNING_FLAGS} ${COMPILE_PLATFORM_FLAGS} ${LTO}" - COMPILE_DEFINITIONS "${PLATFORM_DEFINITIONS}" - ) + set (qpid-proton-proactor src/proactor/win_iocp.cpp ${qpid-proton-proactor-common}) endif(WIN32 AND NOT CYGWIN) endif() @@ -368,11 +356,8 @@ if (PROACTOR STREQUAL "libuv" OR (NOT PROACTOR AND NOT PROACTOR_OK)) find_package(Libuv) if (Libuv_FOUND) set (PROACTOR_OK libuv) - set (qpid-proton-proactor src/proactor/libuv.c src/proactor/proactor-internal.c) + set (qpid-proton-proactor src/proactor/libuv.c ${qpid-proton-proactor-common}) set (PROACTOR_LIBS Libuv::Libuv) - set_source_files_properties (${qpid-proton-proactor} PROPERTIES - COMPILE_FLAGS "${COMPILE_WARNING_FLAGS} ${COMPILE_LANGUAGE_FLAGS} ${LTO}" - ) endif() endif() @@ -388,30 +373,18 @@ set_source_files_properties ( ${qpid-proton-core} ${qpid-proton-layers} ${qpid-proton-extra} + ${qpid-proton-platform} PROPERTIES - COMPILE_FLAGS "${COMPILE_WARNING_FLAGS} ${COMPILE_LANGUAGE_FLAGS} ${LTO}" + COMPILE_FLAGS "${COMPILE_WARNING_FLAGS} ${C_STANDARD_FLAGS}" ) set_source_files_properties ( - ${qpid-proton-platform} ${qpid-proton-platform-io} PROPERTIES - COMPILE_FLAGS "${COMPILE_WARNING_FLAGS} ${COMPILE_PLATFORM_FLAGS} ${LTO}" + COMPILE_FLAGS "${COMPILE_WARNING_FLAGS} ${C_EXTENDED_FLAGS}" COMPILE_DEFINITIONS "${PLATFORM_DEFINITIONS}" ) -if (BUILD_WITH_CXX) - set_source_files_properties ( - ${qpid-proton-core} - ${qpid-proton-proactor} - ${qpid-proton-layers} - ${qpid-proton-extra} - ${qpid-proton-platform} - ${qpid-proton-platform-io} - PROPERTIES LANGUAGE CXX - ) -endif (BUILD_WITH_CXX) - set(qpid-proton-core-src ${qpid-proton-core} ${qpid-proton-layers} @@ -422,27 +395,44 @@ set(qpid-proton-core-src add_library (qpid-proton-core-objects OBJECT ${qpid-proton-core-src}) add_dependencies (qpid-proton-core-objects generated_c_files) -set_target_properties(qpid-proton-core-objects PROPERTIES POSITION_INDEPENDENT_CODE ON) +set_target_properties(qpid-proton-core-objects PROPERTIES + POSITION_INDEPENDENT_CODE ON + COMPILE_FLAGS "${LTO}" + LINK_FLAGS "${LINK_LTO}") target_compile_definitions(qpid-proton-core-objects PRIVATE qpid_proton_core_EXPORTS) # Can't use target_link_libraries() because cmake 2.8.12 doesn't allow object libraries as the first param # otherwise for cmake 3.9 and on this would be: -# target_link_libraries (qpid-proton-core-objects ${SSL_LIB} ${SASL_LIB} ${TIME_LIB} ${PLATFORM_LIBS}) +# target_link_libraries (qpid-proton-core-objects ${SSL_LIB} ${SASL_LIB} ${PLATFORM_LIBS}) target_compile_definitions(qpid-proton-core-objects PRIVATE $) target_compile_options (qpid-proton-core-objects PRIVATE $) target_include_directories(qpid-proton-core-objects PRIVATE $) +add_library (qpid-proton-platform-io-objects OBJECT ${qpid-proton-platform-io}) +set_target_properties(qpid-proton-platform-io-objects PROPERTIES + POSITION_INDEPENDENT_CODE ON + C_EXTENSIONS ON + COMPILE_FLAGS "${LTO}" + LINK_FLAGS "${LINK_LTO}") +target_compile_definitions(qpid-proton-platform-io-objects PRIVATE qpid_proton_EXPORTS) + +target_compile_definitions(qpid-proton-platform-io-objects PRIVATE $) +target_compile_options (qpid-proton-platform-io-objects PRIVATE $) +target_include_directories(qpid-proton-platform-io-objects PRIVATE $) + add_library (qpid-proton-core SHARED $) -target_link_libraries (qpid-proton-core ${SSL_LIB} ${SASL_LIB} ${TIME_LIB} ${PLATFORM_LIBS}) +target_link_libraries (qpid-proton-core LINK_PRIVATE ${SSL_LIB} ${SASL_LIB} ${PLATFORM_LIBS}) set_target_properties (qpid-proton-core PROPERTIES VERSION "${PN_LIB_CORE_VERSION}" SOVERSION "${PN_LIB_CORE_MAJOR_VERSION}" - LINK_FLAGS "${CATCH_UNDEFINED} ${LTO}" + LINK_FLAGS "${CATCH_UNDEFINED} ${LINK_LTO}" ) if (BUILD_STATIC_LIBS) add_library (qpid-proton-core-static STATIC ${qpid-proton-core-src}) + target_compile_definitions(qpid-proton-core-static PUBLIC PROTON_DECLARE_STATIC) + target_link_libraries (qpid-proton-core-static ${SSL_LIB} ${SASL_LIB} ${PLATFORM_LIBS}) endif(BUILD_STATIC_LIBS) set(qpid-proton-noncore-src @@ -450,38 +440,33 @@ set(qpid-proton-noncore-src ${qpid-proton-proactor} # Proton Reactor/Messenger ${qpid-proton-extra} - ${qpid-proton-platform-io} ${qpid-proton-include-extra} ) -add_library (qpid-proton SHARED $ ${qpid-proton-noncore-src}) +add_library (qpid-proton SHARED $ $ ${qpid-proton-noncore-src}) target_link_libraries (qpid-proton LINK_PRIVATE ${SSL_LIB} ${SASL_LIB} ${TIME_LIB} ${PLATFORM_LIBS} ${PROACTOR_LIBS}) set_target_properties (qpid-proton PROPERTIES VERSION "${PN_LIB_LEGACY_VERSION}" SOVERSION "${PN_LIB_LEGACY_MAJOR_VERSION}" - LINK_FLAGS "${CATCH_UNDEFINED} ${LTO}" + LINK_FLAGS "${CATCH_UNDEFINED} ${LINK_LTO}" + COMPILE_FLAGS "${LTO}" ) if (BUILD_STATIC_LIBS) - add_library(qpid-proton-static STATIC ${qpid-proton-core-src} ${qpid-proton-noncore-src}) + add_library (qpid-proton-platform-io-static OBJECT ${qpid-proton-platform-io}) + set_target_properties(qpid-proton-platform-io-static PROPERTIES + C_EXTENSIONS ON) + add_library(qpid-proton-static STATIC ${qpid-proton-core-src} $ ${qpid-proton-noncore-src}) + target_compile_definitions(qpid-proton-static PUBLIC PROTON_DECLARE_STATIC) + target_link_libraries (qpid-proton-static ${SSL_LIB} ${SASL_LIB} ${TIME_LIB} ${PLATFORM_LIBS} ${PROACTOR_LIBS}) endif(BUILD_STATIC_LIBS) -if (MSVC) - # Add a phony dependency for Windows builds to serialize creation - # of generated files. See issue PROTON-1376. - # When a Windows build creates src/encodings.h and src/protocol.h - # only once then this can be removed. - add_dependencies(qpid-proton qpid-proton-core) -endif (MSVC) - -if (MSVC) - # guard against use of C99 violating functions on Windows - include(WindowsC99CheckDef) -endif(MSVC) - if (qpid-proton-proactor) set(HAS_PROACTOR True) + set_source_files_properties (${qpid-proton-proactor} PROPERTIES + COMPILE_FLAGS "${COMPILE_WARNING_FLAGS} ${C_STANDARD_FLAGS}" + ) add_library (qpid-proton-proactor SHARED ${qpid-proton-proactor}) target_link_libraries (qpid-proton-proactor LINK_PUBLIC qpid-proton-core) target_link_libraries (qpid-proton-proactor LINK_PRIVATE ${PLATFORM_LIBS} ${PROACTOR_LIBS}) @@ -489,10 +474,13 @@ if (qpid-proton-proactor) PROPERTIES VERSION "${PN_LIB_PROACTOR_VERSION}" SOVERSION "${PN_LIB_PROACTOR_MAJOR_VERSION}" - LINK_FLAGS "${CATCH_UNDEFINED} ${LTO}" - ) + LINK_FLAGS "${CATCH_UNDEFINED} ${LINK_LTO}" + COMPILE_FLAGS "${LTO}" + ) if (BUILD_STATIC_LIBS) add_library (qpid-proton-proactor-static STATIC ${qpid-proton-proactor}) + target_compile_definitions(qpid-proton-proactor-static PUBLIC PROTON_DECLARE_STATIC) + target_link_libraries (qpid-proton-proactor-static ${PLATFORM_LIBS} ${PROACTOR_LIBS}) endif(BUILD_STATIC_LIBS) endif() diff --git a/c/docs/logging.md b/c/docs/logging.md index b2279acfa1..a4c2966802 100644 --- a/c/docs/logging.md +++ b/c/docs/logging.md @@ -19,7 +19,7 @@ There are several other (less used) environment variables which are supported: The last two variables are still supported, but their effect may not be to turn on exactly the same logging messages as prior to the introduction of the Logger API. -### Logger control introduced with the @ref logger API +### Logger control introduced with the logger API The @ref logger API uses a single environment variable to control the default logging state - `PN_LOG`. This can include a number of strings which correspond to log levels to turn on, these are in descending order of importance (case is not significant): @@ -38,7 +38,7 @@ For example: * Frame * Raw -These string are equivalent to the frame and raw frame protocol traces from `PN_TRACE_FRM` and `PN_TRACE_RAW`, they will ignore any '+' appended to them. For example: +These strings are equivalent to the frame and raw frame protocol traces from `PN_TRACE_FRM` and `PN_TRACE_RAW`, they will ignore any '+' appended to them. For example: PN_LOG='frame' ./proton_program diff --git a/c/docs/user.doxygen.in b/c/docs/user.doxygen.in index ae6894a1ca..8034e6cba9 100644 --- a/c/docs/user.doxygen.in +++ b/c/docs/user.doxygen.in @@ -33,6 +33,7 @@ INLINE_SIMPLE_STRUCTS = YES HIDE_UNDOC_CLASSES = YES HIDE_COMPOUND_REFERENCE = YES HIDE_SCOPE_NAMES = YES +TYPEDEF_HIDES_STRUCT = YES MAX_INITIALIZER_LINES = 0 ALPHABETICAL_INDEX = NO SORT_MEMBER_DOCS = NO diff --git a/c/examples/CMakeLists.txt b/c/examples/CMakeLists.txt index 9771ec59e4..ec2988857f 100644 --- a/c/examples/CMakeLists.txt +++ b/c/examples/CMakeLists.txt @@ -26,7 +26,7 @@ find_package(Threads REQUIRED) include_directories(${Proton_INCLUDE_DIRS}) add_definitions(${Proton_DEFINITIONS}) -foreach (name broker send receive direct send-abort send-ssl) +foreach (name broker send receive direct send-abort send-ssl raw_echo raw_connect) add_executable(c-${name} ${name}.c) target_link_libraries(c-${name} ${Proton_Proactor_LIBRARIES} ${Proton_Core_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) set_target_properties(c-${name} PROPERTIES diff --git a/c/examples/raw_connect.c b/c/examples/raw_connect.c new file mode 100644 index 0000000000..d38a3d796f --- /dev/null +++ b/c/examples/raw_connect.c @@ -0,0 +1,256 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +typedef struct app_data_t { + const char *host, *port; + const char *amqp_address; + + pn_proactor_t *proactor; + pn_listener_t *listener; + + int connects; + int disconnects; + + /* Sender values */ + + /* Receiver values */ +} app_data_t; + +typedef struct connection_data_t { + bool sender; +} connection_data_t; + +static int exit_code = 0; + +/* Close the connection and the listener so so we will get a + * PN_PROACTOR_INACTIVE event and exit, once all outstanding events + * are processed. + */ +static void close_all(pn_raw_connection_t *c, app_data_t *app) { + if (c) pn_raw_connection_close(c); + if (app->listener) pn_listener_close(app->listener); +} + +static void check_condition(pn_event_t *e, pn_condition_t *cond, app_data_t *app) { + if (pn_condition_is_set(cond)) { + fprintf(stderr, "%s: %s: %s\n", pn_event_type_name(pn_event_type(e)), + pn_condition_get_name(cond), pn_condition_get_description(cond)); + close_all(pn_event_raw_connection(e), app); + exit_code = 1; + } +} + +static void send_message(pn_raw_connection_t *c, const char* msg) { + pn_raw_buffer_t buffer; + uint32_t len = strlen(msg); + char *buf = (char*) malloc(1024); + memcpy(buf, msg, len); + buffer.bytes = buf; + buffer.capacity = 1024; + buffer.offset = 0; + buffer.size = len; + pn_raw_connection_write_buffers(c, &buffer, 1); +} + +static void recv_message(pn_raw_buffer_t buf) { + fwrite(buf.bytes, buf.size, 1, stdout); +} + +connection_data_t *make_receiver_data(void) { + connection_data_t *cd = (connection_data_t*) malloc(sizeof(connection_data_t)); + cd->sender = false; + return cd; +} + +connection_data_t *make_sender_data(void) { + connection_data_t *cd = (connection_data_t*) malloc(sizeof(connection_data_t)); + cd->sender = true; + return cd; +} + +#define READ_BUFFERS 4 + +/* This function handles events when we are acting as the receiver */ +static void handle_receive(app_data_t *app, pn_event_t* event) { + switch (pn_event_type(event)) { + + case PN_RAW_CONNECTION_CONNECTED: { + pn_raw_connection_t *c = pn_event_raw_connection(event); + pn_raw_buffer_t buffers[READ_BUFFERS] = {{0}}; + int i = READ_BUFFERS; + for (; i; --i) { + pn_raw_buffer_t *buff = &buffers[READ_BUFFERS-i]; + buff->bytes = (char*) malloc(1024); + buff->capacity = 1024; + buff->size = 0; + buff->offset = 0; + } + pn_raw_connection_give_read_buffers(c, buffers, READ_BUFFERS); + } break; + + case PN_RAW_CONNECTION_NEED_READ_BUFFERS: { + } break; + + default: + break; + } +} + +#define WRITE_BUFFERS 4 + +/* This function handles events when we are acting as the sender */ +static void handle_send(app_data_t* app, pn_event_t* event) { + switch (pn_event_type(event)) { + + case PN_RAW_CONNECTION_CONNECTED: { + printf("**raw connection connected\n"); + app->connects++; + } break; + + case PN_RAW_CONNECTION_DISCONNECTED: { + pn_raw_connection_t *c = pn_event_raw_connection(event); + connection_data_t *cd = (connection_data_t*) pn_raw_connection_get_context(c); + free(cd); + printf("**raw connection disconnected\n"); + app->disconnects++; + check_condition(event, pn_raw_connection_condition(c), app); + } break; + + case PN_RAW_CONNECTION_NEED_WRITE_BUFFERS: { + pn_raw_connection_t *c = pn_event_raw_connection(event); + char line[120]; + if (fgets(line, sizeof(line), stdin)) { + send_message(c, line); + } else { + pn_raw_connection_close(c); + } + } break; + + /* This path handles both received bytes and freeing buffers at close */ + case PN_RAW_CONNECTION_READ: { + pn_raw_connection_t *c = pn_event_raw_connection(event); + pn_raw_buffer_t buffs[READ_BUFFERS]; + size_t n; + while ( (n = pn_raw_connection_take_read_buffers(c, buffs, READ_BUFFERS)) ) { + unsigned i; + for (i=0; isender) { + handle_send(app, event); + } else { + handle_receive(app, event); + } + } + } + } + return exit_code == 0; +} + +void run(app_data_t *app) { + /* Loop and handle events */ + do { + pn_event_batch_t *events = pn_proactor_wait(app->proactor); + pn_event_t *e; + for (e = pn_event_batch_next(events); e; e = pn_event_batch_next(events)) { + if (!handle(app, e)) { + return; + } + } + pn_proactor_done(app->proactor, events); + } while(true); +} + +int main(int argc, char **argv) { + struct app_data_t app = {0}; + char addr[PN_MAX_ADDR]; + pn_raw_connection_t *c = pn_raw_connection(); + connection_data_t *cd = make_sender_data(); + + app.host = (argc > 1) ? argv[1] : ""; + app.port = (argc > 2) ? argv[2] : "amqp"; + + /* Create the proactor and connect */ + app.proactor = pn_proactor(); + pn_raw_connection_set_context(c, cd); + pn_proactor_addr(addr, sizeof(addr), app.host, app.port); + pn_proactor_raw_connect(app.proactor, c, addr); + run(&app); + pn_proactor_free(app.proactor); + return exit_code; +} diff --git a/c/examples/raw_echo.c b/c/examples/raw_echo.c new file mode 100644 index 0000000000..53a4ada7b0 --- /dev/null +++ b/c/examples/raw_echo.c @@ -0,0 +1,321 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +typedef struct app_data_t { + const char *host, *port; + const char *amqp_address; + + pn_proactor_t *proactor; + pn_listener_t *listener; + + int64_t first_idle_time; + int64_t try_accept_time; + int64_t wake_conn_time; + int connects; + int disconnects; + + /* Sender values */ + + /* Receiver values */ +} app_data_t; + +#define MAX_CONNECTIONS 5 + +typedef struct conn_data_t { + pn_raw_connection_t *connection; + int64_t last_recv_time; + int bytes; + int buffers; +} conn_data_t; + +static conn_data_t conn_data[MAX_CONNECTIONS] = {{0}}; + +static int exit_code = 0; + +/* Close the connection and the listener so so we will get a + * PN_PROACTOR_INACTIVE event and exit, once all outstanding events + * are processed. + */ +static void close_all(pn_raw_connection_t *c, app_data_t *app) { + if (c) pn_raw_connection_close(c); + if (app->listener) pn_listener_close(app->listener); +} + +static bool check_condition(pn_event_t *e, pn_condition_t *cond, app_data_t *app) { + if (pn_condition_is_set(cond)) { + fprintf(stderr, "%s: %s: %s\n", pn_event_type_name(pn_event_type(e)), + pn_condition_get_name(cond), pn_condition_get_description(cond)); + return true; + } + + return false; +} + +static void check_condition_fatal(pn_event_t *e, pn_condition_t *cond, app_data_t *app) { + if (check_condition(e, cond, app)) { + close_all(pn_event_raw_connection(e), app); + exit_code = 1; + } +} + +static void recv_message(pn_raw_buffer_t buf) { + fwrite(buf.bytes, buf.size, 1, stdout); +} + +conn_data_t *make_conn_data(pn_raw_connection_t *c) { + int i; + for (i = 0; i < MAX_CONNECTIONS; ++i) { + if (!conn_data[i].connection) { + conn_data[i].connection = c; + return &conn_data[i]; + } + } + return NULL; +} + +void free_conn_data(conn_data_t *c) { + if (!c) return; + c->connection = NULL; +} + +#define READ_BUFFERS 4 + +/* This function handles events when we are acting as the receiver */ +static void handle_receive(app_data_t *app, pn_event_t* event) { + switch (pn_event_type(event)) { + + case PN_RAW_CONNECTION_CONNECTED: { + pn_raw_connection_t *c = pn_event_raw_connection(event); + conn_data_t *cd = (conn_data_t *) pn_raw_connection_get_context(c); + pn_raw_buffer_t buffers[READ_BUFFERS] = {{0}}; + if (cd) { + int i = READ_BUFFERS; + printf("**raw connection %ld connected\n", cd-conn_data); + app->connects++; + for (; i; --i) { + pn_raw_buffer_t *buff = &buffers[READ_BUFFERS-i]; + buff->bytes = (char*) malloc(1024); + buff->capacity = 1024; + buff->size = 0; + buff->offset = 0; + } + pn_raw_connection_give_read_buffers(c, buffers, READ_BUFFERS); + } else { + printf("**raw connection connected: not connected\n"); + } + } break; + + case PN_RAW_CONNECTION_WAKE: { + pn_raw_connection_t *c = pn_event_raw_connection(event); + conn_data_t *cd = (conn_data_t *) pn_raw_connection_get_context(c); + printf("**raw connection %ld woken\n", cd-conn_data); + } break; + + case PN_RAW_CONNECTION_DISCONNECTED: { + pn_raw_connection_t *c = pn_event_raw_connection(event); + conn_data_t *cd = (conn_data_t *) pn_raw_connection_get_context(c); + if (cd) { + printf("**raw connection %ld disconnected: bytes: %d, buffers: %d\n", cd-conn_data, cd->bytes, cd->buffers); + } else { + printf("**raw connection disconnected: not connected\n"); + } + app->disconnects++; + check_condition(event, pn_raw_connection_condition(c), app); + free_conn_data(cd); + } break; + + case PN_RAW_CONNECTION_NEED_READ_BUFFERS: { + } break; + + /* This path handles both received bytes and freeing buffers at close */ + case PN_RAW_CONNECTION_READ: { + pn_raw_connection_t *c = pn_event_raw_connection(event); + conn_data_t *cd = (conn_data_t *) pn_raw_connection_get_context(c); + pn_raw_buffer_t buffs[READ_BUFFERS]; + size_t n; + cd->last_recv_time = pn_proactor_now_64(); + while ( (n = pn_raw_connection_take_read_buffers(c, buffs, READ_BUFFERS)) ) { + unsigned i; + for (i=0; ibytes += buffs[i].size; + recv_message(buffs[i]); + } + cd->buffers += n; + + if (!pn_raw_connection_is_write_closed(c)) { + pn_raw_connection_write_buffers(c, buffs, n); + } else if (!pn_raw_connection_is_read_closed(c)) { + pn_raw_connection_give_read_buffers(c, buffs, n); + } else { + unsigned i; + for (i=0; ifirst_idle_time = 0; + app->try_accept_time = 0; + if (app->wake_conn_time < now) { + app->wake_conn_time = now + 5000; + pn_proactor_set_timeout(pn_listener_proactor(listener), 5000); + } + pn_raw_connection_set_context(c, cd); + + pn_listener_raw_accept(listener, c); + } else { + printf("**too many connections, trying again later...\n"); + + /* No other way to reject connection */ + pn_listener_raw_accept(listener, c); + pn_raw_connection_close(c); + } + + } break; + + case PN_LISTENER_CLOSE: { + app->listener = NULL; /* Listener is closed */ + check_condition_fatal(event, pn_listener_condition(pn_event_listener(event)), app); + } break; + + case PN_PROACTOR_TIMEOUT: { + int64_t now = pn_proactor_now_64(); + pn_millis_t timeout = 5000; + if (app->connects - app->disconnects == 0) { + timeout = 20000; + if (app->first_idle_time == 0) { + printf("**idle detected, shutting down in %dms\n", timeout); + app->first_idle_time = now; + } else if (app->first_idle_time + 20000 <= now) { + pn_listener_close(app->listener); + break; + } + } else if (now >= app->wake_conn_time) { + int i; + for (i = 0; i < MAX_CONNECTIONS; ++i) { + if (conn_data[i].connection) pn_raw_connection_wake(conn_data[i].connection); + } + app->wake_conn_time = now + 5000; + } + pn_proactor_set_timeout(pn_event_proactor(event), timeout); + } break; + + case PN_PROACTOR_INACTIVE: { + return false; + } break; + + default: { + pn_raw_connection_t *c = pn_event_raw_connection(event); + if (c) { + handle_receive(app, event); + } + } + } + return exit_code == 0; +} + +void run(app_data_t *app) { + /* Loop and handle events */ + do { + pn_event_batch_t *events = pn_proactor_wait(app->proactor); + pn_event_t *e; + for (e = pn_event_batch_next(events); e; e = pn_event_batch_next(events)) { + if (!handle(app, e)) { + return; + } + } + pn_proactor_done(app->proactor, events); + } while(true); +} + +int main(int argc, char **argv) { + struct app_data_t app = {0}; + char addr[PN_MAX_ADDR]; + app.host = (argc > 1) ? argv[1] : ""; + app.port = (argc > 2) ? argv[2] : "amqp"; + + /* Create the proactor and connect */ + app.proactor = pn_proactor(); + app.listener = pn_listener(); + pn_proactor_addr(addr, sizeof(addr), app.host, app.port); + pn_proactor_listen(app.proactor, app.listener, addr, 16); + run(&app); + pn_proactor_free(app.proactor); + return exit_code; +} diff --git a/c/include/proton/cid.h b/c/include/proton/cid.h index d35c8ac4ab..7e0c052a89 100644 --- a/c/include/proton/cid.h +++ b/c/include/proton/cid.h @@ -67,7 +67,8 @@ typedef enum { CID_pn_listener, CID_pn_proactor, - CID_pn_listener_socket + CID_pn_listener_socket, + CID_pn_raw_connection } pn_cid_t; /** diff --git a/c/include/proton/delivery.h b/c/include/proton/delivery.h index ec034e9bd6..118fbf6e56 100644 --- a/c/include/proton/delivery.h +++ b/c/include/proton/delivery.h @@ -328,6 +328,7 @@ PN_EXTERN bool pn_delivery_buffered(pn_delivery_t *delivery); * @return the first delivery object that needs to be serviced, else * NULL if none */ +PN_DEPRECATED("Use the PN_DELIVERY event to track deliveries with pending operations") PN_EXTERN pn_delivery_t *pn_work_head(pn_connection_t *connection); /** @@ -339,6 +340,7 @@ PN_EXTERN pn_delivery_t *pn_work_head(pn_connection_t *connection); * @return the next delivery that has pending operations, else * NULL if none */ +PN_DEPRECATED("Use the PN_DELIVERY event to track deliveries with pending operations") PN_EXTERN pn_delivery_t *pn_work_next(pn_delivery_t *delivery); /** diff --git a/c/include/proton/event.h b/c/include/proton/event.h index 8e5fba24f1..9562e78211 100644 --- a/c/include/proton/event.h +++ b/c/include/proton/event.h @@ -347,7 +347,120 @@ typedef enum { * The listener is listening. * Events of this type point to the @ref pn_listener_t. */ - PN_LISTENER_OPEN + PN_LISTENER_OPEN, + + /** + * The raw connection connected. + * Now would be a good time to give the raw connection some buffers + * to read bytes from the underlying socket. If you don't do it + * now you will get @ref PN_RAW_CONNECTION_NEED_READ_BUFFERS (and + * @ref PN_RAW_CONNECTION_NEED_WRITE_BUFFERS) events when the socket is readable + * (or writable) but there are not buffers available. + * + * Events of this type point to a @ref pn_raw_connection_t + */ + PN_RAW_CONNECTION_CONNECTED, + + /** + * The remote end of the raw connection closed the connection so + * that we can no longer read. + * + * When both this and @ref PN_RAW_CONNECTION_CLOSED_WRITE event have + * occurred then the @ref PN_RAW_CONNECTION_DISCONNECTED event will be + * generated. + * + * Events of this type point to a @ref pn_raw_connection_t + */ + PN_RAW_CONNECTION_CLOSED_READ, + + /** + * The remote end of the raw connection closed the connection so + * that we can no longer write. + * + * When both this and @ref PN_RAW_CONNECTION_CLOSED_READ event have + * occurred then the @ref PN_RAW_CONNECTION_DISCONNECTED event will be + * generated. + * + * Events of this type point to a @ref pn_raw_connection_t + */ + PN_RAW_CONNECTION_CLOSED_WRITE, + + /** + * The raw connection is disconnected. + * No more bytes will be read or written on the connection. Every buffer + * in use will already either have been returned using a + * @ref PN_RAW_CONNECTION_READ or @ref PN_RAW_CONNECTION_WRITTEN event. + * This event will always be the last for this raw connection, and so + * the application can clean up the raw connection at this point. + * + * Events of this type point to a @ref pn_raw_connection_t + */ + PN_RAW_CONNECTION_DISCONNECTED, + + /** + * The raw connection might need more read buffers. + * The connection is readable, but the connection has no buffer to read the + * bytes into. If you supply some buffers now maybe you'll get a + * @ref PN_RAW_CONNECTION_READ event soon, but no guarantees. + * + * This event is edge triggered and you will only get it once until you give + * the raw connection some more read buffers. + * + * Events of this type point to a @ref pn_raw_connection_t + */ + PN_RAW_CONNECTION_NEED_READ_BUFFERS, + + /** + * The raw connection might need more write buffers. + * The connection is writable but has no buffers to write. If you give the + * raw connection something to write using @ref pn_raw_connection_write_buffers + * the raw connection can write them. It is not necessary to wait for this event + * before sending buffers to write, but it can be used to aid in flow control (maybe). + * + * This event is edge triggered and you will only get it once until you give + * the raw connection something more to write. + * + * Events of this type point to a @ref pn_raw_connection_t + */ + PN_RAW_CONNECTION_NEED_WRITE_BUFFERS, + + /** + * The raw connection read bytes: The bytes that were read are + * in one of the read buffers given to the raw connection. + * + * This event will be sent if there are bytes that have been read + * in a buffer owned by the raw connection and there is no + * @ref PN_RAW_CONNECTION_READ event still queued. + * + * When a connection closes all read buffers are returned to the + * application using @ref PN_RAW_CONNECTION_READ events with empty buffers. + * + * Events of this type point to a @ref pn_raw_connection_t + */ + PN_RAW_CONNECTION_READ, + + /** + * The raw connection has finished a write and the buffers that were + * used are no longer in use and can be recycled. + * + * This event will be sent if there are buffers that have been written still + * owned by the raw connection and there is no @ref PN_RAW_CONNECTION_WRITTEN + * event currently queued. + * + * When a connection closes all write buffers are returned using + * @ref PN_RAW_CONNECTION_WRITTEN events. + * + * Events of this type point to a @ref pn_raw_connection_t + */ + PN_RAW_CONNECTION_WRITTEN, + + /** + * The raw connection was woken by @ref pn_raw_connection_wake + * + * Events of this type point to a @ref pn_raw_connection_t + */ + PN_RAW_CONNECTION_WAKE + } pn_event_type_t; diff --git a/c/include/proton/link.h b/c/include/proton/link.h index c7e3f5b8aa..014397643a 100644 --- a/c/include/proton/link.h +++ b/c/include/proton/link.h @@ -680,6 +680,35 @@ PN_EXTERN void pn_link_set_max_message_size(pn_link_t *link, uint64_t size); */ PN_EXTERN uint64_t pn_link_remote_max_message_size(pn_link_t *link); +/** + * Access/modify the AMQP properties data for a link object. + * + * This operation will return a pointer to a ::pn_data_t object that is valid + * until the link object is freed. Any data contained by the ::pn_data_t object + * will be sent as the AMQP properties for the link object when the link is + * opened by calling ::pn_link_open. Note that this MUST take the form of a + * symbol keyed map to be valid. + * + * The ::pn_data_t pointer returned is valid until the link object is freed. + * + * @param[in] link the link object + * @return a pointer to a pn_data_t representing the link properties + */ +PN_EXTERN pn_data_t *pn_link_properties(pn_link_t *link); + +/** + * Access the AMQP link properties supplied by the remote link endpoint. + * + * This operation will return a pointer to a ::pn_data_t object that + * is valid until the link object is freed. This data object + * will be empty until the remote link is opened as indicated by + * the ::PN_REMOTE_ACTIVE flag. + * + * @param[in] link the link object + * @return the remote link properties + */ +PN_EXTERN pn_data_t *pn_link_remote_properties(pn_link_t *link); + /** * @} */ diff --git a/c/include/proton/listener.h b/c/include/proton/listener.h index e4bbf350cc..5d2cfe2460 100644 --- a/c/include/proton/listener.h +++ b/c/include/proton/listener.h @@ -134,6 +134,25 @@ PNP_EXTERN pn_proactor_t *pn_listener_proactor(pn_listener_t *c); */ PNP_EXTERN pn_listener_t *pn_event_listener(pn_event_t *event); +/** + * Accept an incoming connection request as a raw connection. + * + * Call after a @ref PN_LISTENER_ACCEPT event. + * + * Errors are returned as @ref PN_RAW_CONNECTION_DISCONNECTED by pn_proactor_wait(). + * + * @param[in] listener the listener + * @param[in] raw_connection the application must create a raw connection with pn_raw_connection() + * this parameter cannot be null.If NULL a new connection is created. + * + * The proactor that owns the @p listener *takes ownership* of @p raw_connection and will + * automatically call pn_raw_connection_free() after the final @ref + * PN_RAW_CONNECTION_DISCONNECTED event is handled, or when pn_proactor_free() is + * called. + * + */ +PNP_EXTERN void pn_listener_raw_accept(pn_listener_t *listener, pn_raw_connection_t *raw_connection); + /** *@} */ diff --git a/c/include/proton/logger.h b/c/include/proton/logger.h index 8dfc74c525..cfc670642d 100644 --- a/c/include/proton/logger.h +++ b/c/include/proton/logger.h @@ -190,7 +190,7 @@ PN_EXTERN void pn_logger_set_mask(pn_logger_t *logger, uint16_t subsystem, uint1 * @param[in] subsystem bits representing subsystems to turn off trace for * @param[in] level bits representing log levels to turn off trace for */ -PN_EXTERN void pn_logger_reset_mask(pn_logger_t *logger, uint16_t subsystem, uint16_t severity); +PN_EXTERN void pn_logger_reset_mask(pn_logger_t *logger, uint16_t subsystem, uint16_t level); /** * Set the tracing function used by a logger. @@ -228,9 +228,11 @@ PN_EXTERN intptr_t pn_logger_get_log_sink_context(pn_logger_t *logger); * be processed the same way. * * @param[in] logger the logger + * @param[in] subsystem the subsystem which is producing this log message + * @param[in] level the log level of the log message * @param[in] fmt the printf formatted message to be logged */ -PN_EXTERN void pn_logger_logf(pn_logger_t *logger, pn_log_subsystem_t subsystem, pn_log_level_t severity, const char *fmt, ...); +PN_EXTERN void pn_logger_logf(pn_logger_t *logger, pn_log_subsystem_t subsystem, pn_log_level_t level, const char *fmt, ...); #ifdef __cplusplus } diff --git a/c/include/proton/message.h b/c/include/proton/message.h index 41c37af1c2..80b9e00be1 100644 --- a/c/include/proton/message.h +++ b/c/include/proton/message.h @@ -731,8 +731,8 @@ PN_EXTERN int pn_message_decode(pn_message_t *msg, const char *bytes, size_t siz * * @param[in] msg a message object * @param[in] bytes the start of empty buffer space - * @param[in] size the amount of empty buffer space - * @param[out] size the amount of data written + * @param[inout] size the amount of empty buffer space. + * On return size holds the amount of data written * @return zero on success or an error code on failure */ PN_EXTERN int pn_message_encode(pn_message_t *msg, char *bytes, size_t *size); diff --git a/c/include/proton/proactor.h b/c/include/proton/proactor.h index b0303c9a3c..93b9c894be 100644 --- a/c/include/proton/proactor.h +++ b/c/include/proton/proactor.h @@ -340,6 +340,29 @@ PNP_EXTERN pn_millis_t pn_proactor_now(void); */ PNP_EXTERN int64_t pn_proactor_now_64(void); +/** + * Connect @p addr and bind to @p raw_connection. + * + * Errors are returned as @ref PN_RAW_CONNECTION_DISCONNECTED events by pn_proactor_wait(). + * + * @note Thread-safe + * + * @param[in] proactor the proactor + * + * @param[in] raw_connection the application must create a raw connection with pn_raw_connection() + * this parameter cannot be null. + * + * @p proactor *takes ownership* of @p raw_connection and will + * automatically call pn_raw_connection_free() after the final @ref + * PN_RAW_CONNECTION_DISCONNECTED event is handled, or when pn_proactor_free() is + * called. + * + * @param[in] addr the "host:port" network address, constructed by pn_proactor_addr() + * An empty host will connect to the local host via the default protocol (IPV6 or IPV4). + * An empty port will connect to the standard AMQP port (5672). + */ +PNP_EXTERN void pn_proactor_raw_connect(pn_proactor_t *proactor, pn_raw_connection_t *raw_connection, const char *addr); + /** * @} */ @@ -391,6 +414,15 @@ PNP_EXTERN int64_t pn_proactor_now_64(void); * @ref PN_PROACTOR_TIMEOUT | @copybrief PN_PROACTOR_TIMEOUT * @ref PN_PROACTOR_INACTIVE | @copybrief PN_PROACTOR_INACTIVE * @ref PN_CONNECTION_WAKE | @copybrief PN_CONNECTION_WAKE + * @ref PN_RAW_CONNECTION_CONNECTED | @copybrief PN_RAW_CONNECTION_CONNECTED + * @ref PN_RAW_CONNECTION_CLOSED_READ | @copybrief PN_RAW_CONNECTION_CLOSED_READ + * @ref PN_RAW_CONNECTION_CLOSED_WRITE | @copybrief PN_RAW_CONNECTION_CLOSED_WRITE + * @ref PN_RAW_CONNECTION_DISCONNECTED | @copybrief PN_RAW_CONNECTION_DISCONNECTED + * @ref PN_RAW_CONNECTION_NEED_READ_BUFFERS | @copybrief PN_RAW_CONNECTION_NEED_READ_BUFFERS + * @ref PN_RAW_CONNECTION_NEED_WRITE_BUFFERS | @copybrief PN_RAW_CONNECTION_NEED_WRITE_BUFFERS + * @ref PN_RAW_CONNECTION_READ | @copybrief PN_RAW_CONNECTION_READ + * @ref PN_RAW_CONNECTION_WRITTEN | @copybrief PN_RAW_CONNECTION_WRITTEN + * @ref PN_RAW_CONNECTION_WAKE | @copybrief PN_RAW_CONNECTION_WAKE * * @} */ diff --git a/c/include/proton/raw_connection.h b/c/include/proton/raw_connection.h new file mode 100644 index 0000000000..a483665198 --- /dev/null +++ b/c/include/proton/raw_connection.h @@ -0,0 +1,265 @@ +#ifndef PROTON_RAW_CONNECTION_H +#define PROTON_RAW_CONNECTION_H 1 + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @file + * + * @addtogroup raw_connection + * @{ + */ + +/** + * A descriptor used to represent a single raw buffer in memory. + * + * @note The intent of the offset is to allow the actual bytes being read/written to be at a variable + * location relative to the head of the buffer because of other data or structures that are important to the application + * associated with the data to be written but not themselves read/written to the connection. + * + * @note For read buffers: When read buffers are returned to the application size will be the number of bytes read. + * Read operations will not change the context, bytes or capacity members of the structure. + * + * @note For write buffers: When write buffers are returned to the application all of the struct members will be returned + * unaltered. Also write operations will not modify the bytes of the buffer passed in at all. In principle this means that + * the write buffer can be used for multiple writes at the same time as long as the actual buffer is unmodified by the + * application at any time the buffer is being used by any raw connection. + */ +typedef struct pn_raw_buffer_t { + uintptr_t context; /**< Used to associate arbitrary application data with this raw buffer */ + char *bytes; /**< Pointer to the start of the raw buffer, if this is null then no buffer is represented */ + uint32_t capacity; /**< Count of available bytes starting at @ref bytes */ + uint32_t size; /**< Number of bytes read or to be written starting at @ref offset */ + uint32_t offset; /**< First byte in the buffer to be read or written */ +} pn_raw_buffer_t; + +/** + * Create a new raw connection for use with the @ref proactor. + * See @ref pn_proactor_raw_connect and @ref pn_listener_raw_accept. + * + * @return A newly allocated pn_connection_t or NULL if there wasn't sufficient memory. + * + * @note This is the only pn_connection_t function that allocates memory. So an application that + * wants good control of out of memory conditions should check the return value for NULL. + * + * @note It would be a good practice is to create a raw connection and attach application + * specific context to it before giving it to the proactor. + * + * @note There is no way to free a pn_connection_t as once passed to the proactor the proactor owns + * it and controls its lifecycle. + */ +PNP_EXTERN pn_raw_connection_t *pn_raw_connection(void); + +/** + * Get the local address of a raw connection. Return `NULL` if not available. + * Pointer is invalid after the transport closes (@ref PN_RAW_CONNECTION_DISCONNECTED event is handled) + */ +PNP_EXTERN const struct pn_netaddr_t *pn_raw_connection_local_addr(pn_raw_connection_t *connection); + +/** + * Get the local address of a raw connection. Return `NULL` if not available. + * Pointer is invalid after the transport closes (@ref PN_RAW_CONNECTION_DISCONNECTED event is handled) + */ +PNP_EXTERN const struct pn_netaddr_t *pn_raw_connection_remote_addr(pn_raw_connection_t *connection); + +/** + * Close a raw connection. + * This will close the underlying socket and release all buffers held by the raw connection. + * It will cause @ref PN_RAW_CONNECTION_READ and @ref PN_RAW_CONNECTION_WRITTEN to be emitted so + * the application can clean up buffers given to the raw connection. After that a + * @ref PN_RAW_CONNECTION_DISCONNECTED event will be emitted to allow the application to clean up + * any other state held by the raw connection. + * + */ +PNP_EXTERN void pn_raw_connection_close(pn_raw_connection_t *connection); + +/** + * Query the raw connection for how many more read buffers it can be given + */ +PNP_EXTERN size_t pn_raw_connection_read_buffers_capacity(pn_raw_connection_t *connection); + +/** + * Query the raw connection for how many more write buffers it can be given + */ +PNP_EXTERN size_t pn_raw_connection_write_buffers_capacity(pn_raw_connection_t *connection); + +/** + * Give the raw connection buffers to use for reading from the underlying socket. + * If the raw socket has no read buffers then the application will never receive + * a @ref PN_RAW_CONNECTION_READ event. + * + * A @ref PN_RAW_CONNECTION_NEED_READ_BUFFERS event will be generated immediately after + * the @ref PN_RAW_CONNECTION_CONNECTED event if there are no read buffers. It will also be + * generated whenever the raw connection runs out of read buffers. In both these cases the + * event will not be generated again until @ref pn_raw_connection_give_read_buffers is called. + * + * @return the number of buffers actually given to the raw connection. This will only be different + * from the number supplied if the connection has no more space to record more buffers. In this case + * the buffers taken will be the earlier buffers in the array supplied, the elements 0 to the + * returned value-1. + * + * @note The buffers given to the raw connection are owned by it until the application + * receives a @ref PN_RAW_CONNECTION_READ event giving them back to the application. They must + * not be accessed at all (written or even read) from calling @ref pn_raw_connection_give_read_buffers + * until receiving this event. + * + * @note The application should not assume that the @ref PN_RAW_CONNECTION_NEED_READ_BUFFERS + * event signifies that the connection is readable. + */ +PNP_EXTERN size_t pn_raw_connection_give_read_buffers(pn_raw_connection_t *connection, pn_raw_buffer_t const *buffers, size_t num); + +/** + * Fetch buffers with bytes read from the raw socket + * + * @param[out] buffers pointer to an array of @ref pn_raw_buffer_t structures which will be filled in with the read buffer information + * @param[in] num the number of buffers allocated in the passed in array of buffers + * @return the number of buffers being returned, if there is are no read bytes then this will be 0. As many buffers will be returned + * as can be given the number that are passed in. So if the number returned is less than the number passed in there are no more buffers + * read. But if the number is the same there may be more read buffers to take. + * + * @note After the application receives @ref PN_RAW_CONNECTION_READ there should be bytes read from the socket and + * hence this call should return buffers. It is safe to carry on calling @ref pn_raw_connection_take_read_buffers + * until it returns 0. + */ +PNP_EXTERN size_t pn_raw_connection_take_read_buffers(pn_raw_connection_t *connection, pn_raw_buffer_t *buffers, size_t num); + +/** + * Give the raw connection buffers to write to the underlying socket. + * + * A @ref PN_RAW_CONNECTION_WRITTEN event will be generated once the buffers have been written to the socket + * until this point the buffers must not be accessed at all (written or even read). + * + * A @ref PN_RAW_CONNECTION_NEED_WRITE_BUFFERS event will be generated immediately after + * the @ref PN_RAW_CONNECTION_CONNECTED event if there are no write buffers. It will also be + * generated whenever the raw connection finishes writing all the write buffers. In both these cases the + * event will not be generated again until @ref pn_raw_connection_write_buffers is called. + * + * @return the number of buffers actually recorded by the raw connection to write. This will only be different + * from the number supplied if the connection has no more space to record more buffers. In this case + * the buffers recorded will be the earlier buffers in the array supplied, the elements 0 to the + * returned value-1. + * + */ +PNP_EXTERN size_t pn_raw_connection_write_buffers(pn_raw_connection_t *connection, pn_raw_buffer_t const *buffers, size_t num); + +/** + * Return a buffer chain with buffers that have all been written to the raw socket + * + * @param[out] buffers pointer to an array of @ref pn_raw_buffer_t structures which will be filled in with the written buffer information + * @param[in] num the number of buffers allocated in the passed in array of buffers + * @return the number of buffers being returned, if there is are no written buffers to return then this will be 0. As many buffers will be returned + * as can be given the number that are passed in. So if the number returned is less than the number passed in there are no more buffers + * written. But if the number is the same there may be more written buffers to take. + * + * @note After the application receives @ref PN_RAW_CONNECTION_WRITTEN there should be bytes written to the socket and + * hence this call should return buffers. It is safe to carry on calling @ref pn_raw_connection_take_written_buffers + * until it returns 0. + */ +PNP_EXTERN size_t pn_raw_connection_take_written_buffers(pn_raw_connection_t *connection, pn_raw_buffer_t *buffers, size_t num); + +/** + * Is @p connection closed for read? + * + * @return true if the raw connection is closed for read. + */ +PNP_EXTERN bool pn_raw_connection_is_read_closed(pn_raw_connection_t *connection); + +/** + * Is @p connection closed for write? + * + * @return true if the raw connection is closed for write. + */ +PNP_EXTERN bool pn_raw_connection_is_write_closed(pn_raw_connection_t *connection); + +/** + * Return a @ref PN_RAW_CONNECTION_WAKE event for @p connection as soon as possible. + * + * At least one wake event will be returned, serialized with other @ref proactor_events + * for the same raw connection. Wakes can be "coalesced" - if several + * @ref pn_raw_connection_wake() calls happen close together, there may be only one + * @ref PN_RAW_CONNECTION_WAKE event that occurs after all of them. + * + * @note Thread-safe + */ +PNP_EXTERN void pn_raw_connection_wake(pn_raw_connection_t *connection); + +/** + * Get additional information about a raw connection error. + * There is a raw connection error if the @ref PN_RAW_CONNECTION_DISCONNECTED event + * is received and the pn_condition_t associated is non null (@see pn_condition_is_set). + * + * The value returned is only valid until the end of handler for the + * @ref PN_RAW_CONNECTION_DISCONNECTED event. + */ +PNP_EXTERN pn_condition_t *pn_raw_connection_condition(pn_raw_connection_t *connection); + +/** + * Get the application context associated with this raw connection. + * + * The application context for a raw connection may be set using + * ::pn_raw_connection_set_context. + * + * @param[in] connection the raw connection whose context is to be returned. + * @return the application context for the raw connection + */ +PNP_EXTERN void *pn_raw_connection_get_context(pn_raw_connection_t *connection); + +/** + * Set a new application context for a raw connection. + * + * The application context for a raw connection may be retrieved + * using ::pn_raw_connection_get_context. + * + * @param[in] connection the raw connection object + * @param[in] context the application context + */ +PNP_EXTERN void pn_raw_connection_set_context(pn_raw_connection_t *connection, void *context); + +/** + * Get the attachments that are associated with a raw connection. + */ +PNP_EXTERN pn_record_t *pn_raw_connection_attachments(pn_raw_connection_t *connection); + +/** + * Return the raw connection associated with an event. + * + * @return NULL if the event is not associated with a raw connection. + */ +PNP_EXTERN pn_raw_connection_t *pn_event_raw_connection(pn_event_t *event); + + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* raw_connection.h */ diff --git a/c/include/proton/ssl.h b/c/include/proton/ssl.h index 91d882e2bc..03acbf5cae 100644 --- a/c/include/proton/ssl.h +++ b/c/include/proton/ssl.h @@ -259,7 +259,7 @@ PN_EXTERN int pn_ssl_domain_set_protocols(pn_ssl_domain_t *domain, const char *p PN_EXTERN int pn_ssl_domain_set_ciphers(pn_ssl_domain_t *domain, const char *ciphers); /** - * **Deprecated** - Use ::pn_transport_require_encrytion() + * **Deprecated** - Use ::pn_transport_require_encryption() * * Permit a server to accept connection requests from non-SSL clients. * diff --git a/c/include/proton/types.h b/c/include/proton/types.h index edcca98aaf..f4f496e1c6 100644 --- a/c/include/proton/types.h +++ b/c/include/proton/types.h @@ -115,6 +115,10 @@ * @brief **Unsettled API** - A listener for incoming connections. * @ingroup io * + * @defgroup raw_connection Raw connection + * @brief **Unsettled API** - An API allowing raw sockets to be used with proactor + * @ingroup io + * * @defgroup connection_driver Connection driver * @brief **Unsettled API** - An API for low-level IO integration. * @ingroup io @@ -437,6 +441,13 @@ typedef struct pn_transport_t pn_transport_t; */ typedef struct pn_proactor_t pn_proactor_t; +/** + * A raw network connection used with the proactor. + * + * @ingroup raw_connection + */ +typedef struct pn_raw_connection_t pn_raw_connection_t; + /** * A batch of events that must be handled in sequence. * diff --git a/c/src/compiler/msvc/snprintf.c b/c/src/compiler/msvc/snprintf.c deleted file mode 100644 index 49a853ce4e..0000000000 --- a/c/src/compiler/msvc/snprintf.c +++ /dev/null @@ -1,56 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - -#include "platform/platform.h" - -#include -#include - -// [v]snprintf on Windows only matches C99 when no errors or overflow. -// Note: [v]snprintf behavior changed in VS2015 to be C99 compliant. -// vsnprintf_s is unchanged. This platform code can go away some day. - - -int pni_vsnprintf(char *buf, size_t count, const char *fmt, va_list ap) { - if (fmt == NULL) - return -1; - if ((buf == NULL) && (count > 0)) - return -1; - if (count > 0) { - int n = vsnprintf_s(buf, count, _TRUNCATE, fmt, ap); - if (n >= 0) // no overflow - return n; // same as C99 - buf[count-1] = '\0'; - } - // separate call to get needed buffer size on overflow - int n = _vscprintf(fmt, ap); - if (n >= (int) count) - return n; - return -1; -} - -int pni_snprintf(char *buf, size_t count, const char *fmt, ...) { - va_list ap; - va_start(ap, fmt); - int n = pni_vsnprintf(buf, count, fmt, ap); - va_end(ap); - return n; -} diff --git a/c/src/core/engine-internal.h b/c/src/core/engine-internal.h index 0df2d2f714..9dbc9199f9 100644 --- a/c/src/core/engine-internal.h +++ b/c/src/core/engine-internal.h @@ -31,6 +31,10 @@ #include "logger_private.h" #include "util.h" +#if __cplusplus +extern "C" { +#endif + typedef enum pn_endpoint_type_t {CONNECTION, SESSION, SENDER, RECEIVER} pn_endpoint_type_t; typedef struct pn_endpoint_t pn_endpoint_t; @@ -292,6 +296,8 @@ struct pn_link_t { pn_delivery_t *unsettled_tail; pn_delivery_t *current; pn_record_t *context; + pn_data_t *properties; + pn_data_t *remote_properties; size_t unsettled_count; uint64_t max_message_size; uint64_t remote_max_message_size; @@ -377,4 +383,8 @@ typedef enum {IN, OUT} pn_dir_t; void pn_do_trace(pn_transport_t *transport, uint16_t ch, pn_dir_t dir, pn_data_t *args, const char *payload, size_t size); +#if __cplusplus +} +#endif + #endif /* engine-internal.h */ diff --git a/c/src/core/engine.c b/c/src/core/engine.c index 0e313b9eb0..8300e42493 100644 --- a/c/src/core/engine.c +++ b/c/src/core/engine.c @@ -19,6 +19,9 @@ * */ +/* for pn_work_head and related deprecations */ +#define PN_USE_DEPRECATED_API 1 + #include "engine-internal.h" #include "framing.h" @@ -683,7 +686,6 @@ static void pni_add_work(pn_connection_t *connection, pn_delivery_t *delivery) { if (!delivery->work) { - assert(!delivery->local.settled); // never allow settled deliveries LL_ADD(connection, work, delivery); delivery->work = true; } @@ -1125,6 +1127,8 @@ static void pn_link_finalize(void *object) if (endpoint->referenced) { pn_decref(link->session); } + pn_free(link->properties); + pn_free(link->remote_properties); } #define pn_link_refcount pn_object_refcount @@ -1168,6 +1172,8 @@ pn_link_t *pn_link_new(int type, pn_session_t *session, const char *name) link->remote_snd_settle_mode = PN_SND_MIXED; link->remote_rcv_settle_mode = PN_RCV_FIRST; link->detached = false; + link->properties = 0; + link->remote_properties = 0; // begin transport state link->state.local_handle = -1; @@ -1972,6 +1978,21 @@ uint64_t pn_link_remote_max_message_size(pn_link_t *link) return link->remote_max_message_size; } +pn_data_t *pn_link_properties(pn_link_t *link) +{ + assert(link); + if (!link->properties) + link->properties = pn_data(0); + return link->properties; +} + +pn_data_t *pn_link_remote_properties(pn_link_t *link) +{ + assert(link); + return link->remote_properties; +} + + pn_link_t *pn_delivery_link(pn_delivery_t *delivery) { assert(delivery); @@ -2171,7 +2192,7 @@ int pn_condition_vformat(pn_condition_t *condition, const char *name, const char return err; char text[1024]; - size_t n = pni_vsnprintf(text, 1024, fmt, ap); + size_t n = vsnprintf(text, 1024, fmt, ap); if (n >= sizeof(text)) text[sizeof(text)-1] = '\0'; err = pn_condition_set_description(condition, text); @@ -2388,3 +2409,5 @@ const char *pn_disposition_type_name(uint64_t d) { default: return "unknown"; } } + +#undef PN_USE_DEPRECATED_API diff --git a/c/src/core/error.c b/c/src/core/error.c index d533a21680..9ebe90d7bd 100644 --- a/c/src/core/error.c +++ b/c/src/core/error.c @@ -31,6 +31,7 @@ #include #include +#include #include #include @@ -83,7 +84,7 @@ int pn_error_vformat(pn_error_t *error, int code, const char *fmt, va_list ap) { assert(error); char text[1024]; - int n = pni_vsnprintf(text, 1024, fmt, ap); + int n = vsnprintf(text, 1024, fmt, ap); if (n >= 1024) { text[1023] = '\0'; } diff --git a/c/src/core/event.c b/c/src/core/event.c index 5a20d8780c..cecc615c0e 100644 --- a/c/src/core/event.c +++ b/c/src/core/event.c @@ -298,109 +298,72 @@ pn_record_t *pn_event_attachments(pn_event_t *event) return event->attachments; } + const char *pn_event_type_name(pn_event_type_t type) { + #define CASE(X) case X: return #X switch (type) { - case PN_EVENT_NONE: - return "PN_EVENT_NONE"; - case PN_REACTOR_INIT: - return "PN_REACTOR_INIT"; - case PN_REACTOR_QUIESCED: - return "PN_REACTOR_QUIESCED"; - case PN_REACTOR_FINAL: - return "PN_REACTOR_FINAL"; - case PN_TIMER_TASK: - return "PN_TIMER_TASK"; - case PN_CONNECTION_INIT: - return "PN_CONNECTION_INIT"; - case PN_CONNECTION_BOUND: - return "PN_CONNECTION_BOUND"; - case PN_CONNECTION_UNBOUND: - return "PN_CONNECTION_UNBOUND"; - case PN_CONNECTION_REMOTE_OPEN: - return "PN_CONNECTION_REMOTE_OPEN"; - case PN_CONNECTION_LOCAL_OPEN: - return "PN_CONNECTION_LOCAL_OPEN"; - case PN_CONNECTION_REMOTE_CLOSE: - return "PN_CONNECTION_REMOTE_CLOSE"; - case PN_CONNECTION_LOCAL_CLOSE: - return "PN_CONNECTION_LOCAL_CLOSE"; - case PN_CONNECTION_FINAL: - return "PN_CONNECTION_FINAL"; - case PN_SESSION_INIT: - return "PN_SESSION_INIT"; - case PN_SESSION_REMOTE_OPEN: - return "PN_SESSION_REMOTE_OPEN"; - case PN_SESSION_LOCAL_OPEN: - return "PN_SESSION_LOCAL_OPEN"; - case PN_SESSION_REMOTE_CLOSE: - return "PN_SESSION_REMOTE_CLOSE"; - case PN_SESSION_LOCAL_CLOSE: - return "PN_SESSION_LOCAL_CLOSE"; - case PN_SESSION_FINAL: - return "PN_SESSION_FINAL"; - case PN_LINK_INIT: - return "PN_LINK_INIT"; - case PN_LINK_REMOTE_OPEN: - return "PN_LINK_REMOTE_OPEN"; - case PN_LINK_LOCAL_OPEN: - return "PN_LINK_LOCAL_OPEN"; - case PN_LINK_REMOTE_CLOSE: - return "PN_LINK_REMOTE_CLOSE"; - case PN_LINK_LOCAL_DETACH: - return "PN_LINK_LOCAL_DETACH"; - case PN_LINK_REMOTE_DETACH: - return "PN_LINK_REMOTE_DETACH"; - case PN_LINK_LOCAL_CLOSE: - return "PN_LINK_LOCAL_CLOSE"; - case PN_LINK_FLOW: - return "PN_LINK_FLOW"; - case PN_LINK_FINAL: - return "PN_LINK_FINAL"; - case PN_DELIVERY: - return "PN_DELIVERY"; - case PN_TRANSPORT: - return "PN_TRANSPORT"; - case PN_TRANSPORT_AUTHENTICATED: - return "PN_TRANSPORT_AUTHENTICATED"; - case PN_TRANSPORT_ERROR: - return "PN_TRANSPORT_ERROR"; - case PN_TRANSPORT_HEAD_CLOSED: - return "PN_TRANSPORT_HEAD_CLOSED"; - case PN_TRANSPORT_TAIL_CLOSED: - return "PN_TRANSPORT_TAIL_CLOSED"; - case PN_TRANSPORT_CLOSED: - return "PN_TRANSPORT_CLOSED"; - case PN_SELECTABLE_INIT: - return "PN_SELECTABLE_INIT"; - case PN_SELECTABLE_UPDATED: - return "PN_SELECTABLE_UPDATED"; - case PN_SELECTABLE_READABLE: - return "PN_SELECTABLE_READABLE"; - case PN_SELECTABLE_WRITABLE: - return "PN_SELECTABLE_WRITABLE"; - case PN_SELECTABLE_ERROR: - return "PN_SELECTABLE_ERROR"; - case PN_SELECTABLE_EXPIRED: - return "PN_SELECTABLE_EXPIRED"; - case PN_SELECTABLE_FINAL: - return "PN_SELECTABLE_FINAL"; - case PN_CONNECTION_WAKE: - return "PN_CONNECTION_WAKE"; - case PN_LISTENER_ACCEPT: - return "PN_LISTENER_ACCEPT"; - case PN_LISTENER_CLOSE: - return "PN_LISTENER_CLOSE"; - case PN_PROACTOR_INTERRUPT: - return "PN_PROACTOR_INTERRUPT"; - case PN_PROACTOR_TIMEOUT: - return "PN_PROACTOR_TIMEOUT"; - case PN_PROACTOR_INACTIVE: - return "PN_PROACTOR_INACTIVE"; - case PN_LISTENER_OPEN: - return "PN_LISTENER_OPEN"; - default: + CASE(PN_EVENT_NONE); + CASE(PN_REACTOR_INIT); + CASE(PN_REACTOR_QUIESCED); + CASE(PN_REACTOR_FINAL); + CASE(PN_TIMER_TASK); + CASE(PN_CONNECTION_INIT); + CASE(PN_CONNECTION_BOUND); + CASE(PN_CONNECTION_UNBOUND); + CASE(PN_CONNECTION_REMOTE_OPEN); + CASE(PN_CONNECTION_LOCAL_OPEN); + CASE(PN_CONNECTION_REMOTE_CLOSE); + CASE(PN_CONNECTION_LOCAL_CLOSE); + CASE(PN_CONNECTION_FINAL); + CASE(PN_SESSION_INIT); + CASE(PN_SESSION_REMOTE_OPEN); + CASE(PN_SESSION_LOCAL_OPEN); + CASE(PN_SESSION_REMOTE_CLOSE); + CASE(PN_SESSION_LOCAL_CLOSE); + CASE(PN_SESSION_FINAL); + CASE(PN_LINK_INIT); + CASE(PN_LINK_REMOTE_OPEN); + CASE(PN_LINK_LOCAL_OPEN); + CASE(PN_LINK_REMOTE_CLOSE); + CASE(PN_LINK_LOCAL_DETACH); + CASE(PN_LINK_REMOTE_DETACH); + CASE(PN_LINK_LOCAL_CLOSE); + CASE(PN_LINK_FLOW); + CASE(PN_LINK_FINAL); + CASE(PN_DELIVERY); + CASE(PN_TRANSPORT); + CASE(PN_TRANSPORT_AUTHENTICATED); + CASE(PN_TRANSPORT_ERROR); + CASE(PN_TRANSPORT_HEAD_CLOSED); + CASE(PN_TRANSPORT_TAIL_CLOSED); + CASE(PN_TRANSPORT_CLOSED); + CASE(PN_SELECTABLE_INIT); + CASE(PN_SELECTABLE_UPDATED); + CASE(PN_SELECTABLE_READABLE); + CASE(PN_SELECTABLE_WRITABLE); + CASE(PN_SELECTABLE_ERROR); + CASE(PN_SELECTABLE_EXPIRED); + CASE(PN_SELECTABLE_FINAL); + CASE(PN_CONNECTION_WAKE); + CASE(PN_LISTENER_ACCEPT); + CASE(PN_LISTENER_CLOSE); + CASE(PN_PROACTOR_INTERRUPT); + CASE(PN_PROACTOR_TIMEOUT); + CASE(PN_PROACTOR_INACTIVE); + CASE(PN_LISTENER_OPEN); + CASE(PN_RAW_CONNECTION_CONNECTED); + CASE(PN_RAW_CONNECTION_DISCONNECTED); + CASE(PN_RAW_CONNECTION_CLOSED_READ); + CASE(PN_RAW_CONNECTION_CLOSED_WRITE); + CASE(PN_RAW_CONNECTION_NEED_READ_BUFFERS); + CASE(PN_RAW_CONNECTION_NEED_WRITE_BUFFERS); + CASE(PN_RAW_CONNECTION_READ); + CASE(PN_RAW_CONNECTION_WRITTEN); + CASE(PN_RAW_CONNECTION_WAKE); + default: return "PN_UNKNOWN"; } return NULL; +#undef CASE } diff --git a/c/src/core/logger_private.h b/c/src/core/logger_private.h index 9c6231bc22..12c717d143 100644 --- a/c/src/core/logger_private.h +++ b/c/src/core/logger_private.h @@ -21,6 +21,10 @@ #include +#if __cplusplus +extern "C" { +#endif + struct pn_logger_t { pn_log_sink_t sink; intptr_t sink_context; @@ -56,4 +60,8 @@ void pni_logger_log_data(pn_logger_t *logger, pn_log_subsystem_t subsystem, pn_l pni_logger_log_data(logger, (pn_log_subsystem_t) (subsys), (pn_log_level_t) (sev), __VA_ARGS__); \ } while(0) +#if __cplusplus +} +#endif + #endif diff --git a/c/src/core/memory.c b/c/src/core/memory.c index 213eab19f8..2b00c06b7e 100644 --- a/c/src/core/memory.c +++ b/c/src/core/memory.c @@ -61,7 +61,7 @@ static struct stats { size_t subrequested; size_t suballoc; size_t subdealloc; -} stats[CID_pn_listener_socket+1] = {{0}}; // Just happens to be the last CID +} stats[CID_pn_raw_connection+1] = {{0}}; // Just happens to be the last CID static bool debug_memory = false; diff --git a/c/src/core/object/string.c b/c/src/core/object/string.c index 6d429ec81e..b505306740 100644 --- a/c/src/core/object/string.c +++ b/c/src/core/object/string.c @@ -230,7 +230,7 @@ int pn_string_vaddf(pn_string_t *string, const char *format, va_list ap) while (true) { va_copy(copy, ap); - int err = pni_vsnprintf(string->bytes + string->size, string->capacity - string->size, format, copy); + int err = vsnprintf(string->bytes + string->size, string->capacity - string->size, format, copy); va_end(copy); if (err < 0) { return err; diff --git a/c/src/core/transport.c b/c/src/core/transport.c index 7b81ddab94..c2f1c059f5 100644 --- a/c/src/core/transport.c +++ b/c/src/core/transport.c @@ -1114,7 +1114,7 @@ int pn_do_error(pn_transport_t *transport, const char *condition, const char *fm char buf[1024]; if (fmt) { // XXX: result - pni_vsnprintf(buf, 1024, fmt, ap); + vsnprintf(buf, 1024, fmt, ap); } else { buf[0] = '\0'; } @@ -1129,7 +1129,7 @@ int pn_do_error(pn_transport_t *transport, const char *condition, const char *fm const char *first = pn_condition_get_description(cond); if (first && fmt) { char extended[2048]; - pni_snprintf(extended, 2048, "%s (%s)", first, buf); + snprintf(extended, 2048, "%s (%s)", first, buf); pn_condition_set_description(cond, extended); } else if (fmt) { pn_condition_set_description(cond, buf); @@ -1335,14 +1335,19 @@ int pn_do_attach(pn_transport_t *transport, uint8_t frame_type, uint16_t channel bool snd_settle, rcv_settle; uint8_t snd_settle_mode, rcv_settle_mode; uint64_t max_msgsz; - int err = pn_data_scan(args, "D.[SIo?B?BD.[SIsIo.s]D.[SIsIo]..IL]", &name, &handle, + bool has_props; + pn_data_t *rem_props = pn_data(0); + int err = pn_data_scan(args, "D.[SIo?B?BD.[SIsIo.s]D.[SIsIo]..IL..?C]", &name, &handle, &is_sender, &snd_settle, &snd_settle_mode, &rcv_settle, &rcv_settle_mode, &source, &src_dr, &src_exp, &src_timeout, &src_dynamic, &dist_mode, &target, &tgt_dr, &tgt_exp, &tgt_timeout, &tgt_dynamic, - &idc, &max_msgsz); - if (err) return err; + &idc, &max_msgsz, &has_props, rem_props); + if (err) { + pn_free(rem_props); + return err; + } char strbuf[128]; // avoid malloc for most link names char *strheap = (name.size >= sizeof(strbuf)) ? (char *) malloc(name.size + 1) : NULL; char *strname = strheap ? strheap : strbuf; @@ -1353,12 +1358,14 @@ int pn_do_attach(pn_transport_t *transport, uint8_t frame_type, uint16_t channel if (!ssn) { pn_do_error(transport, "amqp:not-allowed", "no such channel: %u", channel); if (strheap) free(strheap); + pn_free(rem_props); return PN_EOS; } pn_link_t *link = pni_find_link(ssn, name, is_sender); if (link && (int32_t)link->state.remote_handle >= 0) { pn_do_error(transport, "amqp:invalid-field", "link name already attached: %s", strname); if (strheap) free(strheap); + pn_free(rem_props); return PN_EOS; } if (!link) { /* Make a new link for the attach */ @@ -1373,6 +1380,12 @@ int pn_do_attach(pn_transport_t *transport, uint8_t frame_type, uint16_t channel free(strheap); } + if (has_props) { + link->remote_properties = rem_props; + } else { + pn_free(rem_props); + } + pni_map_remote_handle(link, handle); PN_SET_REMOTE(link->endpoint.state, PN_REMOTE_ACTIVE); pn_terminus_t *rsrc = &link->remote_source; @@ -2078,7 +2091,7 @@ static int pni_process_link_setup(pn_transport_t *transport, pn_endpoint_t *endp if (err) return err; } else { int err = pn_post_frame(transport, AMQP_FRAME_TYPE, ssn_state->local_channel, - "DL[SIoBB?DL[SIsIoC?sCnMM]?DL[SIsIoCM]nnIL]", ATTACH, + "DL[SIoBB?DL[SIsIoC?sCnMM]?DL[SIsIoCM]nnILnnC]", ATTACH, pn_string_get(link->name), state->local_handle, endpoint->type == RECEIVER, @@ -2106,7 +2119,9 @@ static int pni_process_link_setup(pn_transport_t *transport, pn_endpoint_t *endp link->target.properties, link->target.capabilities, - 0, link->max_message_size); + 0, + link->max_message_size, + link->properties); if (err) return err; } } diff --git a/c/src/core/util.h b/c/src/core/util.h index 9ae62ea37c..ea049fd730 100644 --- a/c/src/core/util.h +++ b/c/src/core/util.h @@ -34,6 +34,10 @@ #include #include +#if __cplusplus +extern "C" { +#endif + ssize_t pn_quote_data(char *dst, size_t capacity, const char *src, size_t size); int pn_quote(pn_string_t *dst, const char *src, size_t size); bool pn_env_bool(const char *name); @@ -119,4 +123,8 @@ static inline pn_bytes_t pn_string_bytes(pn_string_t *s) { #define pn_min(X,Y) ((X) > (Y) ? (Y) : (X)) #define pn_max(X,Y) ((X) < (Y) ? (Y) : (X)) +#if __cplusplus +} +#endif + #endif /* util.h */ diff --git a/c/src/messenger/messenger.c b/c/src/messenger/messenger.c index bc7b57f205..bb7999d453 100644 --- a/c/src/messenger/messenger.c +++ b/c/src/messenger/messenger.c @@ -39,7 +39,7 @@ #include "core/log_private.h" #include "core/util.h" -#include "platform/platform.h" // pn_i_getpid, pn_i_now, pni_snprintf +#include "platform/platform.h" // pn_i_getpid, pn_i_now #include "platform/platform_fmt.h" #include "store.h" #include "subscription.h" @@ -983,7 +983,7 @@ static void pn_condition_report(const char *pfx, pn_condition_t *condition) pn_condition_redirect_port(condition)); } else if (pn_condition_is_set(condition)) { char error[1024]; - pni_snprintf(error, 1024, "(%s) %s", + snprintf(error, 1024, "(%s) %s", pn_condition_get_name(condition), pn_condition_get_description(condition)); pn_error_report(pfx, error); diff --git a/c/src/platform/platform.c b/c/src/platform/platform.c index 393f75c909..5e6a3ac41f 100644 --- a/c/src/platform/platform.c +++ b/c/src/platform/platform.c @@ -87,7 +87,7 @@ pn_timestamp_t pn_i_now(void) static void pn_i_strerror(int errnum, char *buf, size_t buflen) { // PROTON-1029 provide a simple default in case strerror fails - pni_snprintf(buf, buflen, "errno: %d", errnum); + snprintf(buf, buflen, "errno: %d", errnum); #ifdef USE_STRERROR_R strerror_r(errnum, buf, buflen); #elif USE_STRERROR_S @@ -106,17 +106,3 @@ int pn_i_error_from_errno(pn_error_t *error, const char *msg) code = PN_INTR; return pn_error_format(error, code, "%s: %s", msg, err); } - -#ifdef USE_ATOLL -#include -int64_t pn_i_atoll(const char* num) { - return atoll(num); -} -#elif USE_ATOI64 -#include -int64_t pn_i_atoll(const char* num) { - return _atoi64(num); -} -#else -#error "Don't know how to convert int64_t values on this platform" -#endif diff --git a/c/src/platform/platform.h b/c/src/platform/platform.h index c993bb98d7..9ec6ada091 100644 --- a/c/src/platform/platform.h +++ b/c/src/platform/platform.h @@ -25,6 +25,10 @@ #include "proton/types.h" #include "proton/error.h" +#if __cplusplus +extern "C" { +#endif + /** Get the current PID * * @return process id @@ -56,24 +60,7 @@ pn_timestamp_t pn_i_now(void); */ int pn_i_error_from_errno(pn_error_t *error, const char *msg); -/** Provide C99 atoll functinality. - * - * @param[in] num the string representation of the number. - * @return the integer value. - * - * @internal - */ -int64_t pn_i_atoll(const char* num); - -int pni_snprintf(char *buf, size_t count, const char *fmt, ...); -int pni_vsnprintf(char *buf, size_t count, const char *fmt, va_list ap); - -#ifndef _MSC_VER - -#define pni_snprintf snprintf -#define pni_vsnprintf vsnprintf - -#else +#ifdef _MSC_VER #if !defined(S_ISDIR) # define S_ISDIR(X) ((X) & _S_IFDIR) @@ -87,4 +74,8 @@ int pni_vsnprintf(char *buf, size_t count, const char *fmt, va_list ap); #endif #endif +#if __cplusplus +} +#endif + #endif /* platform.h */ diff --git a/c/src/platform/platform_fmt.h b/c/src/platform/platform_fmt.h index 17f95f3fd0..ff18d0e7cd 100644 --- a/c/src/platform/platform_fmt.h +++ b/c/src/platform/platform_fmt.h @@ -30,9 +30,7 @@ * Visual studio uses "%I" for size_t instead of "%z". */ -#ifndef __cplusplus - -// normal case +#define __STDC_FORMAT_MACROS #include #define PN_ZI "zi" #define PN_ZU "zu" @@ -56,30 +54,4 @@ #endif /* _OPENVMS */ -#else - -#ifdef _MSC_VER -#define PRIu8 "u" -#define PRIu16 "u" -#define PRIu32 "u" -#define PRIu64 "I64u" - -#define PRIi8 "i" -#define PRIi16 "i" -#define PRIi32 "i" -#define PRIi64 "I64i" - -#define PN_ZI "Ii" -#define PN_ZU "Iu" -#else -// Normal C++ -#define __STDC_FORMAT_MACROS -#include -#define PN_ZI "zi" -#define PN_ZU "zu" - -#endif /* _MSC_VER */ - -#endif /* __cplusplus */ - #endif /* platform_fmt.h */ diff --git a/c/src/proactor/epoll-internal.h b/c/src/proactor/epoll-internal.h index fd02817e13..78cad14dce 100644 --- a/c/src/proactor/epoll-internal.h +++ b/c/src/proactor/epoll-internal.h @@ -22,8 +22,18 @@ * */ +/* Enable POSIX features beyond c99 for modern pthread and standard strerror_r() */ +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif +/* Avoid GNU extensions, in particular the incompatible alternative strerror_r() */ +#undef _GNU_SOURCE + #include #include +#include +#include + #include #include @@ -50,7 +60,8 @@ typedef enum { PCONNECTION_IO, PCONNECTION_TIMER, LISTENER_IO, - PROACTOR_TIMER + PROACTOR_TIMER, + RAW_CONNECTION_IO } epoll_type_t; // Data to use with epoll. @@ -73,7 +84,8 @@ typedef struct ptimer_t { typedef enum { PROACTOR, PCONNECTION, - LISTENER + LISTENER, + RAW_CONNECTION } pcontext_type_t; typedef struct pcontext_t { @@ -82,7 +94,7 @@ typedef struct pcontext_t { pcontext_type_t type; bool working; bool on_wake_list; - bool wake_pending; // unprocessed eventfd wake callback (convert to bool?) + bool wake_pending; // unprocessed eventfd wake callback struct pcontext_t *wake_next; // wake list, guarded by proactor eventfd_mutex bool closing; // Next 4 are protected by the proactor mutex @@ -209,7 +221,7 @@ typedef struct pconnection_t { ptimer_t timer; // TODO: review one timerfd per connection const char *host, *port; uint32_t new_events; - int wake_count; + int wake_count; // TODO: protected by context.mutex so should be moved in there (also really bool) bool server; /* accept, not connect */ bool tick_pending; bool timer_armed; @@ -279,6 +291,70 @@ struct pn_listener_t { uint32_t sched_io_events; }; +typedef char strerrorbuf[1024]; /* used for pstrerror message buffer */ +void pstrerror(int err, strerrorbuf msg); + +// In general all locks to be held singly and shortly (possibly as spin locks). +// See above about lock ordering. + +static inline void pmutex_init(pthread_mutex_t *pm){ + pthread_mutexattr_t attr; + + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ADAPTIVE_NP); + if (pthread_mutex_init(pm, &attr)) { + perror("pthread failure"); + abort(); + } +} + +static inline void pmutex_finalize(pthread_mutex_t *m) { pthread_mutex_destroy(m); } +static inline void lock(pmutex *m) { pthread_mutex_lock(m); } +static inline void unlock(pmutex *m) { pthread_mutex_unlock(m); } + +static inline bool pconnection_has_event(pconnection_t *pc) { + return pn_connection_driver_has_event(&pc->driver); +} + +static inline bool listener_has_event(pn_listener_t *l) { + return pn_collector_peek(l->collector) || (l->pending_count); +} + +static inline bool proactor_has_event(pn_proactor_t *p) { + return pn_collector_peek(p->collector); +} + +bool wake_if_inactive(pn_proactor_t *p); +int pclosefd(pn_proactor_t *p, int fd); + +void proactor_add(pcontext_t *ctx); +bool proactor_remove(pcontext_t *ctx); + +bool unassign_thread(tslot_t *ts, tslot_state new_state); + +void pcontext_init(pcontext_t *ctx, pcontext_type_t t, pn_proactor_t *p); +bool wake(pcontext_t *ctx); +void wake_notify(pcontext_t *ctx); +void wake_done(pcontext_t *ctx); + +void psocket_init(psocket_t* ps, pn_proactor_t* p, epoll_type_t type); +bool start_polling(epoll_extended_t *ee, int epollfd); +void stop_polling(epoll_extended_t *ee, int epollfd); +void rearm_polling(epoll_extended_t *ee, int epollfd); + +int pgetaddrinfo(const char *host, const char *port, int flags, struct addrinfo **res); +void configure_socket(int sock); + +accepted_t *listener_accepted_next(pn_listener_t *listener); + +pcontext_t *pni_psocket_raw_context(psocket_t *ps); +pn_event_batch_t *pni_raw_connection_process(pcontext_t *c, bool sched_wake); + +typedef struct praw_connection_t praw_connection_t; +pcontext_t *pni_raw_connection_context(praw_connection_t *rc); +praw_connection_t *pni_batch_raw_connection(pn_event_batch_t* batch); +void pni_raw_connection_done(praw_connection_t *rc); + #ifdef __cplusplus } #endif diff --git a/c/src/proactor/epoll.c b/c/src/proactor/epoll.c index 20376e4f02..1e693fef1b 100644 --- a/c/src/proactor/epoll.c +++ b/c/src/proactor/epoll.c @@ -69,6 +69,8 @@ #include #include #include +#include +#include #include #include @@ -104,10 +106,8 @@ // could be several eventfds with random assignment of wakeables. -typedef char strerrorbuf[1024]; /* used for pstrerror message buffer */ - /* Like strerror_r but provide a default message if strerror_r fails */ -static void pstrerror(int err, strerrorbuf msg) { +void pstrerror(int err, strerrorbuf msg) { int e = strerror_r(err, msg, sizeof(strerrorbuf)); if (e) snprintf(msg, sizeof(strerrorbuf), "unknown error %d", err); } @@ -126,24 +126,6 @@ static void pstrerror(int err, strerrorbuf msg) { // First define a proactor mutex (pmutex) and timer mechanism (ptimer) to taste. // ======================================================================== -// In general all locks to be held singly and shortly (possibly as spin locks). -// See above about lock ordering. - -static void pmutex_init(pthread_mutex_t *pm){ - pthread_mutexattr_t attr; - - pthread_mutexattr_init(&attr); - pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ADAPTIVE_NP); - if (pthread_mutex_init(pm, &attr)) { - perror("pthread failure"); - abort(); - } -} - -static void pmutex_finalize(pthread_mutex_t *m) { pthread_mutex_destroy(m); } -static inline void lock(pmutex *m) { pthread_mutex_lock(m); } -static inline void unlock(pmutex *m) { pthread_mutex_unlock(m); } - /* epoll_ctl()/epoll_wait() do not form a memory barrier, so cached memory writes to struct epoll_extended_t in the EPOLL_ADD thread might not be visible to epoll_wait() thread. This function creates a memory barrier, @@ -284,7 +266,7 @@ const char *AMQP_PORT_NAME = "amqp"; PN_STRUCT_CLASSDEF(pn_proactor) PN_STRUCT_CLASSDEF(pn_listener) -static bool start_polling(epoll_extended_t *ee, int epollfd) { +bool start_polling(epoll_extended_t *ee, int epollfd) { if (ee->polling) return false; ee->polling = true; @@ -295,7 +277,7 @@ static bool start_polling(epoll_extended_t *ee, int epollfd) { return (epoll_ctl(epollfd, EPOLL_CTL_ADD, ee->fd, &ev) == 0); } -static void stop_polling(epoll_extended_t *ee, int epollfd) { +void stop_polling(epoll_extended_t *ee, int epollfd) { // TODO: check for error, return bool or just log? // TODO: is EPOLL_CTL_DEL ever needed beyond auto de-register when ee->fd is closed? if (ee->fd == -1 || !ee->polling || epollfd == -1) @@ -310,6 +292,19 @@ static void stop_polling(epoll_extended_t *ee, int epollfd) { ee->polling = false; } +void rearm_polling(epoll_extended_t *ee, int epollfd) { + struct epoll_event ev = {0}; + ev.data.ptr = ee; + ev.events = ee->wanted | EPOLLONESHOT; + memory_barrier(ee); + if (epoll_ctl(epollfd, EPOLL_CTL_MOD, ee->fd, &ev) == -1) + EPOLL_FATAL("arming polled file descriptor", errno); +} + +static void rearm(pn_proactor_t *p, epoll_extended_t *ee) { + rearm_polling(ee, p->epollfd); +} + /* * The proactor maintains a number of serialization contexts: each * connection, each listener, the proactor itself. The serialization @@ -337,7 +332,7 @@ static void stop_polling(epoll_extended_t *ee, int epollfd) { // Fake thread for temporarily disabling the scheduling of a context. static struct tslot_t *REWAKE_PLACEHOLDER = (struct tslot_t*) -1; -static void pcontext_init(pcontext_t *ctx, pcontext_type_t t, pn_proactor_t *p) { +void pcontext_init(pcontext_t *ctx, pcontext_type_t t, pn_proactor_t *p) { memset(ctx, 0, sizeof(*ctx)); pmutex_init(&ctx->mutex); ctx->proactor = p; @@ -348,8 +343,6 @@ static void pcontext_finalize(pcontext_t* ctx) { pmutex_finalize(&ctx->mutex); } -static void rearm(pn_proactor_t *p, epoll_extended_t *ee); - /* * Wake strategy with eventfd. * - wakees can be in the list only once @@ -409,7 +402,9 @@ static void pop_wake(pcontext_t *ctx) { } // part1: call with ctx->owner lock held, return true if notify required by caller -static bool wake(pcontext_t *ctx) { +// Note that this will return false if either there is a pending wake OR if we are already +// in the connection context that is to be woken (as we don't have to wake it up) +bool wake(pcontext_t *ctx) { bool notify = false; if (!ctx->wake_pending) { @@ -438,7 +433,7 @@ static bool wake(pcontext_t *ctx) { } // part2: make OS call without lock held -static inline void wake_notify(pcontext_t *ctx) { +void wake_notify(pcontext_t *ctx) { pn_proactor_t *p = ctx->proactor; if (p->eventfd == -1) return; @@ -446,7 +441,7 @@ static inline void wake_notify(pcontext_t *ctx) { } // call with owner lock held, once for each pop from the wake list -static inline void wake_done(pcontext_t *ctx) { +void wake_done(pcontext_t *ctx) { // assert(ctx->wake_pending > 0); ctx->wake_pending = false; } @@ -570,7 +565,7 @@ static bool rewake(pcontext_t *ctx) { } // Call with sched lock -static bool unassign_thread(tslot_t *ts, tslot_state new_state) { +bool unassign_thread(tslot_t *ts, tslot_state new_state) { pcontext_t *ctx = ts->context; bool notify = false; bool deleting = (ts->state == DELETING); @@ -658,7 +653,7 @@ static void make_runnable(pcontext_t *ctx) { -static void psocket_init(psocket_t* ps, pn_proactor_t* p, epoll_type_t type) +void psocket_init(psocket_t* ps, pn_proactor_t* p, epoll_type_t type) { ps->epoll_io.fd = -1; ps->epoll_io.type = type; @@ -696,8 +691,6 @@ static void set_pconnection(pn_connection_t* c, pconnection_t *pc) { static pn_event_batch_t *pconnection_process(pconnection_t *pc, uint32_t events, bool timeout, bool wake, bool topup); static void write_flush(pconnection_t *pc); static void listener_begin_close(pn_listener_t* l); -static void proactor_add(pcontext_t *ctx); -static bool proactor_remove(pcontext_t *ctx); static void poller_done(struct pn_proactor_t* p, tslot_t *ts); static inline pconnection_t *psocket_pconnection(psocket_t* ps) { @@ -739,18 +732,6 @@ static inline pconnection_t *batch_pconnection(pn_event_batch_t *batch) { containerof(batch, pconnection_t, batch) : NULL; } -static inline bool pconnection_has_event(pconnection_t *pc) { - return pn_connection_driver_has_event(&pc->driver); -} - -static inline bool listener_has_event(pn_listener_t *l) { - return pn_collector_peek(l->collector) || (l->pending_count); -} - -static inline bool proactor_has_event(pn_proactor_t *p) { - return pn_collector_peek(p->collector); -} - static void psocket_error_str(psocket_t *ps, const char *msg, const char* what) { pconnection_t *pc = psocket_pconnection(ps); if (pc) { @@ -778,15 +759,6 @@ static void psocket_gai_error(psocket_t *ps, int gai_err, const char* what) { psocket_error_str(ps, gai_strerror(gai_err), what); } -static void rearm(pn_proactor_t *p, epoll_extended_t *ee) { - struct epoll_event ev = {0}; - ev.data.ptr = ee; - ev.events = ee->wanted | EPOLLONESHOT; - memory_barrier(ee); - if (epoll_ctl(p->epollfd, EPOLL_CTL_MOD, ee->fd, &ev) == -1) - EPOLL_FATAL("arming polled file descriptor", errno); -} - static void listener_accepted_append(pn_listener_t *listener, accepted_t item) { if (listener->pending_first+listener->pending_count >= listener->backlog) return; @@ -794,7 +766,7 @@ static void listener_accepted_append(pn_listener_t *listener, accepted_t item) { listener->pending_count++; } -static accepted_t *listener_accepted_next(pn_listener_t *listener) { +accepted_t *listener_accepted_next(pn_listener_t *listener) { if (!listener->pending_count) return NULL; listener->pending_count--; @@ -859,7 +831,7 @@ static void proactor_rearm_overflow(pn_proactor_t *p) { } // Close an FD and rearm overflow listeners. Call with no listener locks held. -static int pclosefd(pn_proactor_t *p, int fd) { +int pclosefd(pn_proactor_t *p, int fd) { int err = close(fd); if (!err) proactor_rearm_overflow(p); return err; @@ -1054,6 +1026,15 @@ static inline bool pconnection_wclosed(pconnection_t *pc) { return pn_connection_driver_write_closed(&pc->driver); } +// Call with pc context locked. +static void pconnection_rearm_timer(pconnection_t *pc) { + if (!pc->timer_armed && !pc->timer.shutting_down && + pc->timer.epoll_io.fd >= 0 && pc->timer.epoll_io.polling) { + pc->timer_armed = true; + rearm(pc->psocket.proactor, &pc->timer.epoll_io); + } +} + /* Call only from working context (no competitor for pc->current_arm or connection driver). If true returned, caller must do pconnection_rearm(). @@ -1063,9 +1044,9 @@ static inline bool pconnection_wclosed(pconnection_t *pc) { EPOLL_CTL_DEL can prevent a parallel HUP/ERR error notification during close/shutdown. Let read()/write() return 0 or -1 to trigger cleanup logic. */ -static bool pconnection_rearm_check(pconnection_t *pc) { +static int pconnection_rearm_check(pconnection_t *pc) { if (pconnection_rclosed(pc) && pconnection_wclosed(pc)) { - return false; + return 0;; } uint32_t wanted_now = (pc->read_blocked && !pconnection_rclosed(pc)) ? EPOLLIN : 0; if (!pconnection_wclosed(pc)) { @@ -1076,16 +1057,15 @@ static bool pconnection_rearm_check(pconnection_t *pc) { wanted_now |= EPOLLOUT; } } - if (!wanted_now) return false; - if (wanted_now == pc->current_arm) return false; + if (!wanted_now) return 0; + if (wanted_now == pc->current_arm) return 0; - lock(&pc->rearm_mutex); /* unlocked in pconnection_rearm... */ - pc->current_arm = pc->psocket.epoll_io.wanted = wanted_now; - return true; /* ... so caller MUST call pconnection_rearm */ + return wanted_now; } -/* Call without lock */ -static inline void pconnection_rearm(pconnection_t *pc) { +static inline void pconnection_rearm(pconnection_t *pc, int wanted_now) { + lock(&pc->rearm_mutex); + pc->current_arm = pc->psocket.epoll_io.wanted = wanted_now; rearm(pc->psocket.proactor, &pc->psocket.epoll_io); unlock(&pc->rearm_mutex); // Return immediately. pc may have just been freed by another thread. @@ -1093,9 +1073,11 @@ static inline void pconnection_rearm(pconnection_t *pc) { /* Only call when context switch is imminent. Sched lock is highly contested. */ // Call with both context and sched locks. -static bool pconnection_sched_sync(pconnection_t *pc) { +static bool pconnection_sched_sync(pconnection_t *pc, bool *timerfd_fired) { + *timerfd_fired = false; if (pc->sched_timeout) { - pc->tick_pending = true; + *timerfd_fired = true;; + pc->timer_armed = false; pc->sched_timeout = false; } if (pc->psocket.sched_io_events) { @@ -1135,10 +1117,14 @@ static void pconnection_done(pconnection_t *pc) { // working context while the lock is held. Need sched_sync too to drain possible stale wake. pc->hog_count = 0; bool has_event = pconnection_has_event(pc); + bool timerfd_fired; // Do as little as possible while holding the sched lock lock(&p->sched_mutex); - pconnection_sched_sync(pc); + pconnection_sched_sync(pc, &timerfd_fired); unlock(&p->sched_mutex); + if (timerfd_fired) + if (ptimer_callback(&pc->timer) != 0) + pc->tick_pending = true; if (has_event || pconnection_work_pending(pc)) { self_wake = true; @@ -1159,10 +1145,11 @@ static void pconnection_done(pconnection_t *pc) { if (self_wake) notify = wake(&pc->context); - bool rearm = pconnection_rearm_check(pc); + pconnection_rearm_timer(pc); + int wanted = pconnection_rearm_check(pc); unlock(&pc->context.mutex); - if (rearm) pconnection_rearm(pc); // May free pc on another thread. Return. + if (wanted) pconnection_rearm(pc, wanted); // May free pc on another thread. Return. lock(&p->sched_mutex); if (unassign_thread(ts, UNUSED)) notify = true; @@ -1241,7 +1228,6 @@ static void pconnection_connected_lh(pconnection_t *pc); static void pconnection_maybe_connect_lh(pconnection_t *pc); static pn_event_batch_t *pconnection_process(pconnection_t *pc, uint32_t events, bool timeout, bool sched_wake, bool topup) { - bool inbound_wake = sched_wake; bool rearm_timer = false; bool timer_fired = false; bool waking = false; @@ -1264,10 +1250,7 @@ static pn_event_batch_t *pconnection_process(pconnection_t *pc, uint32_t events, pc->tick_pending = true; timer_fired = false; } - if (inbound_wake) { - wake_done(&pc->context); - inbound_wake = false; - } + if (sched_wake) wake_done(&pc->context); if (rearm_timer) pc->timer_armed = false; @@ -1405,9 +1388,13 @@ static pn_event_batch_t *pconnection_process(pconnection_t *pc, uint32_t events, } // Never stop working while work remains. hog_count exception to this rule is elsewhere. + bool timerfd_fired; lock(&pc->context.proactor->sched_mutex); - bool workers_free = pconnection_sched_sync(pc); + bool workers_free = pconnection_sched_sync(pc, &timerfd_fired); unlock(&pc->context.proactor->sched_mutex); + if (timerfd_fired) + if (ptimer_callback(&pc->timer) != 0) + pc->tick_pending = true; if (pconnection_work_pending(pc)) { goto retry; // TODO: get rid of goto without adding more locking @@ -1433,18 +1420,15 @@ static pn_event_batch_t *pconnection_process(pconnection_t *pc, uint32_t events, goto retry; } - if (!pc->timer_armed && !pc->timer.shutting_down && pc->timer.epoll_io.fd >= 0) { - pc->timer_armed = true; - rearm(pc->psocket.proactor, &pc->timer.epoll_io); - } - bool rearm_pc = pconnection_rearm_check(pc); // holds rearm_mutex until pconnection_rearm() below + pconnection_rearm_timer(pc); + int wanted = pconnection_rearm_check(pc); // holds rearm_mutex until pconnection_rearm() below unlock(&pc->context.mutex); - if (rearm_pc) pconnection_rearm(pc); // May free pc on another thread. Return right away. + if (wanted) pconnection_rearm(pc, wanted); // May free pc on another thread. Return right away. return NULL; } -static void configure_socket(int sock) { +void configure_socket(int sock) { int flags = fcntl(sock, F_GETFL); flags |= O_NONBLOCK; (void)fcntl(sock, F_SETFL, flags); // TODO: check for error @@ -1518,7 +1502,7 @@ static void pconnection_maybe_connect_lh(pconnection_t *pc) { pc->disconnected = true; } -static int pgetaddrinfo(const char *host, const char *port, int flags, struct addrinfo **res) +int pgetaddrinfo(const char *host, const char *port, int flags, struct addrinfo **res) { struct addrinfo hints = { 0 }; hints.ai_family = AF_UNSPEC; @@ -1532,7 +1516,7 @@ static inline bool is_inactive(pn_proactor_t *p) { } /* If inactive set need_inactive and return true if the proactor needs a wakeup */ -static bool wake_if_inactive(pn_proactor_t *p) { +bool wake_if_inactive(pn_proactor_t *p) { if (is_inactive(p)) { p->need_inactive = true; return wake(&p->context); @@ -2262,7 +2246,7 @@ static pn_event_batch_t *proactor_process(pn_proactor_t *p, bool timeout, bool i return NULL; } -static void proactor_add(pcontext_t *ctx) { +void proactor_add(pcontext_t *ctx) { pn_proactor_t *p = ctx->proactor; lock(&p->context.mutex); if (p->contexts) { @@ -2276,7 +2260,7 @@ static void proactor_add(pcontext_t *ctx) { // call with psocket's mutex held // return true if safe for caller to free psocket -static bool proactor_remove(pcontext_t *ctx) { +bool proactor_remove(pcontext_t *ctx) { pn_proactor_t *p = ctx->proactor; // Disassociate this context from scheduler if (!p->shutting_down) { @@ -2355,7 +2339,7 @@ static tslot_t *resume_one_thread(pn_proactor_t *p) { return ts; } -// Call with sched lock. +// Called with sched lock, returns with sched lock still held. static pn_event_batch_t *process(pcontext_t *ctx) { bool ctx_wake = false; ctx->sched_pending = false; @@ -2364,16 +2348,17 @@ static pn_event_batch_t *process(pcontext_t *ctx) { ctx->sched_wake = false; ctx_wake = true; } - + pn_proactor_t *p = ctx->proactor; + pn_event_batch_t* batch = NULL; switch (ctx->type) { case PROACTOR: { - pn_proactor_t *p = ctx->proactor; bool timeout = p->sched_timeout; if (timeout) p->sched_timeout = false; bool intr = p->sched_interrupt; if (intr) p->sched_interrupt = false; unlock(&p->sched_mutex); - return proactor_process(p, timeout, intr, ctx_wake); + batch = proactor_process(p, timeout, intr, ctx_wake); + break; } case PCONNECTION: { pconnection_t *pc = pcontext_pconnection(ctx); @@ -2381,8 +2366,9 @@ static pn_event_batch_t *process(pcontext_t *ctx) { if (events) pc->psocket.sched_io_events = 0; bool timeout = pc->sched_timeout; if (timeout) pc->sched_timeout = false; - unlock(&ctx->proactor->sched_mutex); - return pconnection_process(pc, events, timeout, ctx_wake, false); + unlock(&p->sched_mutex); + batch = pconnection_process(pc, events, timeout, ctx_wake, false); + break; } case LISTENER: { pn_listener_t *l = pcontext_listener(ctx); @@ -2396,13 +2382,20 @@ static pn_event_batch_t *process(pcontext_t *ctx) { if (ps->working_io_events) n_events++; } - unlock(&ctx->proactor->sched_mutex); - return listener_process(l, n_events, ctx_wake); + unlock(&p->sched_mutex); + batch = listener_process(l, n_events, ctx_wake); + break; + } + case RAW_CONNECTION: { + unlock(&p->sched_mutex); + batch = pni_raw_connection_process(ctx, ctx_wake); + break; } default: assert(NULL); } - return NULL; + lock(&p->sched_mutex); + return batch; } @@ -2447,20 +2440,20 @@ static pcontext_t *post_event(pn_proactor_t *p, struct epoll_event *evp) { ctx->sched_pending = true; break; - case PCONNECTION_IO: { - psocket_t *ps = containerof(ee, psocket_t, epoll_io); - pconnection_t *pc = psocket_pconnection(ps); + case PCONNECTION_TIMER: { + pconnection_t *pc = containerof(containerof(ee, ptimer_t, epoll_io), pconnection_t, timer); assert(pc); ctx = &pc->context; - ps->sched_io_events = evp->events; + pc->sched_timeout = true;; ctx->sched_pending = true; break; } - case PCONNECTION_TIMER: { - pconnection_t *pc = containerof(containerof(ee, ptimer_t, epoll_io), pconnection_t, timer); + case PCONNECTION_IO: { + psocket_t *ps = containerof(ee, psocket_t, epoll_io); + pconnection_t *pc = psocket_pconnection(ps); assert(pc); ctx = &pc->context; - pc->sched_timeout = true;; + ps->sched_io_events = evp->events; ctx->sched_pending = true; break; } @@ -2473,6 +2466,13 @@ static pcontext_t *post_event(pn_proactor_t *p, struct epoll_event *evp) { ctx->sched_pending = true; break; } + case RAW_CONNECTION_IO: { + psocket_t *ps = containerof(ee, psocket_t, epoll_io); + ctx = pni_psocket_raw_context(ps); + ps->sched_io_events = evp->events; + ctx->sched_pending = true; + break; + } } if (ctx && !ctx->runnable && !ctx->runner) return ctx; @@ -2565,7 +2565,6 @@ static pn_event_batch_t *proactor_do_epoll(pn_proactor_t* p, bool can_block) { tslot_t * ts = find_tslot(p); unlock(&p->tslot_mutex); ts->generation++; // wrapping OK. Just looking for any change - pn_event_batch_t *batch = NULL; lock(&p->sched_mutex); assert(ts->context == NULL || ts->earmarked); @@ -2578,11 +2577,11 @@ static pn_event_batch_t *proactor_do_epoll(pn_proactor_t* p, bool can_block) { pcontext_t *ctx = next_runnable(p, ts); if (ctx) { ts->state = BATCHING; - batch = process(ctx); // unlocks sched_lock before returning + pn_event_batch_t *batch = process(ctx); if (batch) { + unlock(&p->sched_mutex); return batch; } - lock(&p->sched_mutex); bool notify = unassign_thread(ts, PROCESSING); if (notify) { unlock(&p->sched_mutex); @@ -2839,6 +2838,14 @@ void pn_proactor_done(pn_proactor_t *p, pn_event_batch_t *batch) { check_earmark_override(p, ts); return; } + praw_connection_t *rc = pni_batch_raw_connection(batch); + if (rc) { + tslot_t *ts = pni_raw_connection_context(rc)->runner; + pni_raw_connection_done(rc); + // rc possibly freed/invalid + check_earmark_override(p, ts); + return; + } pn_proactor_t *bp = batch_proactor(batch); if (bp == p) { bool notify = false; @@ -2941,6 +2948,7 @@ void pn_proactor_disconnect(pn_proactor_t *p, pn_condition_t *cond) { bool do_free = false; bool ctx_notify = false; pmutex *ctx_mutex = NULL; + // TODO: Need to extend this for raw connections too pconnection_t *pc = pcontext_pconnection(ctx); if (pc) { ctx_mutex = &pc->context.mutex; diff --git a/c/src/proactor/epoll_raw_connection.c b/c/src/proactor/epoll_raw_connection.c new file mode 100644 index 0000000000..2b9c4d47ac --- /dev/null +++ b/c/src/proactor/epoll_raw_connection.c @@ -0,0 +1,376 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/* This is currently epoll implementation specific - and will need changing for the other proactors */ + +#include "epoll-internal.h" +#include "proactor-internal.h" +#include "raw_connection-internal.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +/* epoll specific raw connection struct */ +struct praw_connection_t { + pcontext_t context; + struct pn_raw_connection_t raw_connection; + psocket_t psocket; + struct pn_netaddr_t local, remote; /* Actual addresses */ + pmutex rearm_mutex; /* protects pconnection_rearm from out of order arming*/ + pn_event_batch_t batch; + struct addrinfo *addrinfo; /* Resolved address list */ + struct addrinfo *ai; /* Current connect address */ + bool connected; + bool disconnected; + bool waking; // TODO: This is actually protected by context.mutex so should be moved into context (pconnection too) +}; + +static void psocket_error(praw_connection_t *rc, int err, const char* msg) { + pn_condition_t *cond = rc->raw_connection.condition; + if (!pn_condition_is_set(cond)) { /* Preserve older error information */ + strerrorbuf what; + pstrerror(err, what); + char addr[PN_MAX_ADDR]; + pn_netaddr_str(&rc->remote, addr, sizeof(addr)); + pn_condition_format(cond, PNI_IO_CONDITION, "%s - %s %s", what, msg, addr); + } +} + +static void psocket_gai_error(praw_connection_t *rc, int gai_err, const char* what, const char *addr) { + pn_condition_format(rc->raw_connection.condition, PNI_IO_CONDITION, "%s - %s %s", + gai_strerror(gai_err), what, addr); +} + +static void praw_connection_connected_lh(praw_connection_t *prc) { + // Need to check socket for connection error + prc->connected = true; + if (prc->addrinfo) { + freeaddrinfo(prc->addrinfo); + prc->addrinfo = NULL; + } + prc->ai = NULL; + socklen_t len = sizeof(prc->remote.ss); + (void)getpeername(prc->psocket.epoll_io.fd, (struct sockaddr*)&prc->remote.ss, &len); + + pni_raw_connected(&prc->raw_connection); +} + +/* multi-address connections may call pconnection_start multiple times with diffferent FDs */ +static void praw_connection_start(praw_connection_t *prc, int fd) { + int efd = prc->psocket.proactor->epollfd; + + /* Get the local socket name now, get the peer name in pconnection_connected */ + socklen_t len = sizeof(prc->local.ss); + (void)getsockname(fd, (struct sockaddr*)&prc->local.ss, &len); + + epoll_extended_t *ee = &prc->psocket.epoll_io; + if (ee->polling) { /* This is not the first attempt, stop polling and close the old FD */ + int fd = ee->fd; /* Save fd, it will be set to -1 by stop_polling */ + stop_polling(ee, efd); + pclosefd(prc->psocket.proactor, fd); + } + ee->fd = fd; + ee->wanted = EPOLLIN | EPOLLOUT; + start_polling(ee, efd); // TODO: check for error +} + +/* Called on initial connect, and if connection fails to try another address */ +static void praw_connection_maybe_connect_lh(praw_connection_t *prc) { + while (prc->ai) { /* Have an address */ + struct addrinfo *ai = prc->ai; + prc->ai = prc->ai->ai_next; /* Move to next address in case this fails */ + int fd = socket(ai->ai_family, SOCK_STREAM, 0); + if (fd >= 0) { + configure_socket(fd); + if (!connect(fd, ai->ai_addr, ai->ai_addrlen) || errno == EINPROGRESS) { + + /* Until we finish connecting save away the address we're trying to connect to */ + memcpy((struct sockaddr *) &prc->remote.ss, ai->ai_addr, ai->ai_addrlen); + + praw_connection_start(prc, fd); + return; /* Async connection started */ + } else { + close(fd); + } + } + /* connect failed immediately, go round the loop to try the next addr */ + } + int err; + socklen_t errlen = sizeof(err); + getsockopt(prc->psocket.epoll_io.fd, SOL_SOCKET, SO_ERROR, (void *)&err, &errlen); + psocket_error(prc, err, "on connect"); + + freeaddrinfo(prc->addrinfo); + prc->addrinfo = NULL; + prc->disconnected = true; +} + +// +// Raw socket API +// +static pn_event_t * pni_raw_batch_next(pn_event_batch_t *batch); + +static void praw_connection_init(praw_connection_t *prc, pn_proactor_t *p, pn_raw_connection_t *rc) { + pcontext_init(&prc->context, RAW_CONNECTION, p); + psocket_init(&prc->psocket, p, RAW_CONNECTION_IO); + + prc->connected = false; + prc->disconnected = false; + prc->waking = false; + prc->batch.next_event = pni_raw_batch_next; + + pmutex_init(&prc->rearm_mutex); +} + +static void praw_connection_cleanup(praw_connection_t *prc) { + int fd = prc->psocket.epoll_io.fd; + stop_polling(&prc->psocket.epoll_io, prc->psocket.proactor->epollfd); + if (fd != -1) + pclosefd(prc->psocket.proactor, fd); + + lock(&prc->context.mutex); + bool can_free = proactor_remove(&prc->context); + unlock(&prc->context.mutex); + if (can_free) { + free(prc); + } + // else proactor_disconnect logic owns prc and its final free +} + +pn_raw_connection_t *pn_raw_connection(void) { + praw_connection_t *conn = (praw_connection_t*) calloc(1, sizeof(praw_connection_t)); + if (!conn) return NULL; + + pni_raw_initialize(&conn->raw_connection); + + return &conn->raw_connection; +} + +void pn_raw_connection_free(pn_raw_connection_t *conn) { +} + +void pn_proactor_raw_connect(pn_proactor_t *p, pn_raw_connection_t *rc, const char *addr) { + assert(rc); + praw_connection_t *prc = containerof(rc, praw_connection_t, raw_connection); + praw_connection_init(prc, p, rc); + // TODO: check case of proactor shutting down + + lock(&prc->context.mutex); + proactor_add(&prc->context); + + bool notify = false; + bool notify_proactor = false; + + const char *host; + const char *port; + size_t addrlen = strlen(addr); + char *addr_buf = (char*) alloca(addrlen+1); + pni_parse_addr(addr, addr_buf, addrlen+1, &host, &port); + + int gai_error = pgetaddrinfo(host, port, 0, &prc->addrinfo); + if (!gai_error) { + prc->ai = prc->addrinfo; + praw_connection_maybe_connect_lh(prc); /* Start connection attempts */ + if (prc->disconnected) notify = wake(&prc->context); + } else { + psocket_gai_error(prc, gai_error, "connect to ", addr); + prc->disconnected = true; + notify = wake(&prc->context); + lock(&p->context.mutex); + notify_proactor = wake_if_inactive(p); + unlock(&p->context.mutex); + } + + /* We need to issue INACTIVE on immediate failure */ + unlock(&prc->context.mutex); + if (notify) wake_notify(&prc->context); + if (notify_proactor) wake_notify(&p->context); +} + +void pn_listener_raw_accept(pn_listener_t *l, pn_raw_connection_t *rc) { + assert(rc); + praw_connection_t *prc = containerof(rc, praw_connection_t, raw_connection); + praw_connection_init(prc, pn_listener_proactor(l), rc); + // TODO: fuller sanity check on input args + + int err = 0; + int fd = -1; + bool notify = false; + lock(&l->context.mutex); + if (l->context.closing) + err = EBADF; + else { + accepted_t *a = listener_accepted_next(l); + if (a) { + fd = a->accepted_fd; + a->accepted_fd = -1; + } + else err = EWOULDBLOCK; + } + + proactor_add(&prc->context); + + lock(&prc->context.mutex); + if (fd >= 0) { + configure_socket(fd); + praw_connection_start(prc, fd); + praw_connection_connected_lh(prc); + } else { + psocket_error(prc, err, "pn_listener_accept"); + } + + if (!l->context.working && listener_has_event(l)) { + notify = wake(&l->context); + } + unlock(&prc->context.mutex); + unlock(&l->context.mutex); + if (notify) wake_notify(&l->context); +} + +const pn_netaddr_t *pn_raw_connection_local_addr(pn_raw_connection_t *rc) { + praw_connection_t *prc = containerof(rc, praw_connection_t, raw_connection); + if (!prc) return NULL; + return &prc->local; +} + +const pn_netaddr_t *pn_raw_connection_remote_addr(pn_raw_connection_t *rc) { + praw_connection_t *prc = containerof(rc, praw_connection_t, raw_connection); + if (!prc) return NULL; + return &prc->remote; +} + +void pn_raw_connection_wake(pn_raw_connection_t *rc) { + bool notify = false; + praw_connection_t *prc = containerof(rc, praw_connection_t, raw_connection); + if (prc) { + lock(&prc->context.mutex); + if (!prc->context.closing) { + prc->waking = true; + notify = wake(&prc->context); + } + unlock(&prc->context.mutex); + } + if (notify) wake_notify(&prc->context); +} + +static pn_event_t *pni_raw_batch_next(pn_event_batch_t *batch) { + pn_raw_connection_t *raw = &containerof(batch, praw_connection_t, batch)->raw_connection; + return pni_raw_event_next(raw); +} + +pcontext_t *pni_psocket_raw_context(psocket_t* ps) { + return &containerof(ps, praw_connection_t, psocket)->context; +} + +praw_connection_t *pni_batch_raw_connection(pn_event_batch_t *batch) { + return (batch->next_event == pni_raw_batch_next) ? + containerof(batch, praw_connection_t, batch) : NULL; +} + +pcontext_t *pni_raw_connection_context(praw_connection_t *rc) { + return &rc->context; +} + +static long snd(int fd, const void* b, size_t s) { + return send(fd, b, s, MSG_NOSIGNAL | MSG_DONTWAIT); +} + +static long rcv(int fd, void* b, size_t s) { + return recv(fd, b, s, MSG_DONTWAIT); +} + +static void set_error(pn_raw_connection_t *conn, const char *msg, int err) { + psocket_error(containerof(conn, praw_connection_t, raw_connection), err, msg); +} + +pn_event_batch_t *pni_raw_connection_process(pcontext_t *c, bool sched_wake) { + praw_connection_t *rc = containerof(c, praw_connection_t, context); + int events = rc->psocket.sched_io_events; + int fd = rc->psocket.epoll_io.fd; + if (!rc->connected) { + if (events & (EPOLLHUP | EPOLLERR)) { + praw_connection_maybe_connect_lh(rc); + } + if (rc->disconnected) { + pni_raw_disconnect(&rc->raw_connection); + return &rc->batch; + } + if (events & (EPOLLHUP | EPOLLERR)) { + return NULL; + } + praw_connection_connected_lh(rc); + } + + bool wake = false; + lock(&c->mutex); + c->working = true; + if (sched_wake) wake_done(c); + wake = sched_wake || rc->waking; + rc->waking = false; + unlock(&c->mutex); + + if (wake) pni_raw_wake(&rc->raw_connection); + if (events & EPOLLIN) pni_raw_read(&rc->raw_connection, fd, rcv, set_error); + if (events & EPOLLOUT) pni_raw_write(&rc->raw_connection, fd, snd, set_error); + return &rc->batch; +} + +void pni_raw_connection_done(praw_connection_t *rc) { + bool self_notify = false; + lock(&rc->context.mutex); + pn_proactor_t *p = rc->context.proactor; + tslot_t *ts = rc->context.runner; + rc->context.working = false; + self_notify = rc->waking && wake(&rc->context); + unlock(&rc->context.mutex); + if (self_notify) wake_notify(&rc->context); + + pn_raw_connection_t *raw = &rc->raw_connection; + int wanted = + (pni_raw_can_read(raw) ? EPOLLIN : 0) | + (pni_raw_can_write(raw) ? EPOLLOUT : 0); + if (wanted) { + rc->psocket.epoll_io.wanted = wanted; + rearm_polling(&rc->psocket.epoll_io, p->epollfd); // TODO: check for error + } else { + bool finished_disconnect = raw->rclosed && raw->wclosed && !raw->disconnectpending; + if (finished_disconnect) { + // If we're closed and we've sent the disconnect then close + pni_raw_finalize(raw); + praw_connection_cleanup(rc); + } + } + + lock(&p->sched_mutex); + bool notify = unassign_thread(ts, UNUSED); + unlock(&p->sched_mutex); + if (notify) wake_notify(&p->context); +} diff --git a/c/src/proactor/libuv.c b/c/src/proactor/libuv.c index 4779aba67a..409d6c5571 100644 --- a/c/src/proactor/libuv.c +++ b/c/src/proactor/libuv.c @@ -32,7 +32,9 @@ #include #include #include +#include #include +#include #include #include @@ -1347,3 +1349,11 @@ pn_millis_t pn_proactor_now(void) { int64_t pn_proactor_now_64(void) { return uv_hrtime() / 1000000; // uv_hrtime returns time in nanoseconds } + +// Empty stubs for raw connection code +pn_raw_connection_t *pn_raw_connection(void) { return NULL; } +void pn_proactor_raw_connect(pn_proactor_t *p, pn_raw_connection_t *rc, const char *addr) {} +void pn_listener_raw_accept(pn_listener_t *l, pn_raw_connection_t *rc) {} +void pn_raw_connection_wake(pn_raw_connection_t *conn) {} +const struct pn_netaddr_t *pn_raw_connection_local_addr(pn_raw_connection_t *connection) { return NULL; } +const struct pn_netaddr_t *pn_raw_connection_remote_addr(pn_raw_connection_t *connection) { return NULL; } diff --git a/c/src/proactor/netaddr-internal.c b/c/src/proactor/netaddr-internal.c new file mode 100644 index 0000000000..8abdab24ee --- /dev/null +++ b/c/src/proactor/netaddr-internal.c @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifdef _WIN32 +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0501 +#endif +#if _WIN32_WINNT < 0x0501 +#error "Proton requires Windows API support for XP or later." +#endif +#include +#include +#else +/* Enable POSIX features beyond c99 for modern pthread and standard strerror_r() */ +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif +/* Avoid GNU extensions, in particular the incompatible alternative strerror_r() */ +#undef _GNU_SOURCE +#include +#include +#include +#endif + +#include "netaddr-internal.h" + +#include +#include + +/* Common code for proactors that use the POSIX/Winsock sockaddr library for socket addresses. */ + +const struct sockaddr *pn_netaddr_sockaddr(const pn_netaddr_t *na) { + return na ? (struct sockaddr*)&na->ss : NULL; +} + +size_t pn_netaddr_socklen(const pn_netaddr_t *na) { + if (!na) return 0; + switch (na->ss.sa.sa_family) { + case AF_INET: return sizeof(struct sockaddr_in); + case AF_INET6: return sizeof(struct sockaddr_in6); + default: return sizeof(na->ss); + } +} + +const pn_netaddr_t *pn_netaddr_next(const pn_netaddr_t *na) { + return na ? na->next : NULL; +} + +#ifndef NI_MAXHOST +# define NI_MAXHOST 1025 +#endif + +#ifndef NI_MAXSERV +# define NI_MAXSERV 32 +#endif + +int pn_netaddr_host_port(const pn_netaddr_t* na, char *host, size_t hlen, char *port, size_t plen) { + return getnameinfo(pn_netaddr_sockaddr(na), pn_netaddr_socklen(na), + host, hlen, port, plen, NI_NUMERICHOST | NI_NUMERICSERV); +} + +int pn_netaddr_str(const pn_netaddr_t* na, char *buf, size_t len) { + char host[NI_MAXHOST]; + char port[NI_MAXSERV]; + int err = pn_netaddr_host_port(na, host, sizeof(host), port, sizeof(port)); + if (!err) { + return pn_proactor_addr(buf, len, host, port); + } else { + if (buf) *buf = '\0'; + return 0; + } +} diff --git a/c/src/proactor/netaddr-internal.h b/c/src/proactor/netaddr-internal.h index c1013daf7d..501bb38f8c 100644 --- a/c/src/proactor/netaddr-internal.h +++ b/c/src/proactor/netaddr-internal.h @@ -20,59 +20,19 @@ * under the License. */ -#include - -/* Common code for proactors that use the POSIX/Winsock sockaddr library for socket addresses. */ +#include struct pn_netaddr_t { - struct sockaddr_storage ss; - pn_netaddr_t *next; + union { + struct sockaddr sa; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + } ss; + struct pn_netaddr_t *next; }; -const struct sockaddr *pn_netaddr_sockaddr(const pn_netaddr_t *na) { - return na ? (struct sockaddr*)&na->ss : NULL; -} - -size_t pn_netaddr_socklen(const pn_netaddr_t *na) { - if (!na) return 0; - switch (na->ss.ss_family) { - case AF_INET: return sizeof(struct sockaddr_in); - case AF_INET6: return sizeof(struct sockaddr_in6); - default: return sizeof(na->ss); - } -} - -const pn_netaddr_t *pn_netaddr_next(const pn_netaddr_t *na) { - return na ? na->next : NULL; -} - -#ifndef NI_MAXHOST -# define NI_MAXHOST 1025 -#endif - -#ifndef NI_MAXSERV -# define NI_MAXSERV 32 -#endif - -int pn_netaddr_host_port(const pn_netaddr_t* na, char *host, size_t hlen, char *port, size_t plen) { - return getnameinfo(pn_netaddr_sockaddr(na), pn_netaddr_socklen(na), - host, hlen, port, plen, NI_NUMERICHOST | NI_NUMERICSERV); -} - -int pn_netaddr_str(const pn_netaddr_t* na, char *buf, size_t len) { - char host[NI_MAXHOST]; - char port[NI_MAXSERV]; - int err = pn_netaddr_host_port(na, host, sizeof(host), port, sizeof(port)); - if (!err) { - return pn_proactor_addr(buf, len, host, port); - } else { - if (buf) *buf = '\0'; - return 0; - } -} - /* Return port or -1 if sa is not a known address type */ -static int get_port(const struct sockaddr *sa) { +static inline int get_port(const struct sockaddr *sa) { switch (sa->sa_family) { case AF_INET: return ((struct sockaddr_in*)sa)->sin_port; case AF_INET6: return ((struct sockaddr_in6*)sa)->sin6_port; @@ -81,7 +41,7 @@ static int get_port(const struct sockaddr *sa) { } /* Set the port in sa or do nothing if it is not a known address type */ -static void set_port(struct sockaddr *sa, uint16_t port) { +static inline void set_port(struct sockaddr *sa, uint16_t port) { switch (sa->sa_family) { case AF_INET: ((struct sockaddr_in*)sa)->sin_port = port; break; case AF_INET6: ((struct sockaddr_in6*)sa)->sin6_port = port; break; @@ -90,7 +50,7 @@ static void set_port(struct sockaddr *sa, uint16_t port) { } /* If want has port=0 and got has port > 0 then return port of got, else return 0 */ -static uint16_t check_dynamic_port(const struct sockaddr *want, const struct sockaddr *got) { +static inline uint16_t check_dynamic_port(const struct sockaddr *want, const struct sockaddr *got) { if (get_port(want) == 0) { int port = get_port(got); if (port > 0) return (uint16_t)port; diff --git a/c/src/proactor/proactor-internal.c b/c/src/proactor/proactor-internal.c index bc597e82fc..e645ae970f 100644 --- a/c/src/proactor/proactor-internal.c +++ b/c/src/proactor/proactor-internal.c @@ -39,14 +39,7 @@ static const char *AMQPS_PORT_NAME = "amqps"; const char *PNI_IO_CONDITION = "proton:io"; int pn_proactor_addr(char *buf, size_t len, const char *host, const char *port) { - /* Don't use snprintf, Windows is not C99 compliant and snprintf is broken. */ - if (buf && len > 0) { - buf[0] = '\0'; - if (host) strncat(buf, host, len); - strncat(buf, ":", len); - if (port) strncat(buf, port, len); - } - return (host ? strlen(host) : 0) + (port ? strlen(port) : 0) + 1; + return snprintf(buf, len, "%s:%s", host ? host : "", port ? port : ""); } int pni_parse_addr(const char *addr, char *buf, size_t len, const char **host, const char **port) diff --git a/c/src/proactor/raw_connection-internal.h b/c/src/proactor/raw_connection-internal.h new file mode 100644 index 0000000000..02c3af2d7d --- /dev/null +++ b/c/src/proactor/raw_connection-internal.h @@ -0,0 +1,109 @@ +#ifndef PROACTOR_RAW_CONNECTION_INTERNAL_H +#define PROACTOR_RAW_CONNECTION_INTERNAL_H +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + read_buffer_count = 16, + write_buffer_count = 16 +}; + +typedef enum { + buff_rempty = 0, + buff_unread = 1, + buff_read = 2, + buff_wempty = 4, + buff_unwritten = 5, + buff_written = 6 +} buff_type; + +typedef uint16_t buff_ptr; // This is always the index+1 so that 0 can be special + +typedef struct pbuffer_t { + uintptr_t context; + char *bytes; + uint32_t capacity; + uint32_t size; + uint32_t offset; + buff_ptr next; + uint8_t type; // For debugging +} pbuffer_t; + +struct pn_raw_connection_t { + pbuffer_t rbuffers[read_buffer_count]; + pbuffer_t wbuffers[write_buffer_count]; + pn_condition_t *condition; + pn_collector_t *collector; + pn_record_t *attachments; + uint32_t unwritten_offset; + uint16_t rbuffer_count; + uint16_t wbuffer_count; + + buff_ptr rbuffer_first_empty; + buff_ptr rbuffer_first_unused; + buff_ptr rbuffer_last_unused; + buff_ptr rbuffer_first_read; + buff_ptr rbuffer_last_read; + + buff_ptr wbuffer_first_empty; + buff_ptr wbuffer_first_towrite; + buff_ptr wbuffer_last_towrite; + buff_ptr wbuffer_first_written; + buff_ptr wbuffer_last_written; + bool rneedbufferevent; + bool wneedbufferevent; + bool rpending; + bool wpending; + bool rclosed; + bool wclosed; + bool rclosedpending; + bool wclosedpending; + bool rdrainpending; + bool wdrainpending; + bool disconnectpending; + bool wakepending; +}; + +/* + * Raw connection internal API + */ +bool pni_raw_validate(pn_raw_connection_t *conn); +void pni_raw_connected(pn_raw_connection_t *conn); +void pni_raw_disconnect(pn_raw_connection_t *conn); +void pni_raw_process(pn_raw_connection_t *conn, int events, bool wake); +void pni_raw_wake(pn_raw_connection_t *conn); +void pni_raw_read(pn_raw_connection_t *conn, int sock, long (*recv)(int, void*, size_t), void (*set_error)(pn_raw_connection_t *, const char *, int)); +void pni_raw_write(pn_raw_connection_t *conn, int sock, long (*send)(int, const void*, size_t), void (*set_error)(pn_raw_connection_t *, const char *, int)); +bool pni_raw_can_read(pn_raw_connection_t *conn); +bool pni_raw_can_write(pn_raw_connection_t *conn); +pn_event_t *pni_raw_event_next(pn_raw_connection_t *conn); +void pni_raw_initialize(pn_raw_connection_t *conn); +void pni_raw_finalize(pn_raw_connection_t *conn); + +#ifdef __cplusplus +} +#endif + +#endif // PROACTOR_RAW_CONNECTION_INTERNAL_H diff --git a/c/src/proactor/raw_connection.c b/c/src/proactor/raw_connection.c new file mode 100644 index 0000000000..a225df927c --- /dev/null +++ b/c/src/proactor/raw_connection.c @@ -0,0 +1,581 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/* Enable POSIX features beyond c99 for modern pthread and standard strerror_r() */ +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif +/* Avoid GNU extensions, in particular the incompatible alternative strerror_r() */ +#undef _GNU_SOURCE + +#include "proton/raw_connection.h" + +#include "proton/event.h" +#include "proton/listener.h" +#include "proton/object.h" +#include "proton/proactor.h" +#include "proton/types.h" + +#include "core/util.h" +#include "proactor-internal.h" + +#include +#include +#include +#include +#include + +#include "raw_connection-internal.h" + +PN_STRUCT_CLASSDEF(pn_raw_connection) + +void pni_raw_initialize(pn_raw_connection_t *conn) { + // Link together free lists + for (buff_ptr i = 1; i<=read_buffer_count; i++) { + conn->rbuffers[i-1].next = i==read_buffer_count ? 0 : i+1; + conn->rbuffers[i-1].type = buff_rempty; + conn->wbuffers[i-1].next = i==read_buffer_count ? 0 : i+1; + conn->wbuffers[i-1].type = buff_wempty; + } + + conn->condition = pn_condition(); + conn->collector = pn_collector(); + conn->attachments = pn_record(); + + conn->rbuffer_first_empty = 1; + conn->wbuffer_first_empty = 1; +} + +bool pni_raw_validate(pn_raw_connection_t *conn) { + int rempty_count = 0; + for (buff_ptr i = conn->rbuffer_first_empty; i; i = conn->rbuffers[i-1].next) { + if (conn->rbuffers[i-1].type != buff_rempty) return false; + rempty_count++; + } + int runused_count = 0; + for (buff_ptr i = conn->rbuffer_first_unused; i; i = conn->rbuffers[i-1].next) { + if (conn->rbuffers[i-1].type != buff_unread) return false; + runused_count++; + } + int rread_count = 0; + for (buff_ptr i = conn->rbuffer_first_read; i; i = conn->rbuffers[i-1].next) { + if (conn->rbuffers[i-1].type != buff_read) return false; + rread_count++; + } + if (rempty_count+runused_count+rread_count != read_buffer_count) return false; + if (!conn->rbuffer_first_unused && conn->rbuffer_last_unused) return false; + if (conn->rbuffer_last_unused && + (conn->rbuffers[conn->rbuffer_last_unused-1].type != buff_unread || conn->rbuffers[conn->rbuffer_last_unused-1].next != 0)) return false; + if (!conn->rbuffer_first_read && conn->rbuffer_last_read) return false; + if (conn->rbuffer_last_read && + (conn->rbuffers[conn->rbuffer_last_read-1].type != buff_read || conn->rbuffers[conn->rbuffer_last_read-1].next != 0)) return false; + + int wempty_count = 0; + for (buff_ptr i = conn->wbuffer_first_empty; i; i = conn->wbuffers[i-1].next) { + if (conn->wbuffers[i-1].type != buff_wempty) return false; + wempty_count++; + } + int wunwritten_count = 0; + for (buff_ptr i = conn->wbuffer_first_towrite; i; i = conn->wbuffers[i-1].next) { + if (conn->wbuffers[i-1].type != buff_unwritten) return false; + wunwritten_count++; + } + int wwritten_count = 0; + for (buff_ptr i = conn->wbuffer_first_written; i; i = conn->wbuffers[i-1].next) { + if (conn->wbuffers[i-1].type != buff_written) return false; + wwritten_count++; + } + if (wempty_count+wunwritten_count+wwritten_count != write_buffer_count) return false; + if (!conn->wbuffer_first_towrite && conn->wbuffer_last_towrite) return false; + if (conn->wbuffer_last_towrite && + (conn->wbuffers[conn->wbuffer_last_towrite-1].type != buff_unwritten || conn->wbuffers[conn->wbuffer_last_towrite-1].next != 0)) return false; + if (!conn->wbuffer_first_written && conn->wbuffer_last_written) return false; + if (conn->wbuffer_last_written && + (conn->wbuffers[conn->wbuffer_last_written-1].type != buff_written || conn->wbuffers[conn->wbuffer_last_written-1].next != 0)) return false; + return true; +} + +void pni_raw_finalize(pn_raw_connection_t *conn) { + pn_condition_free(conn->condition); + pn_collector_free(conn->collector); + pn_free(conn->attachments); +} + +size_t pn_raw_connection_read_buffers_capacity(pn_raw_connection_t *conn) { + assert(conn); + return read_buffer_count - conn->rbuffer_count; +} + +size_t pn_raw_connection_write_buffers_capacity(pn_raw_connection_t *conn) { + assert(conn); + return write_buffer_count-conn->wbuffer_count; +} + +size_t pn_raw_connection_give_read_buffers(pn_raw_connection_t *conn, pn_raw_buffer_t const *buffers, size_t num) { + assert(conn); + size_t can_take = pn_min(num, pn_raw_connection_read_buffers_capacity(conn)); + if ( can_take==0 ) return 0; + + buff_ptr current = conn->rbuffer_first_empty; + assert(current); + + buff_ptr previous; + for (size_t i = 0; i < can_take; i++) { + // Get next free + assert(conn->rbuffers[current-1].type == buff_rempty); + conn->rbuffers[current-1].context = buffers[i].context; + conn->rbuffers[current-1].bytes = buffers[i].bytes; + conn->rbuffers[current-1].capacity = buffers[i].capacity; + conn->rbuffers[current-1].size = 0; + conn->rbuffers[current-1].offset = buffers[i].offset; + conn->rbuffers[current-1].type = buff_unread; + + previous = current; + current = conn->rbuffers[current-1].next; + } + if (!conn->rbuffer_last_unused) { + conn->rbuffer_last_unused = previous; + } + + conn->rbuffers[previous-1].next = conn->rbuffer_first_unused; + conn->rbuffer_first_unused = conn->rbuffer_first_empty; + conn->rbuffer_first_empty = current; + + conn->rbuffer_count += can_take; + conn->rneedbufferevent = false; + return can_take; +} + +size_t pn_raw_connection_take_read_buffers(pn_raw_connection_t *conn, pn_raw_buffer_t *buffers, size_t num) { + assert(conn); + size_t count = 0; + + buff_ptr current = conn->rbuffer_first_read; + if (!current) return 0; + + buff_ptr previous; + for (; current && count < num; count++) { + assert(conn->rbuffers[current-1].type == buff_read); + buffers[count].context = conn->rbuffers[current-1].context; + buffers[count].bytes = conn->rbuffers[current-1].bytes; + buffers[count].capacity = conn->rbuffers[current-1].capacity; + buffers[count].size = conn->rbuffers[current-1].size; + buffers[count].offset = conn->rbuffers[current-1].offset - conn->rbuffers[current-1].size; + conn->rbuffers[current-1].type = buff_rempty; + + previous = current; + current = conn->rbuffers[current-1].next; + } + if (!count) return 0; + + conn->rbuffers[previous-1].next = conn->rbuffer_first_empty; + conn->rbuffer_first_empty = conn->rbuffer_first_read; + + conn->rbuffer_first_read = current; + if (!current) { + conn->rbuffer_last_read = 0; + } + conn->rbuffer_count -= count; + return count; +} + +size_t pn_raw_connection_write_buffers(pn_raw_connection_t *conn, pn_raw_buffer_t const *buffers, size_t num) { + assert(conn); + size_t can_take = pn_min(num, pn_raw_connection_write_buffers_capacity(conn)); + if ( can_take==0 ) return 0; + + buff_ptr current = conn->wbuffer_first_empty; + assert(current); + + buff_ptr previous; + for (size_t i = 0; i < can_take; i++) { + // Get next free + assert(conn->wbuffers[current-1].type == buff_wempty); + conn->wbuffers[current-1].context = buffers[i].context; + conn->wbuffers[current-1].bytes = buffers[i].bytes; + conn->wbuffers[current-1].capacity = buffers[i].capacity; + conn->wbuffers[current-1].size = buffers[i].size; + conn->wbuffers[current-1].offset = buffers[i].offset; + conn->wbuffers[current-1].type = buff_unwritten; + + previous = current; + current = conn->wbuffers[current-1].next; + } + + if (!conn->wbuffer_first_towrite) { + conn->wbuffer_first_towrite = conn->wbuffer_first_empty; + } + if (conn->wbuffer_last_towrite) { + conn->wbuffers[conn->wbuffer_last_towrite-1].next = conn->wbuffer_first_empty; + } + + conn->wbuffer_last_towrite = previous; + conn->wbuffers[previous-1].next = 0; + conn->wbuffer_first_empty = current; + + conn->wbuffer_count += can_take; + conn->wneedbufferevent = false; + return can_take; +} + +size_t pn_raw_connection_take_written_buffers(pn_raw_connection_t *conn, pn_raw_buffer_t *buffers, size_t num) { + assert(conn); + size_t count = 0; + + buff_ptr current = conn->wbuffer_first_written; + if (!current) return 0; + + buff_ptr previous; + for (; current && count < num; count++) { + assert(conn->wbuffers[current-1].type == buff_written); + buffers[count].context = conn->wbuffers[current-1].context; + buffers[count].bytes = conn->wbuffers[current-1].bytes; + buffers[count].capacity = conn->wbuffers[current-1].capacity; + buffers[count].size = conn->wbuffers[current-1].size; + buffers[count].offset = conn->wbuffers[current-1].offset; + conn->wbuffers[current-1].type = buff_wempty; + + previous = current; + current = conn->wbuffers[current-1].next; + } + if (!count) return 0; + + conn->wbuffers[previous-1].next = conn->wbuffer_first_empty; + conn->wbuffer_first_empty = conn->wbuffer_first_written; + + conn->wbuffer_first_written = current; + if (!current) { + conn->wbuffer_last_written = 0; + } + conn->wbuffer_count -= count; + return count; +} + +static inline void pni_raw_put_event(pn_raw_connection_t *conn, pn_event_type_t type) { + pn_collector_put(conn->collector, PN_CLASSCLASS(pn_raw_connection), (void*)conn, type); +} + +static inline void pni_raw_release_buffers(pn_raw_connection_t *conn) { + for(;conn->rbuffer_first_unused;) { + buff_ptr p = conn->rbuffer_first_unused; + assert(conn->rbuffers[p-1].type == buff_unread); + conn->rbuffers[p-1].size = 0; + if (!conn->rbuffer_first_read) { + conn->rbuffer_first_read = p; + } + if (conn->rbuffer_last_read) { + conn->rbuffers[conn->rbuffer_last_read-1].next = p; + } + conn->rbuffer_last_read = p; + conn->rbuffer_first_unused = conn->rbuffers[p-1].next; + + conn->rbuffers[p-1].next = 0; + conn->rbuffers[p-1].type = buff_read; + } + conn->rbuffer_last_unused = 0; + for(;conn->wbuffer_first_towrite;) { + buff_ptr p = conn->wbuffer_first_towrite; + assert(conn->wbuffers[p-1].type == buff_unwritten); + if (!conn->wbuffer_first_written) { + conn->wbuffer_first_written = p; + } + if (conn->wbuffer_last_written) { + conn->wbuffers[conn->wbuffer_last_written-1].next = p; + } + conn->wbuffer_last_written = p; + conn->wbuffer_first_towrite = conn->wbuffers[p-1].next; + + conn->wbuffers[p-1].next = 0; + conn->wbuffers[p-1].type = buff_written; + } + conn->wbuffer_last_towrite = 0; + conn->rdrainpending = (bool)(conn->rbuffer_first_read); + conn->wdrainpending = (bool)(conn->wbuffer_first_written); +} + +void pni_raw_disconnect(pn_raw_connection_t *conn) { + pni_raw_release_buffers(conn); + conn->disconnectpending = true; +} + +void pni_raw_connected(pn_raw_connection_t *conn) { + pn_condition_clear(conn->condition); + pni_raw_put_event(conn, PN_RAW_CONNECTION_CONNECTED); +} + +void pni_raw_wake(pn_raw_connection_t *conn) { + conn->wakepending = true; +} + +void pni_raw_read(pn_raw_connection_t *conn, int sock, long (*recv)(int, void*, size_t), void(*set_error)(pn_raw_connection_t *, const char *, int)) { + assert(conn); + bool closed = false; + for(;conn->rbuffer_first_unused;) { + buff_ptr p = conn->rbuffer_first_unused; + assert(conn->rbuffers[p-1].type == buff_unread); + char *bytes = conn->rbuffers[p-1].bytes+conn->rbuffers[p-1].offset; + size_t s = conn->rbuffers[p-1].capacity-conn->rbuffers[p-1].offset; + int r = recv(sock, bytes, s); + if (r < 0) { + switch (errno) { + // Interrupted system call try again + case EINTR: continue; + + // Would block + case EWOULDBLOCK: goto finished_reading; + + // Detected an error + default: + set_error(conn, "recv error", errno); + pn_raw_connection_close(conn); + return; + } + } + conn->rbuffers[p-1].size += r; + conn->rbuffers[p-1].offset += r; + + if (!conn->rbuffer_first_read) { + conn->rbuffer_first_read = p; + } + if (conn->rbuffer_last_read) { + conn->rbuffers[conn->rbuffer_last_read-1].next = p; + } + conn->rbuffer_last_read = p; + conn->rbuffer_first_unused = conn->rbuffers[p-1].next; + + conn->rbuffers[p-1].next = 0; + conn->rbuffers[p-1].type = buff_read; + + // Checking for end of stream here ensures that there is a buffer at the end with nothing in it + if (r == 0) { + closed = true; + break; + } + } +finished_reading: + if (!conn->rbuffer_first_unused) { + conn->rbuffer_last_unused = 0; + } + // Read something - we are now either out of buffers; end of stream; or blocked for read + if (conn->rbuffer_first_read && !conn->rpending) { + conn->rpending = true; + } + // Socket closed for read + if (closed) { + if (!conn->rclosed) { + conn->rclosed = true; + conn->rclosedpending = true; + if (conn->wclosed) { + pni_raw_disconnect(conn); + } + } + } + return; +} + +void pni_raw_write(pn_raw_connection_t *conn, int sock, long (*send)(int, const void*, size_t), void(*set_error)(pn_raw_connection_t *, const char *, int)) { + assert(conn); + bool closed = false; + for(;conn->wbuffer_first_towrite;) { + buff_ptr p = conn->wbuffer_first_towrite; + assert(conn->wbuffers[p-1].type == buff_unwritten); + char *bytes = conn->wbuffers[p-1].bytes+conn->wbuffers[p-1].offset+conn->unwritten_offset; + size_t s = conn->wbuffers[p-1].size-conn->unwritten_offset; + int r = send(sock, bytes, s); + if (r < 0) { + // Interrupted system call try again + switch (errno) { + // Interrupted system call try again + case EINTR: continue; + + case EWOULDBLOCK: + goto finished_writing; + + default: + set_error(conn, "send error", errno); + pn_raw_connection_close(conn); + return; + } + } + // return of 0 was never observed in testing and the documentation + // implies that 0 could only be returned if 0 bytes were sent; however + // leaving this case here seems safe. + if (r == 0 && s > 0) { + closed = true; + break; + } + + // Only wrote a partial buffer - adjust buffer + if (r != (int)s) { + conn->unwritten_offset += r; + break; + } + + conn->unwritten_offset = 0; + + if (!conn->wbuffer_first_written) { + conn->wbuffer_first_written = p; + } + if (conn->wbuffer_last_written) { + conn->wbuffers[conn->wbuffer_last_written-1].next = p; + } + conn->wbuffer_last_written = p; + conn->wbuffer_first_towrite = conn->wbuffers[p-1].next; + + conn->wbuffers[p-1].next = 0; + conn->wbuffers[p-1].type = buff_written; + } +finished_writing: + if (!conn->wbuffer_first_towrite) { + conn->wbuffer_last_towrite = 0; + } + // Wrote something; end of stream; out of buffers; or blocked for write + if (conn->wbuffer_first_written && !conn->wpending) { + conn->wpending = true; + } + // Socket closed for write + if (closed) { + if (!conn->wclosed) { + conn->wclosed = true; + conn->wclosedpending = true; + if (conn->rclosed) { + pni_raw_disconnect(conn);; + } + } + } + return; +} + +bool pni_raw_can_read(pn_raw_connection_t *conn) { + return !conn->rclosed && conn->rbuffer_first_unused; +} + +bool pni_raw_can_write(pn_raw_connection_t *conn) { + return !conn->wclosed && conn->wbuffer_first_towrite; +} + +pn_event_t *pni_raw_event_next(pn_raw_connection_t *conn) { + assert(conn); + do { + pn_event_t *event = pn_collector_next(conn->collector); + if (event) { + pn_event_type_t type = pn_event_type(event); + switch (type) { + default: break; + } + } else if (conn->wakepending) { + pni_raw_put_event(conn, PN_RAW_CONNECTION_WAKE); + conn->wakepending = false; + continue; + } else if (conn->rpending) { + pni_raw_put_event(conn, PN_RAW_CONNECTION_READ); + conn->rpending = false; + continue; + } else if (conn->wpending) { + pni_raw_put_event(conn, PN_RAW_CONNECTION_WRITTEN); + conn->wpending = false; + continue; + } else if (conn->rclosedpending) { + pni_raw_put_event(conn, PN_RAW_CONNECTION_CLOSED_READ); + conn->rclosedpending = false; + continue; + } else if (conn->wclosedpending) { + pni_raw_put_event(conn, PN_RAW_CONNECTION_CLOSED_WRITE); + conn->wclosedpending = false; + continue; + } else if (conn->rdrainpending) { + pni_raw_put_event(conn, PN_RAW_CONNECTION_READ); + conn->rdrainpending = false; + continue; + } else if (conn->wdrainpending) { + pni_raw_put_event(conn, PN_RAW_CONNECTION_WRITTEN); + conn->wdrainpending = false; + continue; + } else if (conn->disconnectpending) { + pni_raw_put_event(conn, PN_RAW_CONNECTION_DISCONNECTED); + conn->disconnectpending = false; + continue; + } else if (!conn->wclosed && !conn->wbuffer_first_towrite && !conn->wneedbufferevent) { + // Ran out of write buffers + pni_raw_put_event(conn, PN_RAW_CONNECTION_NEED_WRITE_BUFFERS); + conn->wneedbufferevent = true; + continue; + } else if (!conn->rclosed && !conn->rbuffer_first_unused && !conn->rneedbufferevent) { + // Ran out of read buffers + pni_raw_put_event(conn, PN_RAW_CONNECTION_NEED_READ_BUFFERS); + conn->rneedbufferevent = true; + continue; + } + return pni_log_event(conn, event); + } while (true); +} + +void pn_raw_connection_close(pn_raw_connection_t *conn) { + // TODO: Do we need different flags here? + // TODO: What is the precise semantics for close? + bool rclosed = conn->rclosed; + if (!rclosed) { + conn->rclosed = true; + conn->rclosedpending = true; + } + bool wclosed = conn->wclosed; + if (!wclosed) { + conn->wclosed = true; + conn->wclosedpending = true; + } + if (!rclosed || !wclosed) { + pni_raw_disconnect(conn); + } +} + +bool pn_raw_connection_is_read_closed(pn_raw_connection_t *conn) { + assert(conn); + return conn->rclosed; +} + +bool pn_raw_connection_is_write_closed(pn_raw_connection_t *conn) { + assert(conn); + return conn->wclosed; +} + +pn_condition_t *pn_raw_connection_condition(pn_raw_connection_t *conn) { + assert(conn); + return conn->condition; +} + +void *pn_raw_connection_get_context(pn_raw_connection_t *conn) { + assert(conn); + return pn_record_get(conn->attachments, PN_LEGCTX); +} + +void pn_raw_connection_set_context(pn_raw_connection_t *conn, void *context) { + assert(conn); + pn_record_set(conn->attachments, PN_LEGCTX, context); +} + +pn_record_t *pn_raw_connection_attachments(pn_raw_connection_t *conn) { + assert(conn); + return conn->attachments; +} + +pn_raw_connection_t *pn_event_raw_connection(pn_event_t *event) { + return (pn_event_class(event) == PN_CLASSCLASS(pn_raw_connection)) ? (pn_raw_connection_t*)pn_event_context(event) : NULL; +} diff --git a/c/src/proactor/win_iocp.c b/c/src/proactor/win_iocp.cpp similarity index 99% rename from c/src/proactor/win_iocp.c rename to c/src/proactor/win_iocp.cpp index ae5a369b47..88737e4271 100644 --- a/c/src/proactor/win_iocp.c +++ b/c/src/proactor/win_iocp.cpp @@ -23,26 +23,28 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include #include #include -#include + +#include #include +#include +#include #include #include -#include - -#include -#include +#include #include "netaddr-internal.h" /* Include after socket/inet headers */ #include "core/logger_private.h" @@ -3418,3 +3420,11 @@ pn_millis_t pn_proactor_now(void) { int64_t pn_proactor_now_64(void) { return GetTickCount64(); } + +// Empty stubs for raw connection code +pn_raw_connection_t *pn_raw_connection(void) { return NULL; } +void pn_proactor_raw_connect(pn_proactor_t *p, pn_raw_connection_t *rc, const char *addr) {} +void pn_listener_raw_accept(pn_listener_t *l, pn_raw_connection_t *rc) {} +void pn_raw_connection_wake(pn_raw_connection_t *conn) {} +const struct pn_netaddr_t *pn_raw_connection_local_addr(pn_raw_connection_t *connection) { return NULL; } +const struct pn_netaddr_t *pn_raw_connection_remote_addr(pn_raw_connection_t *connection) { return NULL; } diff --git a/c/src/reactor/io/posix/io.c b/c/src/reactor/io/posix/io.c index 5a0de3b10f..2ddb5c0c17 100644 --- a/c/src/reactor/io/posix/io.c +++ b/c/src/reactor/io/posix/io.c @@ -218,7 +218,7 @@ pn_socket_t pn_accept(pn_io_t *io, pn_socket_t socket, char *name, size_t size) return PN_INVALID_SOCKET; } else { pn_configure_sock(io, sock); - pni_snprintf(name, size, "%s:%s", io->host, io->serv); + snprintf(name, size, "%s:%s", io->host, io->serv); return sock; } } diff --git a/c/src/reactor/io/windows/io.c b/c/src/reactor/io/windows/io.c index 07692d1540..d7d6f291ed 100644 --- a/c/src/reactor/io/windows/io.c +++ b/c/src/reactor/io/windows/io.c @@ -306,7 +306,7 @@ pn_socket_t pn_accept(pn_io_t *io, pn_socket_t listen_sock, char *name, size_t s return INVALID_SOCKET; } else { pn_configure_sock(io, accept_sock); - pni_snprintf(name, size, "%s:%s", io->host, io->serv); + snprintf(name, size, "%s:%s", io->host, io->serv); if (listend) { pni_iocpdesc_start(pni_iocpdesc_map_get(io->iocp, accept_sock)); } diff --git a/c/src/reactor/io/windows/iocp.c b/c/src/reactor/io/windows/iocp.c index 8a1a64a2bb..30f1bceb3a 100644 --- a/c/src/reactor/io/windows/iocp.c +++ b/c/src/reactor/io/windows/iocp.c @@ -66,7 +66,7 @@ // all together when accepting the connection. Reserve enough for // IPv6 addresses, even if the socket is IPv4. The 16 bytes padding // per address is required by AcceptEx. -#define IOCP_SOCKADDRMAXLEN (sizeof(sockaddr_in6) + 16) +#define IOCP_SOCKADDRMAXLEN (sizeof(struct sockaddr_in6) + 16) #define IOCP_SOCKADDRBUFLEN (2 * IOCP_SOCKADDRMAXLEN) static void iocp_log(const char *fmt, ...) @@ -145,9 +145,9 @@ static LPFN_GETACCEPTEXSOCKADDRS lookup_get_accept_ex_sockaddrs(SOCKET s) // match accept socket to listener socket static iocpdesc_t *create_same_type_socket(iocpdesc_t *iocpd) { - sockaddr_storage sa; + struct sockaddr_storage sa; socklen_t salen = sizeof(sa); - if (getsockname(iocpd->socket, (sockaddr*)&sa, &salen) == -1) + if (getsockname(iocpd->socket, (struct sockaddr*)&sa, &salen) == -1) return NULL; SOCKET s = socket(sa.ss_family, SOCK_STREAM, 0); // Currently only work with SOCK_STREAM if (s == INVALID_SOCKET) @@ -192,6 +192,7 @@ struct pni_acceptor_t { LPFN_GETACCEPTEXSOCKADDRS fn_get_accept_ex_sockaddrs; }; +#define CID_pni_acceptor CID_pn_void #define pni_acceptor_compare NULL #define pni_acceptor_inspect NULL #define pni_acceptor_hashcode NULL @@ -213,7 +214,6 @@ static void pni_acceptor_finalize(void *object) static pni_acceptor_t *pni_acceptor(iocpdesc_t *iocpd) { - static const pn_cid_t CID_pni_acceptor = CID_pn_void; static const pn_class_t clazz = PN_CLASS(pni_acceptor); pni_acceptor_t *acceptor = (pni_acceptor_t *) pn_class_new(&clazz, sizeof(pni_acceptor_t)); acceptor->listen_sock = iocpd; @@ -289,7 +289,7 @@ static void complete_accept(accept_result_t *result, HRESULT status) } } -pn_socket_t pni_iocp_end_accept(iocpdesc_t *ld, sockaddr *addr, socklen_t *addrlen, bool *would_block, pn_error_t *error) +pn_socket_t pni_iocp_end_accept(iocpdesc_t *ld, struct sockaddr *addr, socklen_t *addrlen, bool *would_block, pn_error_t *error) { if (!is_listener(ld)) { set_iocp_error_status(error, PN_ERR, WSAEOPNOTSUPP); @@ -325,8 +325,8 @@ pn_socket_t pni_iocp_end_accept(iocpdesc_t *ld, sockaddr *addr, socklen_t *addrl setsockopt(accept_sock, SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, (char*)&ld->socket, sizeof (SOCKET)); if (addr && addrlen && *addrlen > 0) { - sockaddr_storage *local_addr = NULL; - sockaddr_storage *remote_addr = NULL; + struct sockaddr_storage *local_addr = NULL; + struct sockaddr_storage *remote_addr = NULL; int local_addrlen, remote_addrlen; LPFN_GETACCEPTEXSOCKADDRS fn = ld->acceptor->fn_get_accept_ex_sockaddrs; fn(result->address_buffer, 0, IOCP_SOCKADDRMAXLEN, IOCP_SOCKADDRMAXLEN, @@ -358,6 +358,7 @@ typedef struct { struct addrinfo *addrinfo; } connect_result_t; +#define CID_connect_result CID_pn_void #define connect_result_initialize NULL #define connect_result_compare NULL #define connect_result_inspect NULL @@ -372,7 +373,6 @@ static void connect_result_finalize(void *object) } static connect_result_t *connect_result(iocpdesc_t *iocpd, struct addrinfo *addr) { - static const pn_cid_t CID_connect_result = CID_pn_void; static const pn_class_t clazz = PN_CLASS(connect_result); connect_result_t *result = (connect_result_t *) pn_class_new(&clazz, sizeof(connect_result_t)); if (result) { @@ -389,7 +389,7 @@ pn_socket_t pni_iocp_begin_connect(iocp_t *iocp, pn_socket_t sock, struct addrin // addr lives for the duration of the async connect. Caller has passed ownership here. // See connect_result_finalize(). // Use of Windows-specific ConnectEx() requires our socket to be "loosely" pre-bound: - sockaddr_storage sa; + struct sockaddr_storage sa; memset(&sa, 0, sizeof(sa)); sa.ss_family = addr->ai_family; if (bind(sock, (SOCKADDR *) &sa, addr->ai_addrlen)) { @@ -747,13 +747,13 @@ static uintptr_t pni_iocpdesc_hashcode(void *object) return iocpd->socket; } +#define CID_pni_iocpdesc CID_pn_void #define pni_iocpdesc_compare NULL #define pni_iocpdesc_inspect NULL // Reference counted in the iocpdesc map, zombie_list, selector. static iocpdesc_t *pni_iocpdesc(pn_socket_t s) { - static const pn_cid_t CID_pni_iocpdesc = CID_pn_void; static pn_class_t clazz = PN_CLASS(pni_iocpdesc); iocpdesc_t *iocpd = (iocpdesc_t *) pn_class_new(&clazz, sizeof(iocpdesc_t)); assert(iocpd); @@ -1130,6 +1130,7 @@ void pni_iocp_begin_close(iocpdesc_t *iocpd) // === iocp_t +#define CID_pni_iocp CID_pn_void #define pni_iocp_hashcode NULL #define pni_iocp_compare NULL #define pni_iocp_inspect NULL @@ -1172,7 +1173,6 @@ void pni_iocp_finalize(void *obj) iocp_t *pni_iocp() { - static const pn_cid_t CID_pni_iocp = CID_pn_void; static const pn_class_t clazz = PN_CLASS(pni_iocp); iocp_t *iocp = (iocp_t *) pn_class_new(&clazz, sizeof(iocp_t)); return iocp; diff --git a/c/src/reactor/io/windows/iocp.h b/c/src/reactor/io/windows/iocp.h index 6cf0bc090b..d3018e4b67 100644 --- a/c/src/reactor/io/windows/iocp.h +++ b/c/src/reactor/io/windows/iocp.h @@ -30,6 +30,7 @@ typedef struct pni_acceptor_t pni_acceptor_t; typedef struct write_result_t write_result_t; typedef struct read_result_t read_result_t; typedef struct write_pipeline_t write_pipeline_t; +typedef struct iocp_t iocp_t; typedef struct iocpdesc_t iocpdesc_t; @@ -81,7 +82,7 @@ struct iocpdesc_t { iocpdesc_t *triggered_list_prev; iocpdesc_t *deadlines_next; iocpdesc_t *deadlines_prev; - pn_timestamp_t reap_time;; + pn_timestamp_t reap_time; }; typedef enum { IOCP_ACCEPT, IOCP_CONNECT, IOCP_READ, IOCP_WRITE } iocp_type_t; @@ -109,7 +110,7 @@ void pni_iocpdesc_start(iocpdesc_t *iocpd); void pni_iocp_drain_completions(iocp_t *); int pni_iocp_wait_one(iocp_t *, int timeout, pn_error_t *); void pni_iocp_start_accepting(iocpdesc_t *iocpd); -pn_socket_t pni_iocp_end_accept(iocpdesc_t *ld, sockaddr *addr, socklen_t *addrlen, bool *would_block, pn_error_t *error); +pn_socket_t pni_iocp_end_accept(iocpdesc_t *ld, struct sockaddr *addr, socklen_t *addrlen, bool *would_block, pn_error_t *error); pn_socket_t pni_iocp_begin_connect(iocp_t *, pn_socket_t sock, struct addrinfo *addr, pn_error_t *error); ssize_t pni_iocp_begin_write(iocpdesc_t *, const void *, size_t, bool *, pn_error_t *); ssize_t pni_iocp_recv(iocpdesc_t *iocpd, void *buf, size_t size, bool *would_block, pn_error_t *error); diff --git a/c/src/reactor/io/windows/write_pipeline.c b/c/src/reactor/io/windows/write_pipeline.c index 238303c04c..ffe5c20bfb 100644 --- a/c/src/reactor/io/windows/write_pipeline.c +++ b/c/src/reactor/io/windows/write_pipeline.c @@ -146,6 +146,7 @@ struct write_pipeline_t { bool is_writer; }; +#define CID_write_pipeline CID_pn_void #define write_pipeline_compare NULL #define write_pipeline_inspect NULL #define write_pipeline_hashcode NULL @@ -169,7 +170,6 @@ static void write_pipeline_finalize(void *object) write_pipeline_t *pni_write_pipeline(iocpdesc_t *iocpd) { - static const pn_cid_t CID_write_pipeline = CID_pn_void; static const pn_class_t clazz = PN_CLASS(write_pipeline); write_pipeline_t *pipeline = (write_pipeline_t *) pn_class_new(&clazz, sizeof(write_pipeline_t)); pipeline->iocpd = iocpd; @@ -205,13 +205,13 @@ static void remove_as_writer(write_pipeline_t *pl) static void set_depth(write_pipeline_t *pl) { pl->depth = 1; - sockaddr_storage sa; + struct sockaddr_storage sa; socklen_t salen = sizeof(sa); char buf[INET6_ADDRSTRLEN]; DWORD buflen = sizeof(buf); - if (getsockname(pl->iocpd->socket,(sockaddr*) &sa, &salen) == 0 && - getnameinfo((sockaddr*) &sa, salen, buf, buflen, NULL, 0, NI_NUMERICHOST) == 0) { + if (getsockname(pl->iocpd->socket,(struct sockaddr*) &sa, &salen) == 0 && + getnameinfo((struct sockaddr*) &sa, salen, buf, buflen, NULL, 0, NI_NUMERICHOST) == 0) { if ((sa.ss_family == AF_INET6 && strcmp(buf, "::1")) || (sa.ss_family == AF_INET && strncmp(buf, "127.", 4))) { // not loopback diff --git a/c/src/ssl/openssl.c b/c/src/ssl/openssl.c index 0ee5411f97..3a982002f6 100644 --- a/c/src/ssl/openssl.c +++ b/c/src/ssl/openssl.c @@ -52,6 +52,7 @@ #include #include #include +#include /** @file * SSL/TLS support API. @@ -903,7 +904,7 @@ bool pn_ssl_get_cipher_name(pn_ssl_t *ssl0, char *buffer, size_t size ) if (ssl->ssl && (c = SSL_get_current_cipher( ssl->ssl ))) { const char *v = SSL_CIPHER_get_name(c); if (buffer && v) { - pni_snprintf( buffer, size, "%s", v ); + snprintf( buffer, size, "%s", v ); return true; } } @@ -919,7 +920,7 @@ bool pn_ssl_get_protocol_name(pn_ssl_t *ssl0, char *buffer, size_t size ) if (ssl->ssl && (c = SSL_get_current_cipher( ssl->ssl ))) { const char *v = SSL_CIPHER_get_version(c); if (buffer && v) { - pni_snprintf( buffer, size, "%s", v ); + snprintf( buffer, size, "%s", v ); return true; } } @@ -1486,7 +1487,7 @@ int pn_ssl_get_cert_fingerprint(pn_ssl_t *ssl0, char *fingerprint, size_t finger char *cursor = fingerprint; for (size_t i=0; i #include +#include // security.h needs to see this to distinguish from kernel use. #include @@ -52,6 +53,7 @@ #include #undef SECURITY_WIN32 +extern "C" { /** @file * SSL/TLS support API. @@ -757,7 +759,7 @@ bool pn_ssl_get_cipher_name(pn_ssl_t *ssl0, char *buffer, size_t size ) SecPkgContext_ConnectionInfo info; if (QueryContextAttributes(&ssl->ctxt_handle, SECPKG_ATTR_CONNECTION_INFO, &info) == SEC_E_OK) { // TODO: come up with string for all permutations? - pni_snprintf( buffer, size, "%x_%x:%x_%x:%x_%x", + snprintf( buffer, size, "%x_%x:%x_%x:%x_%x", info.aiExch, info.dwExchStrength, info.aiCipher, info.dwCipherStrength, info.aiHash, info.dwHashStrength); @@ -775,12 +777,12 @@ bool pn_ssl_get_protocol_name(pn_ssl_t *ssl0, char *buffer, size_t size ) SecPkgContext_ConnectionInfo info; if (QueryContextAttributes(&ssl->ctxt_handle, SECPKG_ATTR_CONNECTION_INFO, &info) == SEC_E_OK) { if (info.dwProtocol & (SP_PROT_TLS1_CLIENT | SP_PROT_TLS1_SERVER)) - pni_snprintf(buffer, size, "%s", "TLSv1"); + snprintf(buffer, size, "%s", "TLSv1"); // TLSV1.1 and TLSV1.2 are supported as of XP-SP3, but not defined until VS2010 else if ((info.dwProtocol & 0x300)) - pni_snprintf(buffer, size, "%s", "TLSv1.1"); + snprintf(buffer, size, "%s", "TLSv1.1"); else if ((info.dwProtocol & 0xC00)) - pni_snprintf(buffer, size, "%s", "TLSv1.2"); + snprintf(buffer, size, "%s", "TLSv1.2"); else { ssl_log_error("unexpected protocol %x", info.dwProtocol); return false; @@ -2346,3 +2348,5 @@ static HRESULT verify_peer(pni_ssl_t *ssl, HCERTSTORE root_store, const char *se free(nameUCS2); return error; } + +} diff --git a/c/src/ssl/ssl-internal.h b/c/src/ssl/ssl-internal.h index d3205eae48..3121e6fa3a 100644 --- a/c/src/ssl/ssl-internal.h +++ b/c/src/ssl/ssl-internal.h @@ -30,7 +30,15 @@ * */ +#ifdef __cplusplus +extern "C" { +#endif + // release the SSL context void pn_ssl_free(pn_transport_t *transport); +#ifdef __cplusplus +} +#endif + #endif /* ssl-internal.h */ diff --git a/c/tests/CMakeLists.txt b/c/tests/CMakeLists.txt index 86a4a41929..34173040eb 100644 --- a/c/tests/CMakeLists.txt +++ b/c/tests/CMakeLists.txt @@ -44,7 +44,7 @@ if (CMAKE_CXX_COMPILER) macro(add_c_test exe) add_executable(${exe} $ pn_test.cpp ${ARGN}) set_target_properties(${exe} PROPERTIES - COMPILE_FLAGS "${CMAKE_CXX_FLAGS} ${CXX_WARNING_FLAGS}") + COMPILE_FLAGS "${CXX_STANDARD} ${CMAKE_CXX_FLAGS} ${CXX_WARNING_FLAGS}") pn_add_test( EXECUTABLE NAME ${exe} @@ -75,32 +75,33 @@ if (CMAKE_CXX_COMPILER) add_c_test(c-proactor-test pn_test_proactor.cpp proactor_test.cpp) target_link_libraries(c-proactor-test qpid-proton-core qpid-proton-proactor ${PLATFORM_LIBS}) + list(TRANSFORM qpid-proton-proactor PREPEND "../" OUTPUT_VARIABLE qpid-proton-proactor-src) + add_c_test(c-raw-connection-test raw_connection_test.cpp ${qpid-proton-proactor-src}) + target_link_libraries(c-raw-connection-test qpid-proton-core ${PLATFORM_LIBS} ${PROACTOR_LIBS}) + add_c_test(c-ssl-proactor-test pn_test_proactor.cpp ssl_proactor_test.cpp) target_link_libraries(c-ssl-proactor-test qpid-proton-core qpid-proton-proactor ${PLATFORM_LIBS}) # Thread race test. - if (MSVC) - # test does not compile on Windows - set(DEFAULT_THREADERCISER OFF) - else (MSVC) + if (CMAKE_USE_PTHREADS_INIT OR CMAKE_HP_PTHREADS_INIT) + # test requires pthreads set(DEFAULT_THREADERCISER ON) - endif (MSVC) + else (CMAKE_USE_PTHREADS_INIT OR CMAKE_HP_PTHREADS_INIT) + set(DEFAULT_THREADERCISER OFF) + endif (CMAKE_USE_PTHREADS_INIT OR CMAKE_HP_PTHREADS_INIT) option(THREADERCISER "Run the threaderciser concurrency tests" ${DEFAULT_THREADERCISER}) if (THREADERCISER) add_executable(c-threaderciser threaderciser.c) set_target_properties(c-threaderciser - PROPERTIES COMPILE_FLAGS "${COMPILE_LANGUAGE_FLAGS} ${CMAKE_C_FLAGS} ${C_WARNING_FLAGS}") + PROPERTIES COMPILE_FLAGS "${C_STANDARD_FLAGS} ${COMPILE_WARNING_FLAGS}") + target_link_libraries (c-threaderciser qpid-proton-proactor Threads::Threads) pn_add_test( EXECUTABLE NAME c-threaderciser PREPEND_ENVIRONMENT ${test_env} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND $) - target_link_libraries (c-threaderciser qpid-proton-proactor) - find_library(Pthread_LIBRARY pthread) - if (Pthread_LIBRARY) - target_link_libraries (c-threaderciser ${Pthread_LIBRARY}) - endif() + set_tests_properties (c-threaderciser PROPERTIES TIMEOUT 120) endif() if(WIN32) diff --git a/c/tests/engine_test.cpp b/c/tests/engine_test.cpp index fa6e23f8fd..f8b174e7cd 100644 --- a/c/tests/engine_test.cpp +++ b/c/tests/engine_test.cpp @@ -23,6 +23,8 @@ #include +using namespace pn_test; + // push data from one transport to another static int xfer(pn_transport_t *src, pn_transport_t *dest) { ssize_t out = pn_transport_pending(src); @@ -316,3 +318,51 @@ TEST_CASE("engine_link_name_prefix)") { pn_transport_free(t2); pn_connection_free(c2); } + + +TEST_CASE("link_properties)") { + pn_connection_t *c1 = pn_connection(); + pn_transport_t *t1 = pn_transport(); + pn_transport_bind(t1, c1); + + pn_connection_t *c2 = pn_connection(); + pn_transport_t *t2 = pn_transport(); + pn_transport_set_server(t2); + pn_transport_bind(t2, c2); + + pn_connection_open(c1); + pn_connection_open(c2); + + pn_session_t *s1 = pn_session(c1); + pn_session_open(s1); + + pn_link_t *rx = pn_receiver(s1, "props"); + pn_data_t *props = pn_link_properties(rx); + REQUIRE(props != NULL); + + pn_data_clear(props); + pn_data_fill(props, "{S[iii]SI}", "foo", 1, 987, 3, "bar", 965); + pn_link_open(rx); + + while (pump(t1, t2)) { + process_endpoints(c1); + process_endpoints(c2); + } + + // session and link should be up, c2 should have a sender link: + REQUIRE(pn_link_state(rx) == (PN_LOCAL_ACTIVE | PN_REMOTE_ACTIVE)); + REQUIRE(pn_link_remote_properties(rx) == NULL); + + pn_link_t *tx = pn_link_head(c2, (PN_LOCAL_ACTIVE | PN_REMOTE_ACTIVE)); + + REQUIRE(pn_link_remote_properties(tx) != NULL); + CHECK("{\"foo\"=[1, 987, 3], \"bar\"=965}" == pn_test::inspect(pn_link_remote_properties(tx))); + + pn_transport_unbind(t1); + pn_transport_free(t1); + pn_connection_free(c1); + + pn_transport_unbind(t2); + pn_transport_free(t2); + pn_connection_free(c2); +} diff --git a/c/tests/fuzz/CMakeLists.txt b/c/tests/fuzz/CMakeLists.txt index 2fd19b95d2..c13edac090 100644 --- a/c/tests/fuzz/CMakeLists.txt +++ b/c/tests/fuzz/CMakeLists.txt @@ -17,7 +17,7 @@ # under the License. # -add_definitions(${COMPILE_WARNING_FLAGS} ${COMPILE_PLATFORM_FLAGS}) +add_definitions(${C_STANDARD_FLAGS} ${COMPILE_WARNING_FLAGS}) option(FUZZ_REGRESSION_TESTS "Run fuzz tests with regression test driver" ON) option(FUZZ_LONG_TESTS "Run fuzz tests that take a long time" OFF) @@ -77,17 +77,13 @@ pn_add_fuzz_test (fuzz-message-decode fuzz-message-decode.c) target_link_libraries (fuzz-message-decode ${FUZZING_QPID_PROTON_CORE_LIBRARY}) # pn_url_parse is not in proton core and is only used by messenger so compile specially -set(platform_MSVC ${PN_C_SOURCE_DIR}/compiler/msvc/snprintf.c) -set(platform ${platform_${CMAKE_C_COMPILER_ID}}) - pn_add_fuzz_test (fuzz-url fuzz-url.c ${PN_C_SOURCE_DIR}/extra/url.c ${PN_C_SOURCE_DIR}/core/object/object.c ${PN_C_SOURCE_DIR}/core/object/string.c ${PN_C_SOURCE_DIR}/core/util.c - ${PN_C_SOURCE_DIR}/core/memory.c - ${platform}) + ${PN_C_SOURCE_DIR}/core/memory.c) target_compile_definitions(fuzz-url PRIVATE PROTON_DECLARE_STATIC) # This regression test can take a very long time so don't run by default @@ -100,12 +96,3 @@ endif() # pni_sniff_header is internal so it has to be compiled specially pn_add_fuzz_test (fuzz-sniff-header fuzz-sniff-header.c ${PN_C_SOURCE_DIR}/core/autodetect.c) - -if (BUILD_WITH_CXX) - set_source_files_properties ( - StandaloneFuzzTargetMain.c - StandaloneFuzzTargetInit.c - ${fuzz_test_src} - PROPERTIES LANGUAGE CXX - ) -endif (BUILD_WITH_CXX) diff --git a/c/tests/proactor_test.cpp b/c/tests/proactor_test.cpp index aa0ab57263..33906d812f 100644 --- a/c/tests/proactor_test.cpp +++ b/c/tests/proactor_test.cpp @@ -567,6 +567,9 @@ TEST_CASE("proactor_ssl") { TEST_CASE("proactor_addr") { /* Test the address formatter */ + CHECK(1 == pn_proactor_addr(NULL, 0, "", "")); + CHECK(7 == pn_proactor_addr(NULL, 0, "foo", "bar")); + char addr[PN_MAX_ADDR]; pn_proactor_addr(addr, sizeof(addr), "foo", "bar"); CHECK_THAT("foo:bar", Equals(addr)); diff --git a/c/tests/raw_connection_test.cpp b/c/tests/raw_connection_test.cpp new file mode 100644 index 0000000000..d32103b5c2 --- /dev/null +++ b/c/tests/raw_connection_test.cpp @@ -0,0 +1,723 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include "proactor/raw_connection-internal.h" + +#include "pn_test.hpp" + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#endif + +#include + +#include +#include +#include +#include + +using namespace pn_test; +using Catch::Matchers::Contains; +using Catch::Matchers::Equals; + +namespace { + pn_raw_connection_t* mk_raw_connection() { + pn_raw_connection_t* rc = (pn_raw_connection_t*) calloc(1, sizeof(struct pn_raw_connection_t)); + pni_raw_initialize(rc); + return rc; + } + void free_raw_connection(pn_raw_connection_t* c) { + pni_raw_finalize(c); + free(c); + } + int read_err; + void set_read_error(pn_raw_connection_t*, const char*, int err) { + read_err = err; + } + int write_err; + void set_write_error(pn_raw_connection_t*, const char*, int err) { + write_err = err; + } + + size_t max_send_size = 0; + size_t max_recv_size = 0; + +#ifdef MSG_DONTWAIT + long rcv(int fd, void* b, size_t s) { + read_err = 0; + if (max_recv_size && max_recv_size < s) s = max_recv_size; + return ::recv(fd, b, s, MSG_DONTWAIT); + } + + void freepair(int fds[2]) { + ::close(fds[0]); + ::close(fds[1]); + } + + void rcv_stop(int fd) { + ::shutdown(fd, SHUT_RD); + } + + void snd_stop(int fd) { + ::shutdown(fd, SHUT_WR); + } + +#ifdef MSG_NOSIGNAL + long snd(int fd, const void* b, size_t s) { + write_err = 0; + if (max_send_size && max_send_size < s) s = max_send_size; + return ::send(fd, b, s, MSG_NOSIGNAL | MSG_DONTWAIT); + } + + int makepair(int fds[2]) { + return ::socketpair(AF_LOCAL, SOCK_STREAM, PF_UNSPEC, fds); + } +#elif defined(SO_NOSIGPIPE) + long snd(int fd, const void* b, size_t s) { + write_err = 0; + if (max_send_size && max_send_size < s) s = max_send_size; + return ::send(fd, b, s, MSG_DONTWAIT); + } + + int makepair(int fds[2]) { + int rc = ::socketpair(AF_LOCAL, SOCK_STREAM, PF_UNSPEC, fds); + if (rc == 0) { + int optval = 1; + ::setsockopt(fds[0], SOL_SOCKET, SO_NOSIGPIPE, &optval, sizeof(optval)); + ::setsockopt(fds[1], SOL_SOCKET, SO_NOSIGPIPE, &optval, sizeof(optval)); + } + return rc; + } +#endif +#else + // Simple mock up of the read/write functions of a socketpair for testing + // systems without socketpairs (Windows really) + // TODO: perhaps this should used everywhere + static const uint16_t buffsize = 4096; + struct fbuf { + uint8_t buff[buffsize*2] = {}; + int linked_fd = -1; + size_t head = 0; + size_t size = 0; + bool rclosed = true; + bool wclosed = true; + + bool closed() { + return rclosed && wclosed && linked_fd == -1; + } + + void open_linked(int linked_fd0) { + CHECK(closed()); + CHECK(head == 0); + CHECK(size == 0); + linked_fd = linked_fd0; + rclosed = false; + wclosed = false; + } + + void shutdown_rd() { + rclosed = true; + } + + void shutdown_wrt() { + wclosed = true; + } + + void close() { + CHECK_FALSE(closed()); + linked_fd = -1; + rclosed = true; + wclosed = true; + head = 0; + size = 0; + } + }; + + static std::vector buffers; + + long rcv(int fd, void* b, size_t s){ + CHECK(fd < buffers.size()); + read_err = 0; + if (max_recv_size && max_recv_size < s) s = max_recv_size; + + fbuf& buffer = buffers[fd]; + if (buffer.size == 0) { + if (buffer.rclosed) { + return 0; + } else { + errno = EWOULDBLOCK; + return -1; + } + } + + if (buffer.size < s) s = buffer.size; + + ::memcpy(b, &buffer.buff[buffer.head], s); + buffer.head += s % buffsize; + buffer.size -= s; + return s; + } + + long snd(int fd, const void* b, size_t s){ + CHECK(fd < buffers.size()); + write_err = 0; + if (max_send_size && max_send_size < s) s = max_send_size; + + // Write to linked buffer + fbuf& buffer = buffers[fd]; + fbuf& linked_buffer = buffers[buffer.linked_fd]; + if (linked_buffer.size == buffsize) { + errno = EWOULDBLOCK; + return -1; + } + if (linked_buffer.rclosed) { + errno = EPIPE; + return -1; + } + if (s + linked_buffer.size > buffsize) s = buffsize - linked_buffer.size; + ::memcpy(&linked_buffer.buff[linked_buffer.head+linked_buffer.size], b, s); + // If we wrote into the second half them write again into the hole at the front + if (linked_buffer.head+linked_buffer.size > buffsize) { + size_t r = linked_buffer.head+linked_buffer.size - buffsize; + ::memmove(&linked_buffer.buff[0], &linked_buffer.buff[buffsize], r); + } + linked_buffer.size += s; + return s; + } + + void rcv_stop(int fd) { + CHECK(fd < buffers.size()); + buffers[fd].shutdown_rd(); + } + + void snd_stop(int fd) { + CHECK(fd < buffers.size()); + buffers[fd].shutdown_wrt(); + buffers[buffers[fd].linked_fd].shutdown_rd(); + } + + int makepair(int fds[2]) { + size_t maximum_fd = buffers.size(); + buffers.resize( buffers.size()+2); + buffers[maximum_fd].open_linked(maximum_fd+1); + buffers[maximum_fd+1].open_linked(maximum_fd); + fds[0] = maximum_fd; + fds[1] = maximum_fd+1; + return 0; + } + + void freepair(int fds[2]) { + CHECK(fds[0] < buffers.size()); + CHECK(fds[1] < buffers.size()); + buffers[fds[0]].close(); + buffers[fds[1]].close(); + } +#endif + + // Block of memory for buffers + const size_t BUFFMEMSIZE = 8*1024; + const size_t RBUFFCOUNT = 32; + const size_t WBUFFCOUNT = 32; + + char rbuffer_memory[BUFFMEMSIZE]; + char *rbuffer_brk = rbuffer_memory; + + pn_raw_buffer_t rbuffs[RBUFFCOUNT]; + pn_raw_buffer_t wbuffs[WBUFFCOUNT]; + + class BufferAllocator { + char* buffer; + uint32_t size; + uint32_t brk; + + public: + BufferAllocator(char* b, uint32_t s) : buffer(b), size(s), brk(0) {}; + + char* next(uint32_t s) { + if ( brk+s > size) return NULL; + + char *r = buffer+brk; + brk += s; + return r; + } + + template + B next_buffer(uint32_t s); + + template + void split_buffers(B (&buffers)[N]) { + uint32_t buffsize = (size-brk)/N; + uint32_t remainder = (size-brk)%N; + for (int i = 0; i(i==0 ? buffsize+remainder : buffsize); + } + } + }; + + template <> + pn_raw_buffer_t BufferAllocator::next_buffer(uint32_t s) { + pn_raw_buffer_t b = {}; + b.bytes = next(s); + if (b.bytes) {b.capacity = s; b.size = s;} + return b; + } +} + +char message[] = +"Jabberwocky\n" +"By Lewis Carroll\n" +"\n" +"'Twas brillig, and the slithy toves\n" +"Did gyre and gimble in the wabe:\n" +"All mimsy were the borogroves,\n" +"And the mome raths outgrabe.\n" +"\n" +"Beware the Jabberwock, my son!\n" +"The jaws that bite, the claws that catch!\n" +"Beware the Jubjub bird, and shun\n" +"The frumious Bandersnatch!\n" +"\n" +"He took his vorpal sword in hand;\n" +"Long time the manxome foe he sought-\n" +"So rested he by the Tumtum tree\n" +"And stood awhile in thought.\n" +"\n" +"And, as in uffish thought he stood,\n" +"The Jabberwock with eyes of flame,\n" +"Came whiffling through the tulgey wood,\n" +"And burbled as it came!\n" +"\n" +"One, two! One, two! And through and through,\n" +"The vorpal blade went snicker-snack!\n" +"He left it dead, and with its head\n" +"He went galumphing back.\n" +"\n" +"\"And hast thou slain the JabberWock?\n" +"Come to my arms, my beamish boy!\n" +"O frabjous day! Callooh! Callay!\"\n" +"He chortled in his joy.\n" +"\n" +"'Twas brillig, and the slithy toves\n" +"Did gyre and gimble in the wabe:\n" +"All mimsy were the borogroves,\n" +"And the mome raths outgrabe.\n" +; + +TEST_CASE("raw connection") { + auto_free p(mk_raw_connection()); + max_send_size = 0; + + REQUIRE(p); + REQUIRE(pni_raw_validate(p)); + CHECK_FALSE(pn_raw_connection_is_read_closed(p)); + CHECK_FALSE(pn_raw_connection_is_write_closed(p)); + + int rbuff_count = pn_raw_connection_read_buffers_capacity(p); + CHECK(rbuff_count>0); + int wbuff_count = pn_raw_connection_write_buffers_capacity(p); + CHECK(wbuff_count>0); + + BufferAllocator rb(rbuffer_memory, sizeof(rbuffer_memory)); + BufferAllocator wb(message, sizeof(message)); + + rb.split_buffers(rbuffs); + wb.split_buffers(wbuffs); + + int rtaken = pn_raw_connection_give_read_buffers(p, rbuffs, RBUFFCOUNT); + REQUIRE(pni_raw_validate(p)); + REQUIRE(rtaken==rbuff_count); + + SECTION("Write multiple per event loop") { + int wtaken = 0; + for (size_t i = 0; i < WBUFFCOUNT; ++i) { + int taken = pn_raw_connection_write_buffers(p, &wbuffs[i], 1); + if (taken==0) break; + REQUIRE(pni_raw_validate(p)); + REQUIRE(taken==1); + wtaken += taken; + } + + CHECK(pn_raw_connection_read_buffers_capacity(p) == 0); + CHECK(pn_raw_connection_write_buffers_capacity(p) == 0); + + std::vector read(rtaken); + std::vector written(wtaken); + + SECTION("Simple tests using a looped back socketpair") { + int fds[2]; + REQUIRE(makepair(fds) == 0); + pni_raw_connected(p); + + // First event is always connected + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_RAW_CONNECTION_CONNECTED); + // Mo need buffers event as we already gave buffers + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_EVENT_NONE); + + SECTION("Write then read") { + pni_raw_write(p, fds[0], snd, set_write_error); + CHECK(write_err == 0); + REQUIRE(pni_raw_validate(p)); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_RAW_CONNECTION_WRITTEN); + CHECK(pn_raw_connection_write_buffers_capacity(p) == 0); + int wgiven = pn_raw_connection_take_written_buffers(p, &written[0], written.size()); + REQUIRE(pni_raw_validate(p)); + CHECK(wgiven==wtaken); + + // Write more + for (size_t i = wtaken; i < WBUFFCOUNT; ++i) { + int taken = pn_raw_connection_write_buffers(p, &wbuffs[i], 1); + if (taken==0) break; + REQUIRE(pni_raw_validate(p)); + CHECK(taken==1); + wtaken += taken; + } + + pni_raw_write(p, fds[0], snd, set_write_error); + CHECK(write_err == 0); + REQUIRE(pni_raw_validate(p)); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_RAW_CONNECTION_WRITTEN); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_RAW_CONNECTION_NEED_WRITE_BUFFERS); + wgiven += pn_raw_connection_take_written_buffers(p, &written[0], written.size()); + REQUIRE(pni_raw_validate(p)); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_EVENT_NONE); + CHECK(wgiven==wtaken); + + // At this point we've written every buffer + CHECK(pn_raw_connection_write_buffers_capacity(p) == wbuff_count); + + pni_raw_read(p, fds[1], rcv, set_read_error); + CHECK(read_err == 0); + REQUIRE(pni_raw_validate(p)); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_RAW_CONNECTION_READ); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_EVENT_NONE); + CHECK(pn_raw_connection_read_buffers_capacity(p) == 0); + int rgiven = pn_raw_connection_take_read_buffers(p, &read[0], read.size()); + REQUIRE(pni_raw_validate(p)); + CHECK(rgiven > 0); + + CHECK(pn_raw_connection_read_buffers_capacity(p) == rgiven); + + // At this point we should have read everything - make sure it matches + char* start = message; + for (int i = 0; i < rgiven; ++i) { + CHECK(read[i].size > 0); + CHECK(std::string(read[i].bytes, read[i].size) == std::string(start, read[i].size)); + start += read[i].size; + } + REQUIRE(start-message == sizeof(message)); + } + SECTION("Write then read, short writes") { + max_send_size = 10; + int wgiven = 0; + do { + do { + pni_raw_write(p, fds[0], snd, set_write_error); + CHECK(write_err == 0); + REQUIRE(pni_raw_validate(p)); + } while (pn_event_type(pni_raw_event_next(p)) != PN_RAW_CONNECTION_WRITTEN); + CHECK(pn_raw_connection_write_buffers_capacity(p) == wgiven); + int given = pn_raw_connection_take_written_buffers(p, &written[wgiven], written.size()-wgiven); + REQUIRE(pni_raw_validate(p)); + CHECK(given == 1); + CHECK(written[wgiven].offset == wbuffs[wgiven].offset); + CHECK(written[wgiven].size == wbuffs[wgiven].size); + wgiven += given; + } while (wgiven != wtaken); + + // Write more + for (size_t i = wtaken; i < WBUFFCOUNT; ++i) { + int taken = pn_raw_connection_write_buffers(p, &wbuffs[i], 1); + if (taken==0) break; + REQUIRE(pni_raw_validate(p)); + CHECK(taken==1); + wtaken += taken; + } + + do { + do { + pni_raw_write(p, fds[0], snd, set_write_error); + CHECK(write_err == 0); + REQUIRE(pni_raw_validate(p)); + } while (pn_event_type(pni_raw_event_next(p)) != PN_RAW_CONNECTION_WRITTEN); + } while (pn_event_type(pni_raw_event_next(p)) != PN_RAW_CONNECTION_NEED_WRITE_BUFFERS); + wgiven += pn_raw_connection_take_written_buffers(p, &written[0], written.size()); + REQUIRE(pni_raw_validate(p)); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_EVENT_NONE); + CHECK(wgiven==wtaken); + + // At this point we've written every buffer + CHECK(pn_raw_connection_write_buffers_capacity(p) == wbuff_count); + + pni_raw_read(p, fds[1], rcv, set_read_error); + CHECK(read_err == 0); + REQUIRE(pni_raw_validate(p)); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_RAW_CONNECTION_READ); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_EVENT_NONE); + CHECK(pn_raw_connection_read_buffers_capacity(p) == 0); + int rgiven = pn_raw_connection_take_read_buffers(p, &read[0], read.size()); + REQUIRE(pni_raw_validate(p)); + CHECK(rgiven > 0); + + CHECK(pn_raw_connection_read_buffers_capacity(p) == rgiven); + + // At this point we should have read everything - make sure it matches + char* start = message; + for (int i = 0; i < rgiven; ++i) { + CHECK(read[i].size > 0); + CHECK(std::string(read[i].bytes, read[i].size) == std::string(start, read[i].size)); + start += read[i].size; + } + REQUIRE(start-message == sizeof(message)); + } + freepair(fds); + } + } + + SECTION("Write once per event loop") { + int wtaken = pn_raw_connection_write_buffers(p, wbuffs, WBUFFCOUNT); + REQUIRE(pni_raw_validate(p)); + CHECK(wtaken==wbuff_count); + + CHECK(pn_raw_connection_read_buffers_capacity(p) == 0); + CHECK(pn_raw_connection_write_buffers_capacity(p) == 0); + + std::vector read(rtaken); + std::vector written(wtaken); + + SECTION("Check no change in buffer use without read/write") { + + int rgiven = pn_raw_connection_take_read_buffers(p, &read[0], rtaken); + REQUIRE(pni_raw_validate(p)); + CHECK(rgiven==0); + int wgiven = pn_raw_connection_take_written_buffers(p, &written[0], wtaken); + REQUIRE(pni_raw_validate(p)); + CHECK(wgiven==0); + } + + SECTION("Simple tests using a looped back socketpair") { + int fds[2]; + REQUIRE(makepair(fds) == 0); + pni_raw_connected(p); + + // First event is always connected + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_RAW_CONNECTION_CONNECTED); + // Mo need buffers event as we already gave buffers + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_EVENT_NONE); + + SECTION("Ensure nothing is read if nothing is written") { + pni_raw_read(p, fds[1], rcv, set_read_error); + CHECK(read_err == 0); + REQUIRE(pni_raw_validate(p)); + CHECK(pn_raw_connection_read_buffers_capacity(p) == 0); + CHECK(pn_raw_connection_take_read_buffers(p, &read[0], read.size()) == 0); + REQUIRE(pni_raw_validate(p)); + REQUIRE(pni_raw_event_next(p) == NULL); + + snd_stop(fds[0]); + pni_raw_read(p, fds[1], rcv, set_read_error); + CHECK(read_err == 0); + REQUIRE(pni_raw_validate(p)); + CHECK(pn_raw_connection_is_read_closed(p)); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_RAW_CONNECTION_READ); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_RAW_CONNECTION_CLOSED_READ); + rcv_stop(fds[1]); + pni_raw_write(p, fds[0], snd, set_write_error); + CHECK(write_err == EPIPE); + REQUIRE(pni_raw_validate(p)); + CHECK(pn_raw_connection_is_write_closed(p)); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_RAW_CONNECTION_CLOSED_WRITE); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_RAW_CONNECTION_READ); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_RAW_CONNECTION_WRITTEN); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_RAW_CONNECTION_DISCONNECTED); + } + + SECTION("Read/Write interleaved") { + pni_raw_write(p, fds[0], snd, set_write_error); + CHECK(write_err == 0); + REQUIRE(pni_raw_validate(p)); + CHECK(pn_raw_connection_write_buffers_capacity(p) == 0); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_RAW_CONNECTION_WRITTEN); + int wgiven = pn_raw_connection_take_written_buffers(p, &written[0], written.size()); + REQUIRE(pni_raw_validate(p)); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_RAW_CONNECTION_NEED_WRITE_BUFFERS); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_EVENT_NONE); + CHECK(wgiven==wtaken); + CHECK(pn_raw_connection_write_buffers_capacity(p) == wbuff_count); + + pni_raw_read(p, fds[1], rcv, set_read_error); + CHECK(read_err == 0); + REQUIRE(pni_raw_validate(p)); + CHECK(pn_raw_connection_read_buffers_capacity(p) == 0); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_RAW_CONNECTION_READ); + int rgiven = pn_raw_connection_take_read_buffers(p, &read[0], read.size()); + REQUIRE(pni_raw_validate(p)); + CHECK(rgiven > 0); + CHECK(pn_raw_connection_read_buffers_capacity(p) == rgiven); + + // Write more + wtaken += pn_raw_connection_write_buffers(p, &wbuffs[wtaken], WBUFFCOUNT-wtaken); + REQUIRE(pni_raw_validate(p)); + CHECK(wtaken==WBUFFCOUNT); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_EVENT_NONE); + + pni_raw_write(p, fds[0], snd, set_write_error); + CHECK(write_err == 0); + REQUIRE(pni_raw_validate(p)); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_RAW_CONNECTION_WRITTEN); + wgiven += pn_raw_connection_take_written_buffers(p, &written[0], written.size()); + REQUIRE(pni_raw_validate(p)); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_RAW_CONNECTION_NEED_WRITE_BUFFERS); + CHECK(wgiven==wtaken); + + // At this point we've written every buffer + + pni_raw_read(p, fds[1], rcv, set_read_error); + CHECK(read_err == 0); + REQUIRE(pni_raw_validate(p)); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_RAW_CONNECTION_READ); + int rgiven_before = rgiven; + rgiven += pn_raw_connection_take_read_buffers(p, &read[rgiven], read.size()-rgiven); + REQUIRE(pni_raw_validate(p)); + CHECK(rgiven > rgiven_before); + + CHECK(pn_raw_connection_read_buffers_capacity(p) == rgiven); + CHECK(pn_raw_connection_write_buffers_capacity(p) == wbuff_count); + + // At this point we should have read everything - make sure it matches + char* start = message; + for (int i = 0; i < rgiven; ++i) { + CHECK(read[i].size > 0); + CHECK(std::string(read[i].bytes, read[i].size) == std::string(start, read[i].size)); + start += read[i].size; + } + REQUIRE(start-message == sizeof(message)); + } + + SECTION("Write then read") { + pni_raw_write(p, fds[0], snd, set_write_error); + CHECK(write_err == 0); + REQUIRE(pni_raw_validate(p)); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_RAW_CONNECTION_WRITTEN); + CHECK(pn_raw_connection_write_buffers_capacity(p) == 0); + int wgiven = pn_raw_connection_take_written_buffers(p, &written[0], written.size()); + REQUIRE(pni_raw_validate(p)); + CHECK(wgiven==wtaken); + + // Write more + wtaken += pn_raw_connection_write_buffers(p, &wbuffs[wtaken], WBUFFCOUNT-wtaken); + REQUIRE(pni_raw_validate(p)); + CHECK(wtaken==WBUFFCOUNT); + + pni_raw_write(p, fds[0], snd, set_write_error); + CHECK(write_err == 0); + REQUIRE(pni_raw_validate(p)); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_RAW_CONNECTION_WRITTEN); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_RAW_CONNECTION_NEED_WRITE_BUFFERS); + wgiven += pn_raw_connection_take_written_buffers(p, &written[0], written.size()); + REQUIRE(pni_raw_validate(p)); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_EVENT_NONE); + CHECK(wgiven==wtaken); + + // At this point we've written every buffer + CHECK(pn_raw_connection_write_buffers_capacity(p) == wbuff_count); + + pni_raw_read(p, fds[1], rcv, set_read_error); + CHECK(read_err == 0); + REQUIRE(pni_raw_validate(p)); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_RAW_CONNECTION_READ); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_EVENT_NONE); + CHECK(pn_raw_connection_read_buffers_capacity(p) == 0); + int rgiven = pn_raw_connection_take_read_buffers(p, &read[0], read.size()); + REQUIRE(pni_raw_validate(p)); + CHECK(rgiven > 0); + + CHECK(pn_raw_connection_read_buffers_capacity(p) == rgiven); + + // At this point we should have read everything - make sure it matches + char* start = message; + for (int i = 0; i < rgiven; ++i) { + CHECK(read[i].size > 0); + CHECK(std::string(read[i].bytes, read[i].size) == std::string(start, read[i].size)); + start += read[i].size; + } + REQUIRE(start-message == sizeof(message)); + } + + SECTION("Write, close, then read") { + pni_raw_write(p, fds[0], snd, set_write_error); + CHECK(write_err == 0); + REQUIRE(pni_raw_validate(p)); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_RAW_CONNECTION_WRITTEN); + CHECK(pn_raw_connection_write_buffers_capacity(p) == 0); + int wgiven = pn_raw_connection_take_written_buffers(p, &written[0], written.size()); + REQUIRE(pni_raw_validate(p)); + CHECK(wgiven==wtaken); + + // Write more + wtaken += pn_raw_connection_write_buffers(p, &wbuffs[wtaken], WBUFFCOUNT-wtaken); + REQUIRE(pni_raw_validate(p)); + CHECK(wtaken==WBUFFCOUNT); + + pni_raw_write(p, fds[0], snd, set_write_error); + CHECK(write_err == 0); + REQUIRE(pni_raw_validate(p)); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_RAW_CONNECTION_WRITTEN); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_RAW_CONNECTION_NEED_WRITE_BUFFERS); + wgiven += pn_raw_connection_take_written_buffers(p, &written[0], written.size()); + REQUIRE(pni_raw_validate(p)); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_EVENT_NONE); + CHECK(wgiven==wtaken); + + // At this point we've written every buffer + CHECK(pn_raw_connection_write_buffers_capacity(p) == wbuff_count); + + snd_stop(fds[0]); + pni_raw_read(p, fds[1], rcv, set_read_error); + CHECK(read_err == 0); + REQUIRE(pni_raw_validate(p)); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_RAW_CONNECTION_READ); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_RAW_CONNECTION_CLOSED_READ); + REQUIRE(pn_event_type(pni_raw_event_next(p)) == PN_EVENT_NONE); + CHECK(pn_raw_connection_read_buffers_capacity(p) == 0); + int rgiven = pn_raw_connection_take_read_buffers(p, &read[0], read.size()); + REQUIRE(pni_raw_validate(p)); + CHECK(rgiven > 0); + + CHECK(pn_raw_connection_read_buffers_capacity(p) == rgiven); + CHECK(read[rgiven-1].size == 0); + + // At this point we should have read everything - make sure it matches + char* start = message; + for (int i = 0; i < rgiven-1; ++i) { + CHECK(read[i].size > 0); + CHECK(std::string(read[i].bytes, read[i].size) == std::string(start, read[i].size)); + start += read[i].size; + } + REQUIRE(start-message == sizeof(message)); + } + + freepair(fds); + } + } +} diff --git a/c/tools/CMakeLists.txt b/c/tools/CMakeLists.txt index 7a577cb160..471cc79341 100644 --- a/c/tools/CMakeLists.txt +++ b/c/tools/CMakeLists.txt @@ -17,20 +17,8 @@ # under the License. # -include(CheckIncludeFiles) - include_directories (${PN_C_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/include) -CHECK_INCLUDE_FILES("inttypes.h" INTTYPES_AVAILABLE) -if (INTTYPES_AVAILABLE) - list(APPEND PLATFORM_DEFINITIONS "USE_INTTYPES") -else (INTTYPES_AVAILABLE) - if (CMAKE_COMPILER_IS_GNUCC) - # since inttypes.h provides portable printf format macros - set (COMPILE_WARNING_FLAGS "${COMPILE_WARNING_FLAGS} -Wno-format") - endif (CMAKE_COMPILER_IS_GNUCC) -endif (INTTYPES_AVAILABLE) - add_executable(msgr-recv msgr-recv.c msgr-common.c) add_executable(msgr-send msgr-send.c msgr-common.c) add_executable(reactor-recv reactor-recv.c msgr-common.c) @@ -44,10 +32,5 @@ target_link_libraries(reactor-send qpid-proton) set_target_properties ( msgr-recv msgr-send reactor-recv reactor-send PROPERTIES - COMPILE_FLAGS "${COMPILE_WARNING_FLAGS} ${COMPILE_LANGUAGE_FLAGS}" - COMPILE_DEFINITIONS "${PLATFORM_DEFINITIONS}" + COMPILE_FLAGS "${COMPILE_WARNING_FLAGS} ${C_STANDARD_FLAGS}" ) - -if (BUILD_WITH_CXX) - set_source_files_properties (msgr-recv.c msgr-send.c msgr-common.c reactor-recv.c reactor-send.c PROPERTIES LANGUAGE CXX) -endif (BUILD_WITH_CXX) diff --git a/c/tools/msgr-common.c b/c/tools/msgr-common.c index 7c43a45df1..e9c285c30b 100644 --- a/c/tools/msgr-common.c +++ b/c/tools/msgr-common.c @@ -64,7 +64,7 @@ void addresses_free( Addresses_t *a ) int i; for (i = 0; i < a->count; i++) if (a->addresses[i]) free( (void *)a->addresses[i] ); - free( a->addresses ); + free( (void *) a->addresses ); a->addresses = NULL; } } @@ -158,15 +158,15 @@ void parse_password( const char *input, char **password ) } } -static int log = 0; +static int dolog = 0; void enable_logging() { - log = 1; + dolog = 1; } void LOG( const char *fmt, ... ) { - if (log) { + if (dolog) { va_list ap; va_start(ap, fmt); vfprintf( stdout, fmt, ap ); diff --git a/c/tools/msgr-common.h b/c/tools/msgr-common.h index d3f483a5e6..6fefce7ae1 100644 --- a/c/tools/msgr-common.h +++ b/c/tools/msgr-common.h @@ -20,30 +20,7 @@ #include "pncompat/misc_defs.h" -#if defined(USE_INTTYPES) -#ifdef __cplusplus -#define __STDC_FORMAT_MACROS -#endif #include -#endif - -#ifdef _MSC_VER -#if !defined(PRIu64) -#define PRIu64 "I64u" -#endif -#if !defined(SCNu64) -#define SCNu64 "I64u" -#endif -#endif - -/* If still not defined, best guess */ -#if !defined(SCNu64) -#define SCNu64 "ul" -#endif -#if !defined(PRIu64) -#define PRIu64 "ul" -#endif - #include "proton/types.h" #include "proton/message.h" diff --git a/c/versions.cmake b/c/versions.cmake index 1a6ccb4677..c86419b005 100644 --- a/c/versions.cmake +++ b/c/versions.cmake @@ -1,14 +1,14 @@ set(PN_LIB_CORE_MAJOR_VERSION 10) -set(PN_LIB_CORE_MINOR_VERSION 9) +set(PN_LIB_CORE_MINOR_VERSION 10) set(PN_LIB_CORE_PATCH_VERSION 0) set(PN_LIB_CORE_VERSION "${PN_LIB_CORE_MAJOR_VERSION}.${PN_LIB_CORE_MINOR_VERSION}.${PN_LIB_CORE_PATCH_VERSION}") set(PN_LIB_PROACTOR_MAJOR_VERSION 1) -set(PN_LIB_PROACTOR_MINOR_VERSION 6) +set(PN_LIB_PROACTOR_MINOR_VERSION 7) set(PN_LIB_PROACTOR_PATCH_VERSION 0) set(PN_LIB_PROACTOR_VERSION "${PN_LIB_PROACTOR_MAJOR_VERSION}.${PN_LIB_PROACTOR_MINOR_VERSION}.${PN_LIB_PROACTOR_PATCH_VERSION}") set(PN_LIB_LEGACY_MAJOR_VERSION 11) -set(PN_LIB_LEGACY_MINOR_VERSION 10) +set(PN_LIB_LEGACY_MINOR_VERSION 11) set(PN_LIB_LEGACY_PATCH_VERSION 0) set(PN_LIB_LEGACY_VERSION "${PN_LIB_LEGACY_MAJOR_VERSION}.${PN_LIB_LEGACY_MINOR_VERSION}.${PN_LIB_LEGACY_PATCH_VERSION}") diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 59b6a010f2..bd926df0b9 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -18,6 +18,7 @@ # cmake_minimum_required(VERSION 2.8.12) +include(CMakeDependentOption) enable_language(CXX) set(CMAKE_THREAD_PREFER_PTHREAD TRUE) @@ -29,7 +30,8 @@ set (BUILD_CPP_03 OFF CACHE BOOL "Compile the C++ binding as C++03 even when C++ # Check for JSON-CPP support for connection configuration find_package(JsonCpp) -option(ENABLE_JSONCPP "Use jsoncpp parser for connection configuration" ${JsonCpp_FOUND}) +cmake_dependent_option(ENABLE_JSONCPP "Use jsoncpp parser for connection configuration" ${JsonCpp_FOUND} + "NOT BUILD_CPP_03" OFF) if (ENABLE_JSONCPP) include_directories(${JsonCpp_INCLUDE_DIRS}) set(CONNECT_CONFIG_SRC src/connect_config.cpp) @@ -41,40 +43,24 @@ endif() # This effectively checks for cmake version 3.1 or later if (DEFINED CMAKE_CXX_COMPILE_FEATURES) if (BUILD_CPP_03) - set(STD 98) + set(CMAKE_CXX_STANDARD 98) else () - set(STD 11) - list(APPEND PLATFORM_LIBS Threads::Threads) - endif () - set(CMAKE_CXX_STANDARD ${STD}) - set(CMAKE_CXX_EXTENSIONS OFF) - if (MSVC) # Compiler feature checks only needed for Visual Studio in this case - include(cpp.cmake) - elseif (STD EQUAL 11) set(CPP_DEFINITIONS "HAS_CPP11") - endif() + endif () else () if (BUILD_CPP_03) set(CXX_STANDARD "-std=c++98") else () - include(CheckCXXCompilerFlag) - # These flags work with GCC/Clang/SunPro compilers - check_cxx_compiler_flag("-std=c++11" ACCEPTS_CXX11) - check_cxx_compiler_flag("-std=c++0x" ACCEPTS_CXX0X) if (ACCEPTS_CXX11) - set(CXX_STANDARD "-std=c++11") set(CPP_DEFINITIONS "HAS_CPP11") - list(APPEND PLATFORM_LIBS Threads::Threads) - elseif(ACCEPTS_CXX0X) - set(CXX_STANDARD "-std=c++0x") - list(APPEND PLATFORM_LIBS Threads::Threads) - include(cpp.cmake) # Compiler checks needed for C++0x as not all C++11 may be supported else() - include(cpp.cmake) # Compiler checks needed as we have no idea whats going on here! + include(cpp.cmake) # Compiler checks needed as not all C++11 may be supported endif() endif() endif () +list(APPEND PLATFORM_LIBS Threads::Threads) + # Construct #define lines to insert in config_presets.hpp foreach(d ${CPP_DEFINITIONS}) set(presets "${presets}#define PN_CPP_LIB_${d} 1\n") @@ -164,12 +150,6 @@ set(qpid-proton-cpp-source ${CONNECT_CONFIG_SRC} ) -set_source_files_properties ( - ${qpid-proton-cpp-source} - PROPERTIES - COMPILE_FLAGS "${LTO}" - ) - if (MSVC) set (CMAKE_DEBUG_POSTFIX "d") endif () @@ -177,6 +157,9 @@ endif () add_library(qpid-proton-cpp SHARED ${qpid-proton-cpp-source}) if(BUILD_STATIC_LIBS) add_library(qpid-proton-cpp-static STATIC ${qpid-proton-cpp-source}) + target_compile_definitions(qpid-proton-cpp-static + PRIVATE PROTON_DECLARE_STATIC + PUBLIC PN_CPP_DECLARE_STATIC) set(STATIC_LIBS qpid-proton-cpp-static) endif(BUILD_STATIC_LIBS) @@ -188,7 +171,8 @@ set_target_properties ( LINKER_LANGUAGE CXX VERSION "${PN_LIB_CPP_VERSION}" SOVERSION "${PN_LIB_CPP_MAJOR_VERSION}" - LINK_FLAGS "${CATCH_UNDEFINED} ${LTO}" + LINK_FLAGS "${CATCH_UNDEFINED} ${LINK_LTO}" + COMPILE_FLAGS "${LTO}" ) ## Install diff --git a/cpp/examples/CMakeLists.txt b/cpp/examples/CMakeLists.txt index 4c6dc9f43b..8c50612174 100644 --- a/cpp/examples/CMakeLists.txt +++ b/cpp/examples/CMakeLists.txt @@ -98,7 +98,7 @@ if(HAS_ENOUGH_CPP11) multithreaded_client multithreaded_client_flow_control) add_executable(${example} ${example}.cpp) - target_link_libraries(${example} ${CMAKE_THREAD_LIBS_INIT}) + target_link_libraries(${example} Threads::Threads) endforeach() endif() endif() diff --git a/cpp/versions.cmake b/cpp/versions.cmake index 65e9166b35..8753328a27 100644 --- a/cpp/versions.cmake +++ b/cpp/versions.cmake @@ -1,4 +1,4 @@ set(PN_LIB_CPP_MAJOR_VERSION 12) set(PN_LIB_CPP_MINOR_VERSION 7) -set(PN_LIB_CPP_PATCH_VERSION 0) +set(PN_LIB_CPP_PATCH_VERSION 1) set(PN_LIB_CPP_VERSION "${PN_LIB_CPP_MAJOR_VERSION}.${PN_LIB_CPP_MINOR_VERSION}.${PN_LIB_CPP_PATCH_VERSION}") diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 710ac058d3..bdd2b4c500 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -41,7 +41,7 @@ list(APPEND SWIG_MODULE_cproton_EXTRA_DEPS ) swig_add_library(cproton LANGUAGE python SOURCES cproton.i) -swig_link_libraries(cproton ${BINDING_DEPS} ${PYTHON_LIBRARIES}) +swig_link_libraries(cproton ${BINDING_DEPS} ${PYTHON_LIBRARIES} -lm) set_target_properties(${SWIG_MODULE_cproton_REAL_NAME} PROPERTIES LINK_FLAGS "${CATCH_UNDEFINED}") diff --git a/python/cproton.i b/python/cproton.i index 0dcc2ce060..2a7bff964f 100644 --- a/python/cproton.i +++ b/python/cproton.i @@ -22,6 +22,10 @@ #if defined(_WIN32) && ! defined(__CYGWIN__) #include #endif + +/* TODO: Remove once pn_work_head() and related have been removed from Proton */ +#define PN_USE_DEPRECATED_API 1 + #include #include #include diff --git a/python/proton/_delivery.py b/python/proton/_delivery.py index f5c955fa95..0f871cf170 100644 --- a/python/proton/_delivery.py +++ b/python/proton/_delivery.py @@ -445,7 +445,9 @@ def abort(self): @property def work_next(self): - """ + """Deprecated: use on_message(), on_accepted(), on_rejected(), + on_released(), and on_settled() instead. + The next :class:`Delivery` on the connection that has pending operations. diff --git a/python/proton/_endpoints.py b/python/proton/_endpoints.py index f518137151..e528de6888 100644 --- a/python/proton/_endpoints.py +++ b/python/proton/_endpoints.py @@ -53,7 +53,8 @@ pn_terminus_get_durability, pn_terminus_get_expiry_policy, pn_terminus_get_timeout, pn_terminus_get_type, \ pn_terminus_is_dynamic, pn_terminus_outcomes, pn_terminus_properties, pn_terminus_set_address, \ pn_terminus_set_distribution_mode, pn_terminus_set_durability, pn_terminus_set_dynamic, \ - pn_terminus_set_expiry_policy, pn_terminus_set_timeout, pn_terminus_set_type, pn_work_head + pn_terminus_set_expiry_policy, pn_terminus_set_timeout, pn_terminus_set_type, pn_work_head, \ + pn_link_properties, pn_link_remote_properties from ._common import unicode2utf8, utf82unicode from ._condition import cond2obj, obj2cond @@ -471,7 +472,9 @@ def link_head(self, mask): @property def work_head(self): - """ + """Deprecated: use on_message(), on_accepted(), on_rejected(), + on_released(), and on_settled() instead. + Extracts the first delivery on the connection that has pending operations. @@ -771,6 +774,10 @@ def wrap(impl): def __init__(self, impl): Wrapper.__init__(self, impl, pn_link_attachments) + def _init(self): + Endpoint._init(self) + self.properties = None + def _get_attachments(self): return pn_link_attachments(self._impl) @@ -796,6 +803,7 @@ def open(self): sent to the peer. A link is fully active once both peers have attached it. """ + obj2dat(self.properties, pn_link_properties(self._impl)) pn_link_open(self._impl) def close(self): @@ -1200,6 +1208,41 @@ def free(self): """ pn_link_free(self._impl) + @property + def remote_properties(self): + """ + The properties specified by the remote peer for this link. + + This operation will return a :class:`Data` object that + is valid until the link object is freed. This :class:`Data` + object will be empty until the remote link is opened as + indicated by the :const:`REMOTE_ACTIVE` flag. + + :type: :class:`Data` + """ + return dat2obj(pn_link_remote_properties(self._impl)) + + + def _get_properties(self): + return self._properties_dict + + def _set_properties(self, properties_dict): + if isinstance(properties_dict, dict): + self._properties_dict = PropertyDict(properties_dict, raise_on_error=False) + else: + self._properties_dict = properties_dict + + properties = property(_get_properties, _set_properties, doc=""" + Link properties as a dictionary of key/values. The AMQP 1.0 + specification restricts this dictionary to have keys that are only + :class:`symbol` types. It is possible to use the special ``dict`` + subclass :class:`PropertyDict` which will by default enforce this + restrictions on construction. In addition, if strings type are used, + this will silently convert them into symbols. + + :type: ``dict`` containing :class:`symbol`` keys. + """) + class Sender(Link): """ diff --git a/python/setup.py.in b/python/setup.py.in index 31fa59ed52..9fb607e5a4 100644 --- a/python/setup.py.in +++ b/python/setup.py.in @@ -40,6 +40,9 @@ From the Python side, this scripts overrides 1 command - build_ext - and it adds new one. The latter - Configure - is called from the former to setup/discover what's in the system. The rest of the commands and steps are done normally without any kind of monkey patching. + +TODO: On windows we now only support VS2015 and above and python 3, we should check +for this and produce an appropriate error if the requirements are not met. """ import os @@ -134,7 +137,6 @@ class Configure(build_ext): if self.compiler_type=='msvc': sources += [ - os.path.join(proton_src, 'compiler', 'msvc', 'snprintf.c'), os.path.join(proton_src, 'compiler', 'msvc', 'start.c') ] elif self.compiler_type=='unix': @@ -151,7 +153,7 @@ class Configure(build_ext): sources.append(os.path.join(proton_src, 'ssl', 'openssl.c')) elif os.name=='nt': libraries += ['crypt32', 'secur32'] - sources.append(os.path.join(proton_src, 'ssl', 'schannel.c')) + sources.append(os.path.join(proton_src, 'ssl', 'schannel.cpp')) else: sources.append(os.path.join(proton_src, 'ssl', 'ssl_stub.c')) log.warn("OpenSSL not installed - disabling SSL support!") @@ -183,36 +185,6 @@ class Configure(build_ext): log.warn("Windows - only the ANONYMOUS and PLAIN mechanisms will be supported!") sources.append(os.path.join(proton_src, 'sasl', 'cyrus_stub.c')) - # Hack for Windows/msvc: We need to compile proton as C++, but it seems the only way to - # force this in setup.py is to use a .cpp extension! So copy all the source files to .cpp - # and use these as the compile sources - if self.compiler_type=='msvc': - targets = [] - target_base = os.path.join(self.build_temp, 'srcs') - try: - # Might need to make intermediate directories use os.makedirs() not os.mkdir() - os.makedirs(target_base) - except OSError: - pass - - for f in sources: - # We know each file ends in '.c' as we filtered on that above so just add 'pp' to end - target = os.path.join(target_base, os.path.basename(f) + 'pp') - shutil.copy(f, target) - targets.append(target) - - # Copy .h files into temp tree too as we need them to compile - for root, _, files in os.walk(proton_core_src): - for file_ in files: - if file_.endswith('.h'): - shutil.copy(os.path.join(root, file_), os.path.join(target_base, file_)) - - # Copy ssl/sasl .h files - shutil.copy(os.path.join(proton_src, 'sasl', 'sasl-internal.h'), os.path.join(target_base, 'sasl-internal.h')) - shutil.copy(os.path.join(proton_src, 'ssl', 'ssl-internal.h'), os.path.join(target_base, 'ssl-internal.h')) - - sources = targets - # compile all the proton sources. We'll add the resulting list of # objects to the _cproton extension as 'extra objects'. We do this # instead of just lumping all the sources into the extension to prevent @@ -326,5 +298,5 @@ setup(name='python-qpid-proton', # Configure class above ext_modules=[Extension('_cproton', sources=['cproton_wrap.c'], - extra_compile_args=['-pthread', '-DPROTON_DECLARE_STATIC'], + extra_compile_args=['-DPROTON_DECLARE_STATIC'], libraries=['qpid-proton-core'])]) diff --git a/python/tests/integration/test_PROTON_1800_syncrequestresponse_fd_leak.py b/python/tests/integration/test_PROTON_1800_syncrequestresponse_fd_leak.py index e143f8c917..614ed7e257 100644 --- a/python/tests/integration/test_PROTON_1800_syncrequestresponse_fd_leak.py +++ b/python/tests/integration/test_PROTON_1800_syncrequestresponse_fd_leak.py @@ -31,6 +31,7 @@ import os import threading import subprocess +import warnings from collections import namedtuple import cproton @@ -45,20 +46,24 @@ from test_unittest import unittest -def count_fds(): - # type: () -> int - return len(os.listdir('/proc/self/fd/')) +def get_fd_set(): + # type: () -> set[str] + return set(os.listdir('/proc/self/fd/')) @contextlib.contextmanager def no_fd_leaks(test): # type: (unittest.TestCase) -> None - before = count_fds() - yield - delta = count_fds() - before - if delta != 0: - subprocess.check_call("ls -lF /proc/{0}/fd/".format(os.getpid()), shell=True) - test.assertEqual(0, delta, "Found {0} new fd(s) after the test".format(delta)) + with warnings.catch_warnings(record=True) as ws: + before = get_fd_set() + yield + delta = get_fd_set().difference(before) + if len(delta) != 0: + subprocess.check_call("ls -lF /proc/{0}/fd/".format(os.getpid()), shell=True) + test.fail("Found {0} new fd(s) after the test".format(delta)) + + if len(ws) > 0: + test.fail([w.message for w in ws]) class Broker(proton.handlers.MessagingHandler): @@ -109,17 +114,44 @@ def test_broker(): container = proton.reactor.Container(broker) threading.Thread(target=container.run).start() - yield broker - - container.stop() + try: + yield broker + finally: + container.stop() PROC_SELF_FD_EXISTS = os.path.exists("/proc/self/fd"), "Skipped: Directory /proc/self/fd does not exist" +def skipOnFailure(reason="AssertionError ignored."): + """Decorator for test methods that swallows AssertionErrors. + + Passing tests are reported unchanged, tests failing with AssertionError are + reported as skipped, and tests failing with any other kind of error are + permitted to fail. + + Use this to temporarily suppress flaky or environment-sensitive tests without + turning them into dead code that is not being run at all. + + If a test is reliably failing, use unittest.expectedFailure instead.""" + + def skip_on_assertion(old_test): + def new_test(*args, **kwargs): + try: + old_test(*args, **kwargs) + except AssertionError as e: + raise unittest.SkipTest("Test failure '{0}' ignored: {1}".format( + str(e), reason)) + + return new_test + + return skip_on_assertion + + class Proton1800Test(unittest.TestCase): @unittest.skipUnless(*PROC_SELF_FD_EXISTS) - def test_sync_request_response_blocking_connection_no_object_leaks(self): + @skipOnFailure(reason="PROTON-1800: sut is leaking one fd on Ubuntu Xenial docker image") + def test_sync_request_response_blocking_connection_no_fd_leaks(self): with test_broker() as tb: sockname = tb.get_acceptor_sockname() url = "{0}:{1}".format(*sockname) @@ -131,6 +163,7 @@ def test_sync_request_response_blocking_connection_no_object_leaks(self): try: request = "One Two Three Four" response = client.call(Message(body=request)) + self.assertEqual(response.body, "ONE TWO THREE FOUR") finally: client.connection.close() diff --git a/python/tests/integration/test_PROTON_2121_blocking_connection_fd_leak.py b/python/tests/integration/test_PROTON_2121_blocking_connection_fd_leak.py index dd6dcbe968..58e121ca11 100644 --- a/python/tests/integration/test_PROTON_2121_blocking_connection_fd_leak.py +++ b/python/tests/integration/test_PROTON_2121_blocking_connection_fd_leak.py @@ -42,21 +42,21 @@ from test_unittest import unittest -def count_fds(): - # type: () -> int - return len(os.listdir('/proc/self/fd/')) +def get_fd_set(): + # type: () -> set[str] + return set(os.listdir('/proc/self/fd/')) @contextlib.contextmanager def no_fd_leaks(test): # type: (unittest.TestCase) -> None with warnings.catch_warnings(record=True) as ws: - before = count_fds() + before = get_fd_set() yield - delta = count_fds() - before - if delta != 0: + delta = get_fd_set().difference(before) + if len(delta) != 0: subprocess.check_call("ls -lF /proc/{0}/fd/".format(os.getpid()), shell=True) - test.assertEqual(0, delta, "Found {0} new fd(s) after the test".format(delta)) + test.fail("Found {0} new fd(s) after the test".format(delta)) if len(ws) > 0: test.fail([w.message for w in ws]) @@ -101,9 +101,10 @@ def test_broker(): container = proton.reactor.Container(broker) threading.Thread(target=container.run).start() - yield broker - - container.stop() + try: + yield broker + finally: + container.stop() PROC_SELF_FD_EXISTS = os.path.exists("/proc/self/fd"), "Skipped: Directory /proc/self/fd does not exist" diff --git a/python/tests/proton_tests/engine.py b/python/tests/proton_tests/engine.py index 48bee6bab8..1b4c02bf8a 100644 --- a/python/tests/proton_tests/engine.py +++ b/python/tests/proton_tests/engine.py @@ -765,6 +765,17 @@ def test_max_message_size(self): self.pump() assert self.rcv.remote_max_message_size == 13579 + def test_properties(self): + sender_props = {symbol('key1'): 'value1', + symbol('key2'): 'value2'} + self.snd.properties = sender_props + self.snd.open() + self.rcv.open() + self.pump() + + assert self.rcv.remote_properties == sender_props, (self.rcv.remote_properties, sender_props) + assert self.snd.remote_properties == None, (self.snd.remote_properties, None) + def test_cleanup(self): snd, rcv = self.link("test-link") snd.open() diff --git a/scripts/env.py b/scripts/env.py index 443bdcac68..1447e9778a 100644 --- a/scripts/env.py +++ b/scripts/env.py @@ -46,15 +46,16 @@ def main(argv=None): z = args[0].split("=", 1) if len(z) != 2: break # done with env args - if len(z[0]) == 0: - raise Exception("Error: incorrect format for env var: '%s'" % str(args[x])) + name, value = z[0], z[1] + if len(name) == 0: + raise Exception("Error: incorrect format for env var: '%s'" % str(args[0])) del args[0] - if len(z[1]) == 0: + if len(value) == 0: # value is not present, so delete it - if z[0] in new_env: - del new_env[z[0]] + if name in new_env: + del new_env[name] else: - new_env[z[0]] = z[1] + new_env[name] = value if len(args) == 0 or len(args[0]) == 0: raise Exception("Error: syntax error in command arguments") diff --git a/tests/valgrind.supp b/tests/valgrind.supp index 45ef5c5ac8..e1bc52ce1e 100644 --- a/tests/valgrind.supp +++ b/tests/valgrind.supp @@ -173,7 +173,6 @@ TODO(PROTON-2225) investigate threaderciser test Memcheck:Leak fun:malloc - fun:connection_ctx_new ... fun:cpool_connect } diff --git a/tools/cmake/Modules/WindowsC99CheckDef.cmake b/tools/cmake/Modules/WindowsC99CheckDef.cmake deleted file mode 100644 index 6375c3b286..0000000000 --- a/tools/cmake/Modules/WindowsC99CheckDef.cmake +++ /dev/null @@ -1,35 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -# -# Check qpid-proton.dll after linking for dangerous calls to -# Windows functions that suggest but deviate from C99 behavior: -# _snprintf, vsnprintf, _vsnprintf -# See platform.h for safe wrapper calls. -# - -set(obj_dir ${CMAKE_CURRENT_BINARY_DIR}/qpid-proton.dir/${CMAKE_CFG_INTDIR}) - -add_custom_command( - TARGET qpid-proton - PRE_LINK - COMMAND ${PYTHON_EXECUTABLE} - ${CMAKE_CURRENT_LIST_DIR}/WindowsC99SymbolCheck.py - $ - COMMENT "Checking for dangerous use of C99-violating functions") diff --git a/tools/cmake/Modules/WindowsC99SymbolCheck.py b/tools/cmake/Modules/WindowsC99SymbolCheck.py deleted file mode 100644 index dd1f8f9ec8..0000000000 --- a/tools/cmake/Modules/WindowsC99SymbolCheck.py +++ /dev/null @@ -1,66 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -# -# Stop cmake build if pn_i_xxx substitute functions aren't used for -# the dangerous non-complying [v]snprintf family. A source of -# painful bug-hunting. -# -# Each obj must be checked instead of just the dll since Visual Studio -# sometimes inserts references to vsnprintf in DllMainCRTStartup, -# causing false positives. -# -# bad: vsnprintf, __vsnprintf, _imp__vsnprintf, ..., same for snprintf -# OK: vsnprintf_s, pn_i_vsnprintf -# - -import sys -import os -import subprocess -import glob -import re - -def symcheck(objfile): - - symfile = objfile.replace('.obj', '.sym') - cmd = ['dumpbin.exe', '/SYMBOLS', objfile, '/OUT:' + symfile] - - # /dev/null standin - junk = open('junk', 'w') - p = subprocess.Popen(cmd, stdout=junk) - n = p.wait() - if n != 0 : - raise Exception("dumpbin call failure") - - f = open(symfile, 'r') - for line in f : - m = re.search(r'UNDEF.*\b([a-zA-Z_]*snprintf)\b', line) - if m : - sym = m.group(1) - if re.match(r'_*pni_v?snprintf', sym) is None : - raise Exception('Unsafe use of C99 violating function in ' + objfile + ' : ' + sym) - -def main(): - os.chdir(sys.argv[1]) - objs = glob.glob('*.obj') - for obj in glob.glob('*.obj'): - symcheck(obj) - -if __name__ == "__main__": - sys.exit(main())