diff --git a/.gitignore b/.gitignore index 3f7ff7b7b4..d7226880d4 100644 --- a/.gitignore +++ b/.gitignore @@ -33,9 +33,6 @@ Release/*.* *.gcda *.gcov -# Ignore locally installed node_modules -/node_modules - # Ignore tmp directory. tmp @@ -50,7 +47,6 @@ debug_log.txt # Ignore customized configs rippled.cfg validators.txt -test/config.js # Doxygen generated documentation output HtmlDocumentation diff --git a/.travis.yml b/.travis.yml index e9f062abbc..4238acba1e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,9 +10,13 @@ env: # namepart must match the folder name internal # to boost's .tar.gz. - LCOV_ROOT=$HOME/lcov + - GDB_ROOT=$HOME/gdb - BOOST_ROOT=$HOME/boost_1_60_0 - BOOST_URL='http://sourceforge.net/projects/boost/files/boost/1.60.0/boost_1_60_0.tar.gz' +# Travis is timing out on Trusty. So, for now, use Precise. July 2017 +dist: precise + addons: apt: sources: ['ubuntu-toolchain-r-test'] @@ -27,6 +31,8 @@ addons: - binutils-gold # Provides a backtrace if the unittests crash - gdb + # needed to build gdb + - texinfo matrix: include: @@ -40,6 +46,9 @@ matrix: - compiler: clang env: GCC_VER=5 TARGET=debug CLANG_VER=3.8 PATH=$PWD/llvm-$LLVM_VERSION/bin:$PATH + cache: + directories: + - $GDB_ROOT - compiler: clang env: GCC_VER=5 TARGET=debug.nounity CLANG_VER=3.8 PATH=$PWD/llvm-$LLVM_VERSION/bin:$PATH @@ -56,6 +65,7 @@ cache: - $BOOST_ROOT - llvm-$LLVM_VERSION - cmake + - $GDB_ROOT before_install: - bin/ci/ubuntu/install-dependencies.sh @@ -69,3 +79,5 @@ notifications: irc: channels: - "chat.freenode.net#ripple-dev" + +dist: precise diff --git a/Builds/CMake/CMakeFuncs.cmake b/Builds/CMake/CMakeFuncs.cmake index 04b708a292..87f2ee2675 100644 --- a/Builds/CMake/CMakeFuncs.cmake +++ b/Builds/CMake/CMakeFuncs.cmake @@ -12,7 +12,7 @@ endif() macro(parse_target) - if (NOT target) + if (NOT target OR target STREQUAL "default") if (NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug) endif() @@ -110,14 +110,27 @@ macro(parse_target) endwhile() endif() - # Promote these values to the CACHE, then unset the locals - # to prevent shadowing. - set(CMAKE_C_COMPILER ${CMAKE_C_COMPILER} CACHE FILEPATH - "Path to a program" FORCE) - unset(CMAKE_C_COMPILER) - set(CMAKE_CXX_COMPILER ${CMAKE_CXX_COMPILER} CACHE FILEPATH - "Path to a program" FORCE) - unset(CMAKE_CXX_COMPILER) + + if(CMAKE_C_COMPILER MATCHES "-NOTFOUND$" OR + CMAKE_CXX_COMPILER MATCHES "-NOTFOUND$") + message(FATAL_ERROR "Can not find appropriate compiler for target ${target}") + endif() + + # If defined, promote the compiler path values to the CACHE, then + # unset the locals to prevent shadowing. Some scenarios do not + # need or want to find a compiler, such as -GNinja under Windows. + # Setting these values in those case may prevent CMake from finding + # a valid compiler. + if (CMAKE_C_COMPILER) + set(CMAKE_C_COMPILER ${CMAKE_C_COMPILER} CACHE FILEPATH + "Path to a program" FORCE) + unset(CMAKE_C_COMPILER) + endif (CMAKE_C_COMPILER) + if (CMAKE_CXX_COMPILER) + set(CMAKE_CXX_COMPILER ${CMAKE_CXX_COMPILER} CACHE FILEPATH + "Path to a program" FORCE) + unset(CMAKE_CXX_COMPILER) + endif (CMAKE_CXX_COMPILER) if (release) set(CMAKE_BUILD_TYPE Release) @@ -156,11 +169,17 @@ macro(setup_build_cache) set(assert false CACHE BOOL "Enables asserts, even in release builds") set(static false CACHE BOOL "On linux, link protobuf, openssl, libc++, and boost statically") + set(jemalloc false CACHE BOOL "Enables jemalloc for heap profiling") + set(perf false CACHE BOOL "Enables flags that assist with perf recording") if (static AND (WIN32 OR APPLE)) message(FATAL_ERROR "Static linking is only supported on linux.") endif() + if (perf AND (WIN32 OR APPLE)) + message(FATAL_ERROR "perf flags are only supported on linux.") + endif() + if (${CMAKE_GENERATOR} STREQUAL "Unix Makefiles" AND NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug) endif() @@ -302,6 +321,7 @@ macro(use_boost) if ((NOT DEFINED BOOST_ROOT) AND (DEFINED ENV{BOOST_ROOT})) set(BOOST_ROOT $ENV{BOOST_ROOT}) endif() + file(TO_CMAKE_PATH "${BOOST_ROOT}" BOOST_ROOT) if(WIN32 OR CYGWIN) # Workaround for MSVC having two boost versions - x86 and x64 on same PC in stage folders if(DEFINED BOOST_ROOT) @@ -368,6 +388,11 @@ macro(use_openssl openssl_min) endif() find_package(OpenSSL) + # depending on how openssl is built, it might depend + # on zlib. In fact, the openssl find package should + # figure this out for us, but it does not currently... + # so let's add zlib ourselves to the lib list + find_package(ZLIB) if (static) set(CMAKE_FIND_LIBRARY_SUFFIXES tmp) @@ -375,6 +400,7 @@ macro(use_openssl openssl_min) if (OPENSSL_FOUND) include_directories(${OPENSSL_INCLUDE_DIR}) + list(APPEND OPENSSL_LIBRARIES ${ZLIB_LIBRARIES}) else() message(FATAL_ERROR "OpenSSL not found") endif() @@ -507,6 +533,10 @@ macro(setup_build_boilerplate) endif() endif() + if (perf) + add_compile_options(-fno-omit-frame-pointer) + endif() + ############################################################ add_definitions( @@ -525,8 +555,18 @@ macro(setup_build_boilerplate) execute_process( COMMAND ${CMAKE_CXX_COMPILER} -fuse-ld=gold -Wl,--version ERROR_QUIET OUTPUT_VARIABLE LD_VERSION) - if ("${LD_VERSION}" MATCHES "GNU gold") - append_flags(CMAKE_EXE_LINKER_FLAGS -fuse-ld=gold) + # NOTE: THE gold linker inserts -rpath as DT_RUNPATH by default + # intead of DT_RPATH, so you might have slightly unexpected + # runtime ld behavior if you were expecting DT_RPATH. + # Specify --disable-new-dtags to gold if you do not want + # the default DT_RUNPATH behavior. This rpath treatment as well + # as static/dynamic selection means that gold does not currently + # have ideal default behavior when we are using jemalloc. Thus + # for simplicity we don't use it when jemalloc is requested. + # An alternative to disabling would be to figure out all the settings + # required to make gold play nicely with jemalloc. + if (("${LD_VERSION}" MATCHES "GNU gold") AND (NOT jemalloc)) + append_flags(CMAKE_EXE_LINKER_FLAGS -fuse-ld=gold) endif () unset(LD_VERSION) endif() @@ -555,6 +595,15 @@ macro(setup_build_boilerplate) STRING(REGEX REPLACE "[-/]DNDEBUG" "" CMAKE_C_FLAGS_RELEASECLASSIC "${CMAKE_C_FLAGS_RELEASECLASSIC}") endif() + if (jemalloc) + find_package(jemalloc REQUIRED) + add_definitions(-DPROFILE_JEMALLOC) + include_directories(SYSTEM ${JEMALLOC_INCLUDE_DIRS}) + link_libraries(${JEMALLOC_LIBRARIES}) + get_filename_component(JEMALLOC_LIB_PATH ${JEMALLOC_LIBRARIES} DIRECTORY) + set(CMAKE_BUILD_RPATH ${CMAKE_BUILD_RPATH} ${JEMALLOC_LIB_PATH}) + endif() + if (NOT WIN32) add_definitions(-D_FILE_OFFSET_BITS=64) append_flags(CMAKE_CXX_FLAGS -frtti -std=c++14 -Wno-invalid-offsetof @@ -585,7 +634,7 @@ macro(setup_build_boilerplate) if (APPLE) add_definitions(-DBEAST_COMPILE_OBJECTIVE_CPP=1) add_compile_options( - -Wno-deprecated -Wno-deprecated-declarations -Wno-unused-variable -Wno-unused-function) + -Wno-deprecated -Wno-deprecated-declarations -Wno-unused-function) endif() if (is_gcc) @@ -594,27 +643,27 @@ macro(setup_build_boilerplate) endif (is_gcc) else(NOT WIN32) add_compile_options( - /bigobj # Increase object file max size - /EHa # ExceptionHandling all - /fp:precise # Floating point behavior - /Gd # __cdecl calling convention - /Gm- # Minimal rebuild: disabled - /GR # Enable RTTI - /Gy- # Function level linking: disabled + /bigobj # Increase object file max size + /EHa # ExceptionHandling all + /fp:precise # Floating point behavior + /Gd # __cdecl calling convention + /Gm- # Minimal rebuild: disabled + /GR # Enable RTTI + /Gy- # Function level linking: disabled /FS - /MP # Multiprocessor compilation - /openmp- # pragma omp: disabled - /Zc:forScope # Language extension: for scope - /Zi # Generate complete debug info - /errorReport:none # No error reporting to Internet - /nologo # Suppress login banner - /W3 # Warning level 3 - /WX- # Disable warnings as errors - /wd"4018" - /wd"4244" - /wd"4267" - /wd"4800" # Disable C4800(int to bool performance) - /wd"4503" # Decorated name length exceeded, name was truncated + /MP # Multiprocessor compilation + /openmp- # pragma omp: disabled + /Zc:forScope # Language conformance: for scope + /Zi # Generate complete debug info + /errorReport:none # No error reporting to Internet + /nologo # Suppress login banner + /W3 # Warning level 3 + /WX- # Disable warnings as errors + /wd4018 # Disable signed/unsigned comparison warnings + /wd4244 # Disable float to int possible loss of data warnings + /wd4267 # Disable size_t to T possible loss of data warnings + /wd4800 # Disable C4800(int to bool performance) + /wd4503 # Decorated name length exceeded, name was truncated ) add_definitions( -D_WIN32_WINNT=0x6000 @@ -699,7 +748,7 @@ macro(link_common_libraries cur_project) find_library(app_kit AppKit) find_library(foundation Foundation) target_link_libraries(${cur_project} - crypto ssl ${app_kit} ${foundation}) + ${app_kit} ${foundation}) else() target_link_libraries(${cur_project} rt) endif() diff --git a/Builds/CMake/Findjemalloc.cmake b/Builds/CMake/Findjemalloc.cmake new file mode 100644 index 0000000000..820ceeed4a --- /dev/null +++ b/Builds/CMake/Findjemalloc.cmake @@ -0,0 +1,47 @@ +# - Try to find jemalloc +# Once done this will define +# JEMALLOC_FOUND - System has jemalloc +# JEMALLOC_INCLUDE_DIRS - The jemalloc include directories +# JEMALLOC_LIBRARIES - The libraries needed to use jemalloc + +if(NOT USE_BUNDLED_JEMALLOC) + find_package(PkgConfig) + if (PKG_CONFIG_FOUND) + pkg_check_modules(PC_JEMALLOC QUIET jemalloc) + endif() +else() + set(PC_JEMALLOC_INCLUDEDIR) + set(PC_JEMALLOC_INCLUDE_DIRS) + set(PC_JEMALLOC_LIBDIR) + set(PC_JEMALLOC_LIBRARY_DIRS) + set(LIMIT_SEARCH NO_DEFAULT_PATH) +endif() + +set(JEMALLOC_DEFINITIONS ${PC_JEMALLOC_CFLAGS_OTHER}) + +find_path(JEMALLOC_INCLUDE_DIR jemalloc/jemalloc.h + PATHS ${PC_JEMALLOC_INCLUDEDIR} ${PC_JEMALLOC_INCLUDE_DIRS} + ${LIMIT_SEARCH}) + +# If we're asked to use static linkage, add libjemalloc.a as a preferred library name. +if(JEMALLOC_USE_STATIC) + list(APPEND JEMALLOC_NAMES + "${CMAKE_STATIC_LIBRARY_PREFIX}jemalloc${CMAKE_STATIC_LIBRARY_SUFFIX}") +endif() + +list(APPEND JEMALLOC_NAMES jemalloc) + +find_library(JEMALLOC_LIBRARY NAMES ${JEMALLOC_NAMES} + HINTS ${PC_JEMALLOC_LIBDIR} ${PC_JEMALLOC_LIBRARY_DIRS} + ${LIMIT_SEARCH}) + +set(JEMALLOC_LIBRARIES ${JEMALLOC_LIBRARY}) +set(JEMALLOC_INCLUDE_DIRS ${JEMALLOC_INCLUDE_DIR}) + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set JEMALLOC_FOUND to TRUE +# if all listed variables are TRUE +find_package_handle_standard_args(JeMalloc DEFAULT_MSG + JEMALLOC_LIBRARY JEMALLOC_INCLUDE_DIR) + +mark_as_advanced(JEMALLOC_INCLUDE_DIR JEMALLOC_LIBRARY) diff --git a/Builds/Test.py b/Builds/Test.py index e17dba5681..70f53a1d58 100755 --- a/Builds/Test.py +++ b/Builds/Test.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # This file is part of rippled: https://github.com/ripple/rippled -# Copyright (c) 2012 - 2015 Ripple Labs Inc. +# Copyright (c) 2012 - 2017 Ripple Labs Inc. # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above @@ -27,6 +27,11 @@ the -- flag - like this: ./Builds/Test.py -- -j4 # Pass -j4 to scons. +To build with CMake, use the --cmake flag, or any of the specific configuration +flags + + ./Builds/Test.py --cmake -- -j4 # Pass -j4 to cmake --build + Common problems: @@ -61,6 +66,38 @@ def powerset(iterable): IS_WINDOWS = platform.system().lower() == 'windows' IS_OS_X = platform.system().lower() == 'darwin' +# CMake +if IS_WINDOWS: + CMAKE_UNITY_CONFIGS = ['Debug', 'Release'] + CMAKE_NONUNITY_CONFIGS = ['DebugClassic', 'ReleaseClassic'] +else: + CMAKE_UNITY_CONFIGS = [] + CMAKE_NONUNITY_CONFIGS = [] +CMAKE_UNITY_COMBOS = { '' : [['rippled', 'rippled_classic'], CMAKE_UNITY_CONFIGS], + '.nounity' : [['rippled', 'rippled_unity'], CMAKE_NONUNITY_CONFIGS] } + +if IS_WINDOWS: + CMAKE_DIR_TARGETS = { ('msvc' + unity,) : targets for unity, targets in + CMAKE_UNITY_COMBOS.iteritems() } +elif IS_OS_X: + CMAKE_DIR_TARGETS = { (build + unity,) : targets + for build in ['debug', 'release'] + for unity, targets in CMAKE_UNITY_COMBOS.iteritems() } +else: + CMAKE_DIR_TARGETS = { (cc + "." + build + unity,) : targets + for cc in ['gcc', 'clang'] + for build in ['debug', 'release', 'coverage', 'profile'] + for unity, targets in CMAKE_UNITY_COMBOS.iteritems() } + +# list of tuples of all possible options +if IS_WINDOWS or IS_OS_X: + CMAKE_ALL_GENERATE_OPTIONS = [tuple(x) for x in powerset(['-GNinja', '-Dassert=true'])] +else: + CMAKE_ALL_GENERATE_OPTIONS = list(set( + [tuple(x) for x in powerset(['-GNinja', '-Dstatic=true', '-Dassert=true', '-Dsan=address'])] + + [tuple(x) for x in powerset(['-GNinja', '-Dstatic=true', '-Dassert=true', '-Dsan=thread'])])) + +# Scons if IS_WINDOWS or IS_OS_X: ALL_TARGETS = [('debug',), ('release',)] else: @@ -71,7 +108,7 @@ else: # list of tuples of all possible options if IS_WINDOWS or IS_OS_X: - ALL_OPTIONS = [tuple(x) for x in powerset(['--assert'])] + ALL_OPTIONS = [tuple(x) for x in powerset(['--ninja', '--assert'])] else: ALL_OPTIONS = list(set( [tuple(x) for x in powerset(['--ninja', '--static', '--assert', '--sanitize=address'])] + @@ -129,14 +166,76 @@ parser.add_argument( help='Reduce output where possible (unit tests)', ) +# Scons and CMake parameters are too different to run +# both side-by-side +pgroup = parser.add_mutually_exclusive_group() + +pgroup.add_argument( + '--cmake', + action='store_true', + help='Build using CMake.', +) + +pgroup.add_argument( + '--scons', + action='store_true', + help='Build using Scons. Default behavior.') + parser.add_argument( - 'scons_args', + '--dir', '-d', default=(), - nargs='*' + nargs='*', + help='Specify one or more CMake dir names. Implies --cmake. ' + 'Will also be used as -Dtarget= running cmake.' +) + +parser.add_argument( + '--target', + default=(), + nargs='*', + help='Specify one or more CMake build targets. Implies --cmake. ' + 'Will be used as --target running cmake --build.' + ) + +parser.add_argument( + '--config', + default=(), + nargs='*', + help='Specify one or more CMake build configs. Implies --cmake. ' + 'Will be used as --config running cmake --build.' + ) + +parser.add_argument( + '--generator_option', + action='append', + help='Specify a CMake generator option. Repeat for multiple options. ' + 'Implies --cmake. Will be passed to the cmake generator. ' + 'Due to limits of the argument parser, arguments starting with \'-\' ' + 'must be attached to this option. e.g. --generator_option=-GNinja.') + +parser.add_argument( + '--build_option', + action='append', + help='Specify a build option. Repeat for multiple options. Implies --cmake. ' + 'Will be passed to the build tool via cmake --build. ' + 'Due to limits of the argument parser, arguments starting with \'-\' ' + 'must be attached to this option. e.g. --build_option=-j8.') + +parser.add_argument( + 'extra_args', + default=(), + nargs='*', + help='Extra arguments are passed through to the tools' ) ARGS = parser.parse_args() +def decodeString(line): + # Python 2 vs. Python 3 + if isinstance(line, str): + return line + else: + return line.decode() def shell(cmd, args=(), silent=False): """"Execute a shell command and return the output.""" @@ -147,6 +246,7 @@ def shell(cmd, args=(), silent=False): command = (cmd,) + args + # shell is needed in Windows to find scons in the path process = subprocess.Popen( command, stdin=subprocess.PIPE, @@ -155,12 +255,9 @@ def shell(cmd, args=(), silent=False): shell=IS_WINDOWS) lines = [] count = 0 - for line in process.stdout: - # Python 2 vs. Python 3 - if isinstance(line, str): - decoded = line - else: - decoded = line.decode() + # readline returns '' at EOF + for line in iter(process.stdout.readline, ''): + decoded = decodeString(line) lines.append(decoded) if verbose: print(decoded, end='') @@ -227,45 +324,163 @@ def run_build(args=None): print(*lines, sep='') sys.exit(1) +def get_cmake_dir(cmake_dir): + return os.path.join('build' , 'cmake' , cmake_dir) + +def run_cmake(directory, cmake_dir, args): + print('Generating build in', directory, 'with', *args or ('default options',)) + old_dir = os.getcwd() + if not os.path.exists(directory): + os.makedirs(directory) + os.chdir(directory) + if IS_WINDOWS and not any(arg.startswith("-G") for arg in args) and not os.path.exists("CMakeCache.txt"): + if '--ninja' in args: + args += ( '-GNinja', ) + else: + args += ( '-GVisual Studio 14 2015 Win64', ) + args += ( '-Dtarget=' + cmake_dir, os.path.join('..', '..', '..'), ) + resultcode, lines = shell('cmake', args) + + if resultcode: + print('Generating FAILED:') + if not ARGS.verbose: + print(*lines, sep='') + sys.exit(1) + + os.chdir(old_dir) + +def run_cmake_build(directory, target, config, args): + print('Building', target, config, 'in', directory, 'with', *args or ('default options',)) + build_args=('--build', directory) + if target: + build_args += ('--target', target) + if config: + build_args += ('--config', config) + if args: + build_args += ('--',) + build_args += tuple(args) + resultcode, lines = shell('cmake', build_args) + + if resultcode: + print('Build FAILED:') + if not ARGS.verbose: + print(*lines, sep='') + sys.exit(1) + +def run_cmake_tests(directory, target, config): + failed = [] + if IS_WINDOWS: + target += '.exe' + executable = os.path.join(directory, config if config else 'Debug', target) + if(not os.path.exists(executable)): + executable = os.path.join(directory, target) + print('Unit tests for', executable) + testflag = '--unittest' + quiet = '' + if ARGS.test: + testflag += ('=' + ARGS.test) + if ARGS.quiet: + quiet = '-q' + resultcode, lines = shell(executable, (testflag, quiet,)) + + if resultcode: + if not ARGS.verbose: + print('ERROR:', *lines, sep='') + failed.append([target, 'unittest']) + + return failed def main(): - if ARGS.all: - to_build = ALL_BUILDS - else: - to_build = [tuple(ARGS.scons_args)] - all_failed = [] - for build in to_build: - args = () - # additional arguments come first - for arg in list(ARGS.scons_args): - if arg not in build: - args += (arg,) - args += build + if ARGS.dir or ARGS.target or ARGS.config or ARGS.build_option or ARGS.generator_option: + ARGS.cmake=True - run_build(args) - failed = run_tests(args) - - if failed: - print('FAILED:', *(':'.join(f) for f in failed)) - if not ARGS.keep_going: - sys.exit(1) - else: - all_failed.extend([','.join(build), ':'.join(f)] - for f in failed) + if not ARGS.cmake: + if ARGS.all: + to_build = ALL_BUILDS else: - print('Success') + to_build = [tuple(ARGS.extra_args)] - if ARGS.clean: - shutil.rmtree('build') - if '--ninja' in args: - os.remove('build.ninja') - os.remove('.ninja_deps') - os.remove('.ninja_log') + for build in to_build: + args = () + # additional arguments come first + for arg in list(ARGS.extra_args): + if arg not in build: + args += (arg,) + args += build + + run_build(args) + failed = run_tests(args) + + if failed: + print('FAILED:', *(':'.join(f) for f in failed)) + if not ARGS.keep_going: + sys.exit(1) + else: + all_failed.extend([','.join(build), ':'.join(f)] + for f in failed) + else: + print('Success') + + if ARGS.clean: + shutil.rmtree('build') + if '--ninja' in args: + os.remove('build.ninja') + os.remove('.ninja_deps') + os.remove('.ninja_log') + else: + if ARGS.all: + build_dir_targets = CMAKE_DIR_TARGETS + generator_options = CMAKE_ALL_GENERATE_OPTIONS + else: + build_dir_targets = { tuple(ARGS.dir) : [ARGS.target, ARGS.config] } + if ARGS.generator_option: + generator_options = [tuple(ARGS.generator_option)] + else: + generator_options = [tuple()] + + if not build_dir_targets: + # Let CMake choose the build tool. + build_dir_targets = { () : [] } + + if ARGS.build_option: + ARGS.build_option = ARGS.build_option + list(ARGS.extra_args) + else: + ARGS.build_option = list(ARGS.extra_args) + + for args in generator_options: + for build_dirs, (build_targets, build_configs) in build_dir_targets.iteritems(): + if not build_dirs: + build_dirs = ('default',) + if not build_targets: + build_targets = ('rippled',) + if not build_configs: + build_configs = ('',) + for cmake_dir in build_dirs: + cmake_full_dir = get_cmake_dir(cmake_dir) + run_cmake(cmake_full_dir, cmake_dir, args) + + for target in build_targets: + for config in build_configs: + run_cmake_build(cmake_full_dir, target, config, ARGS.build_option) + failed = run_cmake_tests(cmake_full_dir, target, config) + + if failed: + print('FAILED:', *(':'.join(f) for f in failed)) + if not ARGS.keep_going: + sys.exit(1) + else: + all_failed.extend([decodeString(cmake_dir + + "." + target + "." + config), ':'.join(f)] + for f in failed) + else: + print('Success') + if ARGS.clean: + shutil.rmtree(cmake_full_dir) if all_failed: - if len(to_build) > 1: + if len(all_failed) > 1: print() print('FAILED:', *(':'.join(f) for f in all_failed)) sys.exit(1) diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj index 20dd9bceb7..d73f5dc2c3 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj +++ b/Builds/VisualStudio2015/RippleD.vcxproj @@ -266,13 +266,15 @@ - + + + - + @@ -280,155 +282,183 @@ - - - - - + - + - + + + - + - + - + - + - - - - + + + + + + + + + + - - - - + + + + + + - + + + + + - + - + - + + + + + + + - + - + - + + + - + - + - - - + - + - + - + - - - - - - - + + + + + + + + + - + - + - + + + + + + + - - - + + + + + + + - - - - - + - - - + + + + + + + + + + + - - - - - - + + - + @@ -446,6 +476,8 @@ + + @@ -835,6 +867,12 @@ + + True + True + + + @@ -1061,6 +1099,10 @@ True True + + True + True + True True @@ -1093,11 +1135,7 @@ - - True - True - - + @@ -1431,10 +1469,6 @@ True True - - True - True - True True @@ -1495,6 +1529,8 @@ + + @@ -1793,8 +1829,6 @@ True True - - @@ -1827,18 +1861,26 @@ - - - - - - - + True True + + + + + + + + + + + + + + @@ -1847,8 +1889,6 @@ - - True True @@ -1861,12 +1901,6 @@ ..\..\src\soci\src\core;..\..\src\sqlite;%(AdditionalIncludeDirectories) ..\..\src\soci\src\core;..\..\src\sqlite;%(AdditionalIncludeDirectories) - - True - True - ..\..\src\soci\src\core;..\..\src\sqlite;%(AdditionalIncludeDirectories) - ..\..\src\soci\src\core;..\..\src\sqlite;%(AdditionalIncludeDirectories) - True True @@ -1941,8 +1975,6 @@ - - @@ -2085,6 +2117,10 @@ True True + + True + True + True True @@ -3157,7 +3193,15 @@ True True - + + True + True + + + True + True + + True True @@ -3165,6 +3209,10 @@ True True + + True + True + True True @@ -3221,7 +3269,11 @@ ..\..\src\rocksdb2\include;..\..\src\snappy\config;..\..\src\snappy\snappy;%(AdditionalIncludeDirectories) ..\..\src\rocksdb2\include;..\..\src\snappy\config;..\..\src\snappy\snappy;%(AdditionalIncludeDirectories) - + + True + True + + True True @@ -3247,7 +3299,11 @@ - + + True + True + + True True @@ -4291,6 +4347,10 @@ True True + + True + True + True True @@ -4343,6 +4403,10 @@ True True + + True + True + True True @@ -4375,10 +4439,6 @@ True True - - True - True - True True @@ -4423,6 +4483,14 @@ True True + + True + True + + + True + True + True True @@ -4435,11 +4503,7 @@ True True - - True - True - - + True True @@ -4895,6 +4959,10 @@ True True + + True + True + True True @@ -4943,6 +5011,10 @@ True True + + True + True + True True @@ -4997,7 +5069,11 @@ True True - + + True + True + + True True @@ -5005,7 +5081,11 @@ True True - + + True + True + + True True @@ -5029,7 +5109,11 @@ True True - + + True + True + + True True @@ -5063,6 +5147,10 @@ True True + + True + True + True True diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters index 03fc1ae808..326781e37c 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters @@ -558,16 +558,19 @@ beast - + beast\core beast\core + + beast\core + beast\core - + beast\core @@ -579,130 +582,145 @@ beast\core\detail - - beast\core\detail - - - beast\core\detail - - + beast\core\detail beast\core\detail - + beast\core\detail - + + beast\core\detail + + beast\core\detail beast\core\detail - + beast\core\detail - + beast\core\detail beast\core\detail - + beast\core\detail - + beast\core\detail beast\core\detail - - beast\core\detail - - + beast\core - + + beast\core + + + beast\core + + + beast\core + + + beast\core + + beast\core beast\core - - beast\core - - - beast\core - beast\core + + beast\core\impl + + + beast\core\impl + + + beast\core\impl + beast\core\impl - + + beast\core\impl + + + beast\core\impl + + beast\core\impl beast\core\impl - + beast\core\impl - + beast\core\impl - + + beast\core\impl + + + beast\core\impl + + + beast\core\impl + + beast\core - + beast\core - + beast\core - + + beast\core + + beast\core beast\core - + beast\core - + beast\core - - beast\core - - + beast\core beast - + beast\http - + beast\http - - beast\http - - - beast\http - - - beast\http - - + beast\http\detail - + beast\http\detail @@ -711,28 +729,46 @@ beast\http\detail + + beast\http\detail + + + beast\http + beast\http + + beast\http + + + beast\http + beast\http - + beast\http - + beast\http\impl - + + beast\http\impl + + + beast\http\impl + + + beast\http\impl + + beast\http\impl beast\http\impl - - beast\http\impl - - + beast\http\impl @@ -741,36 +777,51 @@ beast\http\impl + + beast\http\impl + + + beast\http\impl + + + beast\http\impl + beast\http\impl beast\http - - beast\http - - - beast\http - - + beast\http beast\http - - beast\http - beast\http - + + beast\http + + + beast\http + + beast\http beast\http + + beast\http + + + beast\http + + + beast\http + beast\http @@ -780,28 +831,22 @@ beast - - beast\websocket\detail - - - beast\websocket\detail - beast\websocket\detail beast\websocket\detail - + beast\websocket\detail - + beast\websocket\detail beast\websocket\detail - + beast\websocket\detail @@ -828,6 +873,9 @@ beast\websocket\impl + + beast\websocket\impl + beast\websocket\impl @@ -1353,6 +1401,12 @@ ripple\app\consensus + + ripple\app\consensus + + + ripple\app\consensus + ripple\app\ledger @@ -1581,6 +1635,9 @@ ripple\app\misc\impl + + ripple\app\misc\impl + ripple\app\misc\impl @@ -1617,10 +1674,7 @@ ripple\app\misc - - ripple\app\misc - - + ripple\app\misc @@ -1959,9 +2013,6 @@ ripple\basics\impl - - ripple\basics\impl - ripple\basics\impl @@ -2034,6 +2085,9 @@ ripple\basics + + ripple\basics + ripple\basics @@ -2412,9 +2466,6 @@ ripple\beast\utility\src - - ripple\beast\utility - ripple\beast\utility @@ -2454,21 +2505,33 @@ ripple\conditions\impl + + ripple\consensus + ripple\consensus + + ripple\consensus + ripple\consensus + + ripple\consensus + ripple\consensus - - ripple\consensus - ripple\consensus + + ripple\consensus + + + ripple\core + ripple\core @@ -2481,18 +2544,12 @@ ripple\core - - ripple\core - ripple\core\impl ripple\core\impl - - ripple\core\impl - ripple\core\impl @@ -2538,9 +2595,6 @@ ripple\core - - ripple\core - ripple\core @@ -2706,6 +2760,9 @@ ripple\ledger\impl + + ripple\ledger\impl + ripple\ledger\impl @@ -3762,12 +3819,21 @@ ripple\unity - + + ripple\unity + + + ripple\unity + + ripple\unity ripple\unity + + ripple\unity + ripple\unity @@ -3810,7 +3876,10 @@ ripple\unity - + + ripple\unity + + ripple\unity @@ -3834,7 +3903,10 @@ ripple\unity - + + ripple\unity + + ripple\unity @@ -5070,6 +5142,9 @@ test\app + + test\app + test\app @@ -5109,6 +5184,9 @@ test\basics + + test\basics + test\beast @@ -5133,9 +5211,6 @@ test\beast - - test\beast - test\beast @@ -5169,6 +5244,12 @@ test\consensus + + test\consensus + + + test\core + test\core @@ -5178,10 +5259,7 @@ test\core - - test\core - - + test\core @@ -5592,6 +5670,9 @@ test\rpc + + test\rpc + test\rpc @@ -5628,6 +5709,9 @@ test\rpc + + test\rpc + test\rpc @@ -5670,13 +5754,19 @@ test\shamap - + + test\unity + + test\unity test\unity - + + test\unity + + test\unity @@ -5694,7 +5784,10 @@ test\unity - + + test\unity + + test\unity @@ -5718,6 +5811,9 @@ test\unity + + test\unity + test\unity diff --git a/Builds/XCode/README.md b/Builds/XCode/README.md index 821b4f3750..698fe2187c 100644 --- a/Builds/XCode/README.md +++ b/Builds/XCode/README.md @@ -2,13 +2,13 @@ ## Important -We don't recommend OS X for rippled production use at this time. Currently, the +We don't recommend macos for rippled production use at this time. Currently, the Ubuntu platform has received the highest level of quality assurance and -testing. +testing. That said, macos is suitable for many development/test tasks. ## Prerequisites -You'll need OSX 10.8 or later +You'll need macos 10.8 or later To clone the source code repository, create branches for inspection or modification, build rippled using clang, and run the system tests you will need @@ -17,7 +17,7 @@ these software components: * [XCode](https://developer.apple.com/xcode/) * [Homebrew](http://brew.sh/) * [Git](http://git-scm.com/) -* [Scons](http://www.scons.org/) +* [CMake](http://cmake.org/) ## Install Software @@ -64,14 +64,14 @@ later. ### Install Scons -Requires version 2.3.0 or later +Requires version 3.6.0 or later ``` -brew install scons +brew install cmake ``` `brew` will generally install the latest stable version of any package, which -will satisfy the scons minimum version requirement for rippled. +should satisfy the cmake minimum version requirement for rippled. ### Install Package Config @@ -163,10 +163,16 @@ export BOOST_ROOT=/Users/Abigail/Downloads/boost_1_61_0 ## Build ``` -scons +mkdir xcode_build && cd xcode_build +cmake -GXcode .. ``` -See: [here](https://ripple.com/wiki/Rippled_build_instructions#Building) +There are a number of variables/options that our CMake files support and they +can be added to the above command as needed (e.g. `-Dassert=ON` to enable +asserts) + +After generation succeeds, the xcode project file can be opened and used to +build and debug. ## Unit Tests (Recommended) diff --git a/Builds/build_all.sh b/Builds/build_all.sh old mode 100644 new mode 100755 index 52beed007f..e1de6c192d --- a/Builds/build_all.sh +++ b/Builds/build_all.sh @@ -2,5 +2,7 @@ num_procs=$(lscpu -p | grep -v '^#' | sort -u -t, -k 2,4 | wc -l) # number of physical cores -cd .. -./Builds/Test.py -a -c -- -j${num_procs} +path=$(cd $(dirname $0) && pwd) +cd $(dirname $path) +${path}/Test.py -a -c --test=TxQ -- -j${num_procs} +${path}/Test.py -a -c -k --test=TxQ --cmake -- -j${num_procs} diff --git a/CMakeLists.txt b/CMakeLists.txt index 407fbf89e1..7d648978bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,24 +15,30 @@ # * ninja builds # * check openssl version on linux # * static builds (swd TBD: needs to be tested by building & deploying on different systems) -# -# TBD: -# * jemalloc support -# * count -# * Windows protobuf compiler puts generated file in src directory instead of build directory. +# * jemalloc enabled builds (linux and macos only) +# * perf builds (linux only) - which just sets recommended compiler flags +# for running perf on the executable # # Notes: -# * Use the -G"Visual Studio 14 2015 Win64" generator on Windows. Without this -# a 32-bit project will be created. There is no way to set the generator or -# force a 64-bit build in CMakeLists.txt (setting CMAKE_GENERATOR_PLATFORM won't work). -# The best solution may be to wrap cmake with a script. +# * Use the -G"Visual Studio 14 2015 Win64" generator, or the "VS2015 x86 x64 +# Cross Tools" Command Prompt on Windows. Without this a 32-bit project will be +# created. There is no way to set the generator or force a 64-bit build in +# CMakeLists.txt (setting CMAKE_GENERATOR_PLATFORM won't work). The best solution +# may be to wrap cmake with a script. +# +# * Ninja command line builds seem to work under Windows, but only from within +# the "VS2015 x86 x64 Cross Tools" Command Prompt. # # * It is not possible to generate a visual studio project on linux or # mac. The visual studio generator is only available on windows. # -# * The visual studio project can be _either_ unity or -# non-unity (selected at generation time). It does not appear possible -# to disable compilation based on configuration. +# * The Visual Studio solution will be generated with two projects, one +# unity, one non-unity. Which is default depends on the nounity flag in +# -Dtarget. Unity targets will create `rippled` and `rippled_classic`. +# Non-unity targets will create `rippled` and `rippled_unity`. In either +# case, only the `rippled` build will be enabled by default. It does +# not appear possible to include both unity and non-unity configs in one +# project and disable compilation based on configuration. # # * Language is _much_ worse than python, poor documentation and "quirky" # language support (for example, generator expressions can only be used @@ -181,8 +187,11 @@ prepend(ripple_unity_srcs src/ripple/unity/ app_consensus.cpp app_ledger.cpp -app_main.cpp +app_ledger_impl.cpp +app_main1.cpp +app_main2.cpp app_misc.cpp +app_misc_impl.cpp app_paths.cpp app_tx.cpp conditions.cpp @@ -192,19 +201,23 @@ basics.cpp crypto.cpp ledger.cpp net.cpp -overlay.cpp +overlay1.cpp +overlay2.cpp peerfinder.cpp json.cpp protocol.cpp -rpcx.cpp +rpcx1.cpp +rpcx2.cpp shamap.cpp server.cpp) prepend(test_unity_srcs src/test/unity/ -app_test_unity.cpp +app_test_unity1.cpp +app_test_unity2.cpp basics_test_unity.cpp -beast_test_unity.cpp +beast_test_unity1.cpp +beast_test_unity2.cpp conditions_test_unity.cpp consensus_test_unity.cpp core_test_unity.cpp @@ -216,8 +229,10 @@ protocol_test_unity.cpp resource_test_unity.cpp rpc_test_unity.cpp server_test_unity.cpp +server_status_test_unity.cpp shamap_test_unity.cpp -jtx_unity.cpp +jtx_unity1.cpp +jtx_unity2.cpp csf_unity.cpp) list(APPEND rippled_src_unity ${beast_unity_srcs} ${ripple_unity_srcs} ${test_unity_srcs}) @@ -294,8 +309,11 @@ foreach(curdir basics beast conditions + consensus core + csf json + jtx ledger nodestore overlay @@ -304,9 +322,7 @@ foreach(curdir resource rpc server - shamap - jtx - csf) + shamap) file(GLOB_RECURSE cursrcs src/test/${curdir}/*.cpp) list(APPEND test_srcs "${cursrcs}") endforeach() diff --git a/README.md b/README.md index ad1c18b513..27ed68bbbe 100644 --- a/README.md +++ b/README.md @@ -78,8 +78,8 @@ git-subtree. See the corresponding README for more details. * [Ripple Knowledge Center](https://ripple.com/learn/) * [Ripple Developer Center](https://ripple.com/build/) -* [Ripple Whitepapers & Reports](https://ripple.com/whitepapers-reports/) - * [Ripple Consensus Whitepaper](https://ripple.com/consensus-whitepaper/) +* Ripple Whitepapers & Reports + * [Ripple Consensus Whitepaper](https://ripple.com/files/ripple_consensus_whitepaper.pdf) * [Ripple Solutions Guide](https://ripple.com/files/ripple_solutions_guide.pdf) To learn about how Ripple is transforming global payments visit diff --git a/SConstruct b/SConstruct index 6b30788bfa..1d104ec38a 100644 --- a/SConstruct +++ b/SConstruct @@ -976,7 +976,7 @@ def get_classic_sources(toolchain): append_sources(result, *list_sources('src/test/server', '.cpp')) append_sources(result, *list_sources('src/test/shamap', '.cpp')) append_sources(result, *list_sources('src/test/jtx', '.cpp')) - append_sources(result, *list_sources('src/test/csf', '.cpp')) + append_sources(result, *list_sources('src/test/csf', '.cpp')) if use_shp(toolchain): @@ -1008,8 +1008,11 @@ def get_unity_sources(toolchain): 'src/ripple/beast/unity/beast_utility_unity.cpp', 'src/ripple/unity/app_consensus.cpp', 'src/ripple/unity/app_ledger.cpp', - 'src/ripple/unity/app_main.cpp', + 'src/ripple/unity/app_ledger_impl.cpp', + 'src/ripple/unity/app_main1.cpp', + 'src/ripple/unity/app_main2.cpp', 'src/ripple/unity/app_misc.cpp', + 'src/ripple/unity/app_misc_impl.cpp', 'src/ripple/unity/app_paths.cpp', 'src/ripple/unity/app_tx.cpp', 'src/ripple/unity/conditions.cpp', @@ -1019,16 +1022,20 @@ def get_unity_sources(toolchain): 'src/ripple/unity/crypto.cpp', 'src/ripple/unity/ledger.cpp', 'src/ripple/unity/net.cpp', - 'src/ripple/unity/overlay.cpp', + 'src/ripple/unity/overlay1.cpp', + 'src/ripple/unity/overlay2.cpp', 'src/ripple/unity/peerfinder.cpp', 'src/ripple/unity/json.cpp', 'src/ripple/unity/protocol.cpp', - 'src/ripple/unity/rpcx.cpp', + 'src/ripple/unity/rpcx1.cpp', + 'src/ripple/unity/rpcx2.cpp', 'src/ripple/unity/shamap.cpp', 'src/ripple/unity/server.cpp', - 'src/test/unity/app_test_unity.cpp', + 'src/test/unity/app_test_unity1.cpp', + 'src/test/unity/app_test_unity2.cpp', 'src/test/unity/basics_test_unity.cpp', - 'src/test/unity/beast_test_unity.cpp', + 'src/test/unity/beast_test_unity1.cpp', + 'src/test/unity/beast_test_unity2.cpp', 'src/test/unity/consensus_test_unity.cpp', 'src/test/unity/core_test_unity.cpp', 'src/test/unity/conditions_test_unity.cpp', @@ -1040,8 +1047,10 @@ def get_unity_sources(toolchain): 'src/test/unity/resource_test_unity.cpp', 'src/test/unity/rpc_test_unity.cpp', 'src/test/unity/server_test_unity.cpp', + 'src/test/unity/server_status_test_unity.cpp', 'src/test/unity/shamap_test_unity.cpp', - 'src/test/unity/jtx_unity.cpp', + 'src/test/unity/jtx_unity1.cpp', + 'src/test/unity/jtx_unity2.cpp', 'src/test/unity/csf_unity.cpp' ) diff --git a/bin/ci/ubuntu/build-and-test.sh b/bin/ci/ubuntu/build-and-test.sh index 2015b00372..03f84008ea 100755 --- a/bin/ci/ubuntu/build-and-test.sh +++ b/bin/ci/ubuntu/build-and-test.sh @@ -10,6 +10,12 @@ echo "using TARGET: $TARGET" # Ensure APP defaults to rippled if it's not set. : ${APP:=rippled} + +JOBS=${NUM_PROCESSORS:-2} +if [[ ${TARGET} == *.nounity ]]; then + JOBS=$((2*${JOBS})) +fi + if [[ ${BUILD:-scons} == "cmake" ]]; then echo "cmake building ${APP}" CMAKE_TARGET=$CC.$TARGET @@ -19,7 +25,7 @@ if [[ ${BUILD:-scons} == "cmake" ]]; then mkdir -p "build/${CMAKE_TARGET}" pushd "build/${CMAKE_TARGET}" cmake ../.. -Dtarget=$CMAKE_TARGET - cmake --build . -- -j${NUM_PROCESSORS:-2} + cmake --build . -- -j${JOBS} popd export APP_PATH="$PWD/build/${CMAKE_TARGET}/${APP}" echo "using APP_PATH: $APP_PATH" @@ -33,7 +39,7 @@ else # $CC will be either `clang` or `gcc` # http://docs.travis-ci.com/user/migrating-from-legacy/?utm_source=legacy-notice&utm_medium=banner&utm_campaign=legacy-upgrade # indicates that 2 cores are available to containers. - scons -j${NUM_PROCESSORS:-2} $CC.$TARGET + scons -j${JOBS} $CC.$TARGET fi # We can be sure we're using the build/$CC.$TARGET variant # (-f so never err) @@ -62,15 +68,19 @@ if [[ $TARGET == "coverage" ]]; then lcov --no-external -c -i -d . -o baseline.info fi -# Execute unit tests under gdb, printing a call stack -# if we get a crash. -gdb -return-child-result -quiet -batch \ - -ex "set env MALLOC_CHECK_=3" \ - -ex "set print thread-events off" \ - -ex run \ - -ex "thread apply all backtrace full" \ - -ex "quit" \ - --args $APP_PATH $APP_ARGS +if [[ ${TARGET} == debug ]]; then + # Execute unit tests under gdb, printing a call stack + # if we get a crash. + $GDB_ROOT/bin/gdb -return-child-result -quiet -batch \ + -ex "set env MALLOC_CHECK_=3" \ + -ex "set print thread-events off" \ + -ex run \ + -ex "thread apply all backtrace full" \ + -ex "quit" \ + --args $APP_PATH $APP_ARGS +else + $APP_PATH $APP_ARGS +fi if [[ $TARGET == "coverage" ]]; then # Create test coverage data file @@ -87,6 +97,8 @@ if [[ $TARGET == "coverage" ]]; then # Push the results (lcov.info) to codecov codecov -X gcov # don't even try and look for .gcov files ;) + + find . -name "*.gcda" | xargs rm -f fi diff --git a/bin/ci/ubuntu/install-dependencies.sh b/bin/ci/ubuntu/install-dependencies.sh index 5257eaa68b..2bcbcca6e5 100755 --- a/bin/ci/ubuntu/install-dependencies.sh +++ b/bin/ci/ubuntu/install-dependencies.sh @@ -8,7 +8,7 @@ TWD=$( cd ${TWD:-${1:-${PWD:-$( pwd )}}}; pwd ) echo "Target path is: $TWD" # Override gcc version to $GCC_VER. # Put an appropriate symlink at the front of the path. -mkdir -v $HOME/bin +mkdir -pv $HOME/bin for g in gcc g++ gcov gcc-ar gcc-nm gcc-ranlib do test -x $( type -p ${g}-$GCC_VER ) @@ -66,3 +66,17 @@ tar xfvz lcov-1.12.tar.gz -C $HOME # Set install path mkdir -p $LCOV_ROOT cd $HOME/lcov-1.12 && make install PREFIX=$LCOV_ROOT + + +if [[ ${TARGET} == debug && ! -x ${GDB_ROOT}/bin/gdb ]]; then + pushd $HOME + #install gdb + wget https://ftp.gnu.org/gnu/gdb/gdb-8.0.tar.xz + tar xf gdb-8.0.tar.xz + pushd gdb-8.0 + ./configure CFLAGS='-w -O2' CXXFLAGS='-std=gnu++11 -g -O2 -w' --prefix=$GDB_ROOT + make -j2 + make install + popd + popd +fi diff --git a/bin/sh/install-boost.sh b/bin/sh/install-boost.sh index 1f609c53ba..8a49771e92 100644 --- a/bin/sh/install-boost.sh +++ b/bin/sh/install-boost.sh @@ -16,7 +16,7 @@ then tar xzf /tmp/boost.tar.gz cd $BOOST_ROOT && \ ./bootstrap.sh --prefix=$BOOST_ROOT && \ - ./b2 -d1 define=_GLIBCXX_USE_CXX11_ABI=0 -j${NUM_PROCESSORS:-2} &&\ + ./b2 -d1 define=_GLIBCXX_USE_CXX11_ABI=0 -j$((2*${NUM_PROCESSORS:-2})) &&\ ./b2 -d0 define=_GLIBCXX_USE_CXX11_ABI=0 install else echo "Using cached boost at $BOOST_ROOT" diff --git a/circle.yml b/circle.yml index 2080004d0b..67ba0a0ba2 100644 --- a/circle.yml +++ b/circle.yml @@ -8,7 +8,7 @@ dependencies: - sudo apt-get update -qq - sudo apt-get purge -qq libboost1.48-dev - sudo apt-get install -qq libboost1.60-all-dev - - sudo apt-get install -qq clang-3.6 gcc-5 g++-5 libobjc-5-dev libgcc-5-dev libstdc++-5-dev libclang1-3.6 libgcc1 libgomp1 libstdc++6 scons protobuf-compiler libprotobuf-dev libssl-dev exuberant-ctags + - sudo apt-get install -qq clang-3.6 gcc-5 g++-5 libobjc-5-dev libgcc-5-dev libstdc++-5-dev libclang1-3.6 libgcc1 libgomp1 libstdc++6 scons protobuf-compiler libprotobuf-dev libssl-dev exuberant-ctags texinfo - lsb_release -a - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-5 99 - sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-5 99 @@ -16,6 +16,11 @@ dependencies: - gcc --version - clang --version - clang++ --version + - if [[ ! -e gdb-8.0 ]]; then wget https://ftp.gnu.org/gnu/gdb/gdb-8.0.tar.xz && tar xf gdb-8.0.tar.xz && cd gdb-8.0 && ./configure && make && cd ..; fi + - pushd gdb-8.0 && sudo make install && popd + - gdb --version + cache_directories: + - gdb-8.0 test: pre: - scons clang.debug diff --git a/doc/rippled-example.cfg b/doc/rippled-example.cfg index 5eeb418dd1..47ce2f8654 100644 --- a/doc/rippled-example.cfg +++ b/doc/rippled-example.cfg @@ -172,6 +172,13 @@ # NOTE If no ports support the peer protocol, rippled cannot # receive incoming peer connections or become a superpeer. # +# limit = +# +# Optional. An integer value that will limit the number of connected +# clients that the port will accept. Once the limit is reached, new +# connections will be refused until other clients disconnect. +# Omit or set to 0 to allow unlimited numbers of clients. +# # user = # password = # diff --git a/docs/consensus.qbk b/docs/consensus.qbk index 8077f97bea..832413201c 100644 --- a/docs/consensus.qbk +++ b/docs/consensus.qbk @@ -480,69 +480,87 @@ struct Ledger //... implementation specific }; ``` -[heading Generic Consensus Interface] - -Following the -[@https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern CRTP] -idiom, generic =Consensus= relies on a deriving class implementing a set of -helpers and callbacks that encapsulate implementation specific details of the -algorithm. Below are excerpts of the generic consensus implementation and of -helper types that will interact with the concrete implementing class. +[heading PeerProposal] The =PeerProposal= type represents the signed position taken +by a peer during consensus. The only type requirement is owning an instance of a +generic =ConsensusProposal=. ``` - // Represents our proposed position or a peer's proposed position +// and is provided with the generic code template class ConsensusProposal; +struct PeerPosition +{ + ConsensusProposal< + NodeID_t, + typename Ledger::ID, + typename TxSet::ID> const & + proposal() const; + + // ... implementation specific +}; +``` +[heading Generic Consensus Interface] + +The generic =Consensus= relies on =Adaptor= template class to implement a set +of helper functions that plug the consensus algorithm into a specific application. +The =Adaptor= class also defines the types above needed by the algorithm. Below +are excerpts of the generic consensus implementation and of helper types that will +interact with the concrete implementing class. + +``` // Represents a transction under dispute this round template class DisputedTx; -template class Consensus +// Represents how the node participates in Consensus this round +enum class ConsensusMode { proposing, observing, wrongLedger, switchedLedger}; + +// Measure duration of phases of consensus +class ConsensusTimer { -protected: - enum class Mode { proposing, observing, wrongLedger, switchedLedger}; - - // Measure duration of phases of consensus - class Stopwatch - { - public: - std::chrono::milliseconds read() const; - // details omitted ... - }; - - // Initial ledger close times, not rounded by closeTimeResolution - // Used to gauge degree of synchronization between a node and its peers - struct CloseTimes - { - std::map peers; - NetClock::time_point self; - }; - - // Encapsulates the result of consensus. - struct Result - { - //! The set of transactions consensus agrees go in the ledger - TxSet_t set; - - //! Our proposed position on transactions/close time - Proposal_t position; - - //! Transactions which are under dispute with our peers - using Dispute_t = DisputedTx; - hash_map disputes; - - // Set of TxSet ids we have already compared/created disputes - hash_set compares; - - // Measures the duration of the establish phase for this consensus round - Stopwatch roundTime; - - // Indicates state in which consensus ended. Once in the accept phase - // will be either Yes or MovedOn - ConsensusState state = ConsensusState::No; - }; - public: + std::chrono::milliseconds read() const; + // details omitted ... +}; + +// Initial ledger close times, not rounded by closeTimeResolution +// Used to gauge degree of synchronization between a node and its peers +struct ConsensusCloseTimes +{ + std::map peers; + NetClock::time_point self; +}; + +// Encapsulates the result of consensus. +template +struct ConsensusResult +{ + //! The set of transactions consensus agrees go in the ledger + Adaptor::TxSet_t set; + + //! Our proposed position on transactions/close time + ConsensusProposal<...> position; + + //! Transactions which are under dispute with our peers + hash_map> disputes; + + // Set of TxSet ids we have already compared/created disputes + hash_set compares; + + // Measures the duration of the establish phase for this consensus round + ConsensusTimer roundTime; + + // Indicates state in which consensus ended. Once in the accept phase + // will be either Yes or MovedOn + ConsensusState state = ConsensusState::No; +}; + +template +class Consensus +{ +public: + Consensus(clock_type, Adaptor &, beast::journal); + // Kick-off the next round of consensus. void startRound( NetClock::time_point const& now, @@ -568,26 +586,20 @@ public: The stub below shows the set of callback/helper functions required in the implementing class. ``` -struct Traits +struct Adaptor { - using Ledger_t = Ledger; - using TxSet_t = TxSet; - using NodeID_t = ...; // Integer-like std::uint32_t to uniquely identify a node + using Ledger_t = Ledger; + using TxSet_t = TxSet; + using PeerProposal_t = PeerProposal; + using NodeID_t = ...; // Integer-like std::uint32_t to uniquely identify a node -}; -class ConsensusImp : public Consensus -{ // Attempt to acquire a specific ledger from the network. boost::optional acquireLedger(Ledger::ID const & ledgerID); // Acquire the transaction set associated with a proposed position. boost::optional acquireTxSet(TxSet::ID const & setID); - // Get peers' proposed positions. Returns an iterable - // with value_type convertable to ConsensusPosition<...> - auto const & proposals(Ledger::ID const & ledgerID); - // Whether any transactions are in the open ledger bool hasOpenTransactions() const; @@ -602,24 +614,27 @@ class ConsensusImp : public Consensus // application thinks consensus should use as the prior ledger. Ledger::ID getPrevLedger(Ledger::ID const & prevLedgerID, Ledger const & prevLedger, - Mode mode); + ConsensusMode mode); + // Called when consensus operating mode changes + void onModeChange(ConsensuMode before, ConsensusMode after); + // Called when ledger closes. Implementation should generate an initial Result // with position based on the current open ledger's transactions. - Result onClose(Ledger const &, Ledger const & prev, Mode mode); + ConsensusResult onClose(Ledger const &, Ledger const & prev, ConsensusMode mode); // Called when ledger is accepted by consensus - void onAccept(Result const & result, + void onAccept(ConsensusResult const & result, RCLCxLedger const & prevLedger, NetClock::duration closeResolution, - CloseTimes const & rawCloseTimes, - Mode const & mode); + ConsensusCloseTimes const & rawCloseTimes, + ConsensusMode const & mode); // Propose the position to peers. void propose(ConsensusProposal<...> const & pos); // Relay a received peer proposal on to other peer's. - void relay(ConsensusProposal<...> const & pos); + void relay(PeerPosition_t const & pos); // Relay a disputed transaction to peers void relay(TxSet::Tx const & tx); diff --git a/docs/source.dox b/docs/source.dox index 7475222c9b..d0d82d7a86 100644 --- a/docs/source.dox +++ b/docs/source.dox @@ -111,8 +111,11 @@ INPUT = \ ../src/test/jtx/WSClient.h \ ../src/ripple/consensus/Consensus.h \ ../src/ripple/consensus/ConsensusProposal.h \ + ../src/ripple/consensus/ConsensusTypes.h \ ../src/ripple/consensus/DisputedTx.h \ ../src/ripple/consensus/LedgerTiming.h \ + ../src/ripple/consensus/Validations.h \ + ../src/ripple/consensus/ConsensusParms.h \ ../src/ripple/app/consensus/RCLCxTx.h \ ../src/ripple/app/consensus/RCLCxLedger.h \ ../src/ripple/app/consensus/RCLConsensus.h \ @@ -120,6 +123,7 @@ INPUT = \ ../src/ripple/app/tx/apply.h \ ../src/ripple/app/tx/applySteps.h \ ../src/ripple/app/tx/impl/InvariantCheck.h \ + ../src/ripple/app/consensus/RCLValidations.h \ INPUT_ENCODING = UTF-8 FILE_PATTERNS = diff --git a/src/beast/.github/ISSUE_TEMPLATE.md b/src/beast/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000000..b342a0d7ac --- /dev/null +++ b/src/beast/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,23 @@ +PLEASE DON'T FORGET TO "STAR" THIS REPOSITORY :) + +When reporting a bug please include the following: + +### Version of Beast + +You can find the version number in +or using the command "git log -1". + +### Steps necessary to reproduce the problem + +A small compiling program is the best. If your code is +public, you can provide a link to the repository. + +### All relevant compiler information + +If you are unable to compile please include the type and +version of compiler you are using as well as all compiler +output including the error message, file, and line numbers +involved. + +The more information you provide the sooner your issue +can get resolved! diff --git a/src/beast/.gitignore b/src/beast/.gitignore index 99f984bdaa..1740b535d7 100644 --- a/src/beast/.gitignore +++ b/src/beast/.gitignore @@ -1,2 +1,7 @@ bin/ bin64/ + +# Because of CMake and VS2017 +Win32/ +x64/ + diff --git a/src/beast/.travis.yml b/src/beast/.travis.yml index 236e8da718..6a6ab527f1 100644 --- a/src/beast/.travis.yml +++ b/src/beast/.travis.yml @@ -11,17 +11,15 @@ env: # to boost's .tar.gz. - LCOV_ROOT=$HOME/lcov - VALGRIND_ROOT=$HOME/valgrind-install - - BOOST_ROOT=$HOME/boost_1_61_0 - - BOOST_URL='http://sourceforge.net/projects/boost/files/boost/1.61.0/boost_1_61_0.tar.gz' + - BOOST_ROOT=$HOME/boost_1_58_0 + - BOOST_URL='http://sourceforge.net/projects/boost/files/boost/1.58.0/boost_1_58_0.tar.gz' addons: apt: - sources: ['ubuntu-toolchain-r-test'] - packages: - - gcc-5 - - g++-5 + sources: &base_sources + - ubuntu-toolchain-r-test + packages: &base_packages - python-software-properties - - libssl-dev - libffi-dev - libstdc++6 - binutils-gold @@ -35,35 +33,81 @@ addons: matrix: include: - # GCC/Coverage/Autobahn (if master or develop branch) + # gcc coverage + - compiler: gcc + env: + - GCC_VER=6 + - VARIANT=coverage + - ADDRESS_MODEL=64 + - DO_VALGRIND=false + - BUILD_SYSTEM=cmake + - PATH=$PWD/cmake/bin:$PATH + addons: + apt: + packages: + - gcc-6 + - g++-6 + - libssl-dev + - *base_packages + sources: + - *base_sources + + # older GCC, release + - compiler: gcc + env: + - GCC_VER=4.8 + - VARIANT=release + - DO_VALGRIND=false + - ADDRESS_MODEL=64 + addons: + apt: + packages: + - gcc-4.8 + - g++-4.8 + - *base_packages + sources: + - *base_sources + + # later GCC - compiler: gcc env: - GCC_VER=5 - - VARIANT=coverage + - VARIANT=release + - DO_VALGRIND=true - ADDRESS_MODEL=64 - BUILD_SYSTEM=cmake - PATH=$PWD/cmake/bin:$PATH + addons: + apt: + packages: + - gcc-5 + - g++-5 + - libssl-dev + - *base_packages + sources: + - *base_sources - # Clang/UndefinedBehaviourSanitizer + # clang ubsan+asan - compiler: clang env: - GCC_VER=5 - - VARIANT=usan + - VARIANT=ubasan - CLANG_VER=3.8 + - DO_VALGRIND=false - ADDRESS_MODEL=64 - UBSAN_OPTIONS='print_stacktrace=1' - BUILD_SYSTEM=cmake - PATH=$PWD/cmake/bin:$PATH - PATH=$PWD/llvm-$LLVM_VERSION/bin:$PATH - - # Clang/AddressSanitizer - - compiler: clang - env: - - GCC_VER=5 - - VARIANT=asan - - CLANG_VER=3.8 - - ADDRESS_MODEL=64 - - PATH=$PWD/llvm-$LLVM_VERSION/bin:$PATH + addons: + apt: + packages: + - gcc-5 + - g++-5 + - libssl-dev + - *base_packages + sources: + - *base_sources cache: directories: @@ -72,7 +116,7 @@ cache: - llvm-$LLVM_VERSION - cmake -before_install: +before_install: &base_before_install - scripts/install-dependencies.sh script: diff --git a/src/beast/CHANGELOG.md b/src/beast/CHANGELOG.md index 2dcb1650c7..803c8014ec 100644 --- a/src/beast/CHANGELOG.md +++ b/src/beast/CHANGELOG.md @@ -1,3 +1,829 @@ +Version 79: + +* Remove spurious fallthrough guidance + +-------------------------------------------------------------------------------- + +Version 78: + +* Add span +* Documentation work +* Use make_unique_noinit +* Fix warning in zlib +* Header file tidying +* Tidy up FieldsReader doc +* Add Boost.Locale utf8 benchmark comparison +* Tidy up dstream for existing Boost versions +* Tidy up file_posix unused variable +* Fix warning in root ca declaration + +HTTP: + +* Tidy up basic_string_body +* Add vector_body +* span, string, vector bodies are public +* Fix spurious uninitialized warning +* fields temp string uses allocator + +API Changes: + +* Add message::keep_alive() +* Add message::chunked() and message::content_length() +* Remove string_view_body + +Actions Required: + +* Change user defined implementations of Fields and + FieldsReader to meet the new requirements. + +* Use span_body instead of string_view_body + +-------------------------------------------------------------------------------- + +Version 77: + +* file_posix works without large file support + +-------------------------------------------------------------------------------- + +Version 76: + +* Always go through write_some +* Use Boost.Config +* BodyReader may construct from a non-const message +* Add serializer::get +* Add serializer::chunked +* Serializer members are not const +* serializing file_body is not const +* Add file_body_win32 +* Fix parse illegal characters in obs-fold +* Disable SSE4.2 optimizations + +API Changes: + +* Rename to serializer::keep_alive +* BodyReader, BodyWriter use two-phase init + +Actions Required: + +* Use serializer::keep_alive instead of serializer::close and + take the logical NOT of the return value. + +* Modify instances of user-defined BodyReader and BodyWriter + types to perfrom two-phase initialization, as per the + updated documented type requirements. + +-------------------------------------------------------------------------------- + +Version 75: + +* Use file_body for valid requests, string_body otherwise. +* Construct buffer_prefix_view in-place +* Shrink serializer buffers using buffers_ref +* Tidy up BEAST_NO_BIG_VARIANTS +* Shrink serializer buffers using buffers_ref +* Add serializer::limit +* file_body tests +* Using SSE4.2 intrinsics in basic_parser if available + +-------------------------------------------------------------------------------- + +Version 74: + +* Add file_stdio and File concept +* Add file_win32 +* Add file_body +* Remove common/file_body.hpp +* Add file_posix +* Fix Beast include directories for cmake targets +* remove redundant flush() from example + +-------------------------------------------------------------------------------- + +Version 73: + +* Jamroot tweak +* Verify certificates in SSL clients +* Adjust benchmarks +* Initialize local variable in basic_parser +* Fixes for gcc-4.8 + +HTTP: + +* basic_parser optimizations +* Add basic_parser tests + +API Changes: + +* Refactor header and message constructors +* serializer::next replaces serializer::get + +Actions Required: + +* Evaluate each message constructor call site and + adjust the constructor argument list as needed. + +* Use serializer::next instead of serializer::get at call sites + +-------------------------------------------------------------------------------- + +Version 72: + +HTTP: + +* Tidy up set payload in http-server-fast +* Refine Body::size specification +* Newly constructed responses have a 200 OK result +* Refactor file_body for best practices +* Add http-server-threaded example +* Documentation tidying +* Various improvements to http_server_fast.cpp + +WebSocket: + +* Add websocket-server-async example + + +-------------------------------------------------------------------------------- + +Version 71: + +* Fix extra ; warning +* Documentation revision +* Fix spurious on_chunk invocation +* Call prepare_payload in HTTP example +* Check trailers in test +* Fix buffer overflow handling for string_body and mutable_body +* Concept check in basic_dynamic_body +* Tidy up http_sync_port error check +* Tidy up Jamroot /permissive- + +WebSockets: + +* Fine tune websocket op asserts +* Refactor websocket composed ops +* Allow close, ping, and write to happen concurrently +* Fix race in websocket read op +* Fix websocket write op +* Add cmake options for examples and tests + +API Changes: + +* Return `std::size_t` from `Body::writer::put` + +Actions Required: + +* Return the number of bytes actually transferred from the + input buffers in user defined `Body::writer::put` functions. + +-------------------------------------------------------------------------------- + +Version 70: + +* Serialize in one step when possible +* Add basic_parser header and body limits +* Add parser::on_header to set a callback +* Fix BEAST_FALLTHROUGH +* Fix HEAD response in file_service + +API Changes: + +* Rename to message::base +* basic_parser default limits are now 1MB/8MB + +Actions Required: + +* Change calls to message::header_part() with message::base() + +* Call body_limit and/or header_limit as needed to adjust the + limits to suitable values if the defaults are insufficient. + +-------------------------------------------------------------------------------- + +Version 69: + +* basic_parser optimizations +* Use BEAST_FALLTHROUGH to silence warnings +* Add /permissive- to msvc toolchain + +-------------------------------------------------------------------------------- + +Version 68: + +* Split common tests to a new project +* Small speed up in fields comparisons +* Adjust buffer size in fast server +* Use string_ref in older Boost versions +* Optimize field lookups +* Add const_body, mutable_body to examples +* Link statically on cmake MSVC + +API Changes: + +* Change BodyReader, BodyWriter requirements +* Remove BodyReader::is_deferred +* http::error::bad_target replaces bad_path + +Actions Required: + +* Change user defined instances of BodyReader and BodyWriter + to meet the new requirements. + +* Replace references to http::error::bad_path with http::error::bad_target + +-------------------------------------------------------------------------------- + +Version 67: + +* Fix doc example link +* Add http-server-small example +* Merge stream_base to stream and tidy +* Use boost::string_view +* Rename to http-server-fast +* Appveyor use Boost 1.64.0 +* Group common example headers + +API Changes: + +* control_callback replaces ping_callback + +Actions Required: + +* Change calls to websocket::stream::ping_callback to use + websocket::stream::control_callback + +* Change user defined ping callbacks to have the new + signature and adjust the callback definition appropriately. + +-------------------------------------------------------------------------------- + +Version 66: + +* string_param optimizations +* Add serializer request/response aliases +* Make consuming_buffers smaller +* Fix costly potential value-init in parser +* Fix unused parameter warning +* Handle bad_alloc in parser +* Tidy up message piecewise ctors +* Add header aliases +* basic_fields optimizations +* Add http-server example +* Squelch spurious warning on gcc + +-------------------------------------------------------------------------------- + +Version 65: + +* Enable narrowing warning on msvc cmake +* Fix integer types in deflate_stream::bi_reverse +* Fix narrowing in static_ostream +* Fix narrowing in ostream +* Fix narrowing in inflate_stream +* Fix narrowing in deflate_stream +* Fix integer warnings +* Enable unused variable warning on msvc cmake + +-------------------------------------------------------------------------------- + +Version 64: + +* Simplify buffered_read_stream composed op +* Simplify ssl teardown composed op +* Simplify websocket write_op +* Exemplars are compiled code +* Better User-Agent in examples +* async_write requires a non-const message +* Doc tidying +* Add link_directories to cmake + +API Changes: + +* Remove make_serializer + +Actions Required: + +* Replace calls to make_serializer with variable declarations + +-------------------------------------------------------------------------------- + +Version 63: + +* Use std::to_string instead of lexical_cast +* Don't use cached Boost +* Put num_jobs back up on Travis +* Only build and run tests in variant=coverage +* Move benchmarks to a separate project +* Only run the tests under ubasan +* Tidy up CMakeLists.txt +* Tidy up Jamfiles +* Control running with valgrind explicitly + +-------------------------------------------------------------------------------- + +Version 62: + +* Remove libssl-dev from a Travis matrix item +* Increase detail::static_ostream coverage +* Add server-framework tests +* Doc fixes and tidy +* Tidy up namespaces in examples +* Clear the error faster +* Avoid explicit operator bool for error +* Add http::is_fields trait +* Squelch harmless not_connected errors +* Put slow tests back for coverage builds + +API Changes: + +* parser requires basic_fields +* Refine FieldsReader concept +* message::prepare_payload replaces message::prepare + +Actions Required: + +* Callers using `parser` with Fields types other than basic_fields + will need to create their own subclass of basic_parser to work + with their custom fields type. + +* Implement chunked() and keep_alive() for user defined FieldsReader types. + +* Change calls to msg.prepare to msg.prepare_payload. For messages + with a user-defined Fields, provide the function prepare_payload_impl + in the fields type according to the Fields requirements. + +-------------------------------------------------------------------------------- + +Version 61: + +* Remove Spirit dependency +* Use generic_cateogry for errno +* Reorganize SSL examples +* Tidy up some integer conversion warnings +* Add message::header_part() +* Tidy up names in error categories +* Flush the output stream in the example +* Clean close in Secure WebSocket client +* Add server-framework SSL HTTP and WebSocket ports +* Fix shadowing warnings +* Tidy up http-crawl example +* Add multi_port to server-framework +* Tidy up resolver calls +* Use one job on CI +* Don't run slow tests on certain targets + +API Changes: + +* header::version is unsigned +* status-codes is unsigned + +-------------------------------------------------------------------------------- + +Version 60: + +* String comparisons are public interfaces +* Fix response message type in async websocket accept +* New server-framework, full featured server example + +-------------------------------------------------------------------------------- + +Version 59: + +* Integrated Beast INTERFACE (cmake) +* Fix base64 alphabet +* Remove obsolete doc/README.md + +API Changes: + +* Change Body::size signature (API Change): + +Actions Required: + +* For any user-defined models of Body, change the function signature + to accept `value_type const&` and modify the function definition + accordingly. + +-------------------------------------------------------------------------------- + +Version 58: + +* Fix unaligned reads in utf8-checker +* Qualify size_t in message template +* Reorganize examples +* Specification for http read +* Avoid `std::string` in websocket +* Fix basic_fields insert ordering +* basic_fields::set optimization +* basic_parser::put doc +* Use static string in basic_fields::reader +* Remove redundant code +* Fix parsing chunk size with leading zeroes +* Better message formal parameter names + +API Changes: + +* `basic_fields::set` renamed from `basic_fields::replace` + +Actions Required: + +* Rename calls to `basic_fields::replace` to `basic_fields::set` + +-------------------------------------------------------------------------------- + +Version 57: + +* Fix message.hpp javadocs +* Fix warning in basic_parser.cpp +* Integrate docca for documentation and tidy + +-------------------------------------------------------------------------------- + +Version 56: + +* Add provisional IANA header field names +* Add string_view_body +* Call on_chunk when the extension is empty +* HTTP/1.1 is the default version +* Try harder to find Boost (cmake) +* Reset error codes +* More basic_parser tests +* Add an INTERFACE cmake target +* Convert buffer in range loops + +-------------------------------------------------------------------------------- + +Version 55: + +* Don't allocate memory to handle obs-fold +* Avoid a parser allocation using non-flat buffer +* read_size replaces read_size_helper + +-------------------------------------------------------------------------------- + +Version 54: + +* static_buffer coverage +* flat_buffer coverage +* multi_buffer coverage +* consuming_buffers members and coverage +* basic_fields members and coverage +* Add string_param +* Retain ownership when reading using a message +* Fix incorrect use of [[fallthrough]] + +API Changes: + +* basic_fields refactor + +-------------------------------------------------------------------------------- + +Version 53: + +* Fix basic_parser::maybe_flatten +* Fix read_size_helper usage + +-------------------------------------------------------------------------------- + +Version 52: + +* flat_buffer is an AllocatorAwareContainer +* Add drain_buffer class + +API Changes: + +* `auto_fragment` is a member of `stream` +* `binary`, `text` are members of `stream` +* read_buffer_size is a member of `stream` +* read_message_max is a member of `stream` +* `write_buffer_size` is a member of `stream` +* `ping_callback` is a member of stream +* Remove `opcode` from `read`, `async_read` +* `read_frame` returns `bool` fin +* `opcode` is private +* finish(error_code&) is a BodyReader requirement + +Actions Required: + +* Change call sites which use `auto_fragment` with `set_option` + to call `stream::auto_fragment` instead. + +* Change call sites which use message_type with `set_option` + to call `stream::binary` or `stream::text` instead. + +* Change call sites which use `read_buffer_size` with `set_option` to + call `stream::read_buffer_size` instead. + +* Change call sites which use `read_message_max` with `set_option` to + call `stream::read_message_max` instead. + +* Change call sites which use `write_buffer_size` with `set_option` to + call `stream::write_buffer_size` instead. + +* Change call sites which use `ping_callback1 with `set_option` to + call `stream::ping_callback` instead. + +* Remove the `opcode` reference parameter from calls to synchronous + and asynchronous read functions, replace the logic with calls to + `stream::got_binary` and `stream::got_text` instead. + +* Remove the `frame_info` parameter from all read frame call sites + +* Check the return value 'fin' for calls to `read_frame` + +* Change ReadHandlers passed to `async_read_frame` to have + the signature `void(error_code, bool fin)`, use the `bool` + to indicate if the frame is the last frame. + +* Remove all occurrences of the `opcode` enum at call sites + +-------------------------------------------------------------------------------- + +Version 51 + +* Fix operator<< for header +* Tidy up file_body +* Fix file_body::get() not setting the more flag correctly +* Use BOOST_FALLTHROUGH +* Use BOOST_STRINGIZE +* DynamicBuffer benchmarks +* Add construct, destroy to handler_alloc +* Fix infinite loop in basic_parser + +API Changes: + +* Tune up static_buffer +* multi_buffer implementation change + +Actions Required: + +* Call sites passing a number to multi_buffer's constructor + will need to be adjusted, see the corresponding commit message. + +-------------------------------------------------------------------------------- + +Version 50 + +* parser is constructible from other body types +* Add field enumeration +* Use allocator more in basic_fields +* Fix basic_fields allocator awareness +* Use field in basic_fields and call sites +* Use field in basic_parser +* Tidy up basic_fields, header, and field concepts +* Fields concept work +* Body documentation work +* Add missing handler_alloc nested types +* Fix chunk delimiter parsing +* Fix test::pipe read_size +* Fix chunk header parsing + +API Changes: + +* Remove header_parser +* Add verb to on_request for parsers +* Refactor prepare +* Protect basic_fields special members +* Remove message connection settings +* Remove message free functions +* Remove obsolete serializer allocator +* http read_some, async_read_some don't return bytes + +-------------------------------------------------------------------------------- + +Version 49 + +* Use instead of + +HTTP: + +* Add HEAD request example + +API Changes: + +* Refactor method and verb +* Canonicalize string_view parameter types +* Tidy up empty_body writer error +* Refactor header status, reason, and target + +-------------------------------------------------------------------------------- + +Version 48 + +* Make buffer_prefix_view public +* Remove detail::sync_ostream +* Tidy up core type traits + +API Changes: + +* Tidy up chunk decorator +* Rename to buffer_cat_view +* Consolidate parsers to parser.hpp +* Rename to parser + +-------------------------------------------------------------------------------- + +Version 47 + +* Disable operator<< for buffer_body +* buffer_size overload for basic_multi_buffer::const_buffers_type +* Fix undefined behavior in pausation +* Fix leak in basic_flat_buffer + +API Changes: + +* Refactor treatment of request-method +* Refactor treatment of status code and obsolete reason +* Refactor HTTP serialization and parsing + +-------------------------------------------------------------------------------- + +Version 46 + +* Add test::pipe +* Documentation work + +API Changes: + +* Remove HTTP header aliases +* Refactor HTTP serialization +* Refactor type traits + +-------------------------------------------------------------------------------- + +Version 45 + +* Workaround for boost::asio::basic_streambuf type check +* Fix message doc image +* Better test::enable_yield_to +* Fix header::reason +* Documentation work +* buffer_view skips empty buffer sequences +* Disable reverse_iterator buffer_view test + +-------------------------------------------------------------------------------- + +Version 44 + +* Use BOOST_THROW_EXCEPTION +* Tidy up read_size_helper and dynamic buffers +* Require Boost 1.58.0 or later +* Tidy up and make get_lowest_layer public +* Use BOOST_STATIC_ASSERT +* Fix async return values in docs +* Fix README websocket example +* Add buffers_adapter regression test +* Tidy up is_dynamic_buffer traits test +* Make buffers_adapter meet requirements + +-------------------------------------------------------------------------------- + +Version 43 + +* Require Boost 1.64.0 +* Fix strict aliasing warnings in buffers_view +* Tidy up buffer_prefix overloads and test +* Add write limit to test::string_ostream +* Additional constructors for consuming_buffers + +-------------------------------------------------------------------------------- + +Version 42 + +* Fix javadoc typo +* Add formal review notes +* Make buffers_view a public interface + +-------------------------------------------------------------------------------- + +Version 41 + +* Trim Appveyor matrix rows +* Concept revision and documentation +* Remove coveralls integration +* Tidy up formal parameter names + +WebSocket + +* Tidy up websocket::close_code enum and constructors + +API Changes + +* Return http::error::end_of_stream on HTTP read eof +* Remove placeholders +* Rename prepare_buffer(s) to buffer_prefix +* Remove handler helpers, tidy up hook invocations + +-------------------------------------------------------------------------------- + +Version 40 + +* Add to_static_string +* Consolidate get_lowest_layer in type_traits.hpp +* Fix basic_streambuf movable trait +* Tidy up .travis.yml + +-------------------------------------------------------------------------------- + +Version 39 + +Beast versions are now identified by a single integer which +is incremented on each merge. The macro BEAST_VERSION +identifies the version number, currently at 39. A version +setting commit will always be at the tip of the master +and develop branches. + +* Use beast::string_view alias +* Fixed braced-init error with older gcc + +HTTP + +* Tidy up basic_parser javadocs + +WebSocket: + +* Add websocket async echo ssl server test: +* Fix eof error on ssl::stream shutdown + +API Changes: + +* Refactor http::header contents +* New ostream() returns dynamic buffer output stream +* New buffers() replaces to_string() +* Rename to multi_buffer, basic_multi_buffer +* Rename to flat_buffer, basic_flat_buffer +* Rename to static_buffer, static_buffer_n +* Rename to buffered_read_stream +* Harmonize concepts and identifiers with net-ts +* Tidy up HTTP reason_string + +-------------------------------------------------------------------------------- + +1.0.0-b38 + +* Refactor static_string +* Refactor base64 +* Use static_string for WebSocket handshakes +* Simplify get_lowest_layer test +* Add test_allocator to extras/test +* More flat_streambuf tests +* WebSocket doc work +* Prevent basic_fields operator[] assignment + +API Changes: + +* Refactor WebSocket error codes +* Remove websocket::keep_alive option + +-------------------------------------------------------------------------------- + +1.0.0-b37 + +* CMake hide command lines in .vcxproj Output windows" +* Rename to detail::is_invocable +* Rename project to http-bench +* Fix flat_streambuf +* Add ub sanitizer blacklist +* Add -funsigned-char to asan build target +* Fix narrowing warning in table constants + +WebSocket: + +* Add is_upgrade() free function +* Document websocket::stream thread safety +* Rename to websocket::detail::pausation + +API Changes: + +* Provide websocket::stream accept() overloads +* Refactor websocket decorators +* Move everything in basic_fields.hpp to fields.hpp +* Rename to http::dynamic_body, consolidate header + +-------------------------------------------------------------------------------- + +1.0.0-b36 + +* Update README.md + +-------------------------------------------------------------------------------- + +1.0.0-b35 + +* Add Appveyor build scripts and badge +* Tidy up MSVC CMake configuration +* Make close_code a proper enum +* Add flat_streambuf +* Rename to BEAST_DOXYGEN +* Update .gitignore for VS2017 +* Fix README.md CMake instructions + +API Changes: + +* New HTTP interfaces +* Remove http::empty_body + +-------------------------------------------------------------------------------- + 1.0.0-b34 * Fix and tidy up CMake build scripts diff --git a/src/beast/CMakeLists.txt b/src/beast/CMakeLists.txt index c82014589f..fa2cee17eb 100644 --- a/src/beast/CMakeLists.txt +++ b/src/beast/CMakeLists.txt @@ -2,19 +2,26 @@ cmake_minimum_required (VERSION 3.5.2) -project (Beast) +project (Beast VERSION 79) set_property (GLOBAL PROPERTY USE_FOLDERS ON) +option (Beast_BUILD_EXAMPLES "Build examples" ON) +option (Beast_BUILD_TESTS "Build tests" ON) if (MSVC) - # /wd4244 /wd4127 + set (CMAKE_VERBOSE_MAKEFILE FALSE) + add_definitions (-D_WIN32_WINNT=0x0601) add_definitions (-D_SCL_SECURE_NO_WARNINGS=1) add_definitions (-D_CRT_SECURE_NO_WARNINGS=1) - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4100 /wd4244 /wd4251 /MP /W4 /bigobj") - set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Ob2 /Oi /Ot /GL") - set (CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /Oi /Ot") + set (Boost_USE_STATIC_LIBS ON) + set (Boost_USE_STATIC_RUNTIME ON) + + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /W4 /bigobj /permissive-") + set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd") + set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Ob2 /Oi /Ot /GL /MT") + set (CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /Oi /Ot /MT") set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /SAFESEH:NO") set (CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG") @@ -28,11 +35,15 @@ if (MSVC) set (CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO ${replacement_flags}) else() - set(THREADS_PREFER_PTHREAD_FLAG ON) - find_package(Threads) + set (THREADS_PREFER_PTHREAD_FLAG ON) + find_package (Threads) - set(CMAKE_CXX_FLAGS + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Wpedantic -Wno-unused-parameter") + + if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wrange-loop-analysis") + endif () endif() #------------------------------------------------------------------------------- @@ -42,31 +53,18 @@ endif() option (Boost_USE_STATIC_LIBS "Use static libraries for boost" ON) -set (Boost_NO_SYSTEM_PATHS ON) -set (Boost_USE_MULTITHREADED ON) +set(BOOST_COMPONENTS system) +if (Beast_BUILD_EXAMPLES OR Beast_BUILD_TESTS) + list(APPEND BOOST_COMPONENTS coroutine context filesystem program_options thread) +endif() +find_package (Boost 1.58.0 REQUIRED COMPONENTS ${BOOST_COMPONENTS}) -unset (Boost_INCLUDE_DIR CACHE) -unset (Boost_LIBRARY_DIRS CACHE) -find_package (Boost REQUIRED COMPONENTS - coroutine - context - filesystem - program_options - system - thread - ) +link_directories(${Boost_LIBRARY_DIRS}) -include_directories (SYSTEM ${Boost_INCLUDE_DIRS}) -link_libraries (${Boost_LIBRARIES}) - -if (MSVC) - add_definitions (-DBOOST_ALL_NO_LIB) # disable autolinking -elseif (MINGW) +if (MINGW) link_libraries(ws2_32 mswsock) endif() -add_definitions (-DBOOST_COROUTINES_NO_DEPRECATION_WARNING=1) # for asio - #------------------------------------------------------------------------------- # # OpenSSL @@ -83,15 +81,23 @@ endif() find_package(OpenSSL) +if (OPENSSL_FOUND) + add_definitions (-DBEAST_USE_OPENSSL=1) + +else() + add_definitions (-DBEAST_USE_OPENSSL=0) + message("OpenSSL not found.") +endif() + # #------------------------------------------------------------------------------- function(DoGroupSources curdir rootdir folder) - file(GLOB children RELATIVE ${PROJECT_SOURCE_DIR}/${curdir} ${PROJECT_SOURCE_DIR}/${curdir}/*) - foreach(child ${children}) - if(IS_DIRECTORY ${PROJECT_SOURCE_DIR}/${curdir}/${child}) + file (GLOB children RELATIVE ${PROJECT_SOURCE_DIR}/${curdir} ${PROJECT_SOURCE_DIR}/${curdir}/*) + foreach (child ${children}) + if (IS_DIRECTORY ${PROJECT_SOURCE_DIR}/${curdir}/${child}) DoGroupSources(${curdir}/${child} ${rootdir} ${folder}) - elseif(${child} STREQUAL "CMakeLists.txt") + elseif (${child} STREQUAL "CMakeLists.txt") source_group("" FILES ${PROJECT_SOURCE_DIR}/${curdir}/${child}) else() string(REGEX REPLACE ^${rootdir} ${folder} groupname ${curdir}) @@ -102,77 +108,87 @@ function(DoGroupSources curdir rootdir folder) endfunction() function(GroupSources curdir folder) - DoGroupSources(${curdir} ${curdir} ${folder}) + DoGroupSources (${curdir} ${curdir} ${folder}) endfunction() #------------------------------------------------------------------------------- if ("${VARIANT}" STREQUAL "coverage") - set(CMAKE_CXX_FLAGS - "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage") - set(CMAKE_BUILD_TYPE RELWITHDEBINFO) - set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lgcov") -elseif ("${VARIANT}" STREQUAL "asan") - set(CMAKE_CXX_FLAGS - "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") - set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address") - set(CMAKE_BUILD_TYPE RELWITHDEBINFO) -elseif ("${VARIANT}" STREQUAL "usan") - set(CMAKE_CXX_FLAGS - "${CMAKE_CXX_FLAGS} -fsanitize=undefined -fno-omit-frame-pointer") - set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined") - set(CMAKE_BUILD_TYPE RELWITHDEBINFO) + if (MSVC) + else() + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse4.2 -fprofile-arcs -ftest-coverage") + set (CMAKE_BUILD_TYPE RELWITHDEBINFO) + set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lgcov") + endif() + +elseif ("${VARIANT}" STREQUAL "ubasan") + if (MSVC) + else() + set (CMAKE_CXX_FLAGS + "${CMAKE_CXX_FLAGS} -DBEAST_NO_SLOW_TESTS=1 -msse4.2 -funsigned-char -fno-omit-frame-pointer -fsanitize=address,undefined -fsanitize-blacklist=${PROJECT_SOURCE_DIR}/scripts/blacklist.supp") + set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address,undefined") + set (CMAKE_BUILD_TYPE RELWITHDEBINFO) + endif() + elseif ("${VARIANT}" STREQUAL "debug") - set(CMAKE_BUILD_TYPE DEBUG) + set (CMAKE_BUILD_TYPE DEBUG) + elseif ("${VARIANT}" STREQUAL "release") - set(CMAKE_BUILD_TYPE RELEASE) + set (CMAKE_BUILD_TYPE RELEASE) + endif() +#------------------------------------------------------------------------------- +# +# Library interface +# + +add_library (${PROJECT_NAME} INTERFACE) +target_link_libraries (${PROJECT_NAME} INTERFACE ${Boost_SYSTEM_LIBRARY}) +if (NOT MSVC) + target_link_libraries (${PROJECT_NAME} INTERFACE Threads::Threads) +endif() +target_compile_definitions (${PROJECT_NAME} INTERFACE BOOST_COROUTINES_NO_DEPRECATION_WARNING=1) +target_include_directories(${PROJECT_NAME} INTERFACE ${PROJECT_SOURCE_DIR}/include) +target_include_directories(${PROJECT_NAME} SYSTEM INTERFACE ${Boost_INCLUDE_DIRS}) + +#------------------------------------------------------------------------------- +# +# Tests and examples +# + +include_directories (.) include_directories (extras) include_directories (include) -set(ZLIB_SOURCES - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/crc32.h - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/deflate.h - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/inffast.h - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/inffixed.h - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/inflate.h - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/inftrees.h - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/trees.h - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/zlib.h - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/zutil.h - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/adler32.c - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/compress.c - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/crc32.c - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/deflate.c - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/infback.c - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/inffast.c - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/inflate.c - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/inftrees.c - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/trees.c - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/uncompr.c - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/zutil.c -) +if (OPENSSL_FOUND) + include_directories (${OPENSSL_INCLUDE_DIR}) +endif() file(GLOB_RECURSE BEAST_INCLUDES ${PROJECT_SOURCE_DIR}/include/beast/*.hpp ${PROJECT_SOURCE_DIR}/include/beast/*.ipp ) +file(GLOB_RECURSE COMMON_INCLUDES + ${PROJECT_SOURCE_DIR}/example/common/*.hpp + ) + +file(GLOB_RECURSE EXAMPLE_INCLUDES + ${PROJECT_SOURCE_DIR}/example/*.hpp + ) + file(GLOB_RECURSE EXTRAS_INCLUDES ${PROJECT_SOURCE_DIR}/extras/beast/*.hpp ${PROJECT_SOURCE_DIR}/extras/beast/*.ipp ) -add_subdirectory (examples) -if (NOT OPENSSL_FOUND) - message("OpenSSL not found. Not building examples/ssl") -else() - add_subdirectory (examples/ssl) +if (Beast_BUILD_TESTS) + add_subdirectory (test) endif() -add_subdirectory (test) -add_subdirectory (test/core) -add_subdirectory (test/http) -add_subdirectory (test/websocket) -add_subdirectory (test/zlib) +if (Beast_BUILD_EXAMPLES AND + (NOT "${VARIANT}" STREQUAL "coverage") AND + (NOT "${VARIANT}" STREQUAL "ubasan")) + add_subdirectory (example) +endif() diff --git a/src/beast/Jamroot b/src/beast/Jamroot index 277100ab23..a35ffbb5de 100644 --- a/src/beast/Jamroot +++ b/src/beast/Jamroot @@ -8,6 +8,8 @@ import os ; import feature ; import boost ; +import modules ; +import testing ; boost.use-project ; @@ -50,40 +52,24 @@ if [ os.name ] = MACOSX using clang : : ; } -variant coverage - : - debug - : - "-fprofile-arcs -ftest-coverage" +variant coverage : + release + : + "-msse4.2 -fprofile-arcs -ftest-coverage" "-lgcov" - ; + ; -variant asan +variant ubasan : release : - "-fsanitize=address -fno-omit-frame-pointer" - "-fsanitize=address" - ; - -variant msan - : - debug - : - "-fsanitize=memory -fno-omit-frame-pointer -fsanitize-memory-track-origins=2 -fsanitize-memory-use-after-dtor" - "-fsanitize=memory" - ; - -variant usan - : - debug - : - "-fsanitize=undefined -fno-omit-frame-pointer" - "-fsanitize=undefined" + "-msse4.2 -funsigned-char -fno-omit-frame-pointer -fsanitize=address,undefined -fsanitize-blacklist=scripts/blacklist.supp" + "-fsanitize=address,undefined" ; project beast : requirements + /boost//headers . ./extras ./include @@ -103,9 +89,10 @@ project beast clang:-std=c++11 clang:-Wno-unused-parameter clang:-Wno-unused-variable # Temporary until we can figure out -isystem + clang:-Wrange-loop-analysis msvc:_SCL_SECURE_NO_WARNINGS=1 msvc:_CRT_SECURE_NO_WARNINGS=1 - msvc:"/wd4100 /wd4251 /bigobj" + msvc:"/permissive- /bigobj" msvc:release:"/Ob2 /Oi /Ot" LINUX:_XOPEN_SOURCE=600 LINUX:_GNU_SOURCE=1 @@ -125,4 +112,4 @@ project beast ; build-project test ; -build-project examples ; +build-project example ; diff --git a/src/beast/README.md b/src/beast/README.md index 1dcf4b52a1..9b854e8a6c 100644 --- a/src/beast/README.md +++ b/src/beast/README.md @@ -1,63 +1,50 @@ Beast -[![Join the chat at https://gitter.im/vinniefalco/Beast](https://badges.gitter.im/vinniefalco/Beast.svg)](https://gitter.im/vinniefalco/Beast?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](https://travis-ci.org/vinniefalco/Beast.svg?branch=master)](https://travis-ci.org/vinniefalco/Beast) [![codecov](https://codecov.io/gh/vinniefalco/Beast/branch/master/graph/badge.svg)](https://codecov.io/gh/vinniefalco/Beast) [![coveralls](https://coveralls.io/repos/github/vinniefalco/Beast/badge.svg?branch=master)](https://coveralls.io/github/vinniefalco/Beast?branch=master) [![Documentation](https://img.shields.io/badge/documentation-master-brightgreen.svg)](http://vinniefalco.github.io/beast/) [![License](https://img.shields.io/badge/license-boost-brightgreen.svg)](LICENSE_1_0.txt) - # HTTP and WebSocket built on Boost.Asio in C++11 ---- - -## Appearances - -| CppCast 2017 | CppCon 2016 | -| ------------ | ----------- | -| Vinnie Falco | Beast | - ---- +Branch | Build | Coverage | Documentation +------------|---------------|----------------|--------------- +[master](https://github.com/vinniefalco/Beast/tree/master) | [![Build Status](https://travis-ci.org/vinniefalco/Beast.svg?branch=master)](https://travis-ci.org/vinniefalco/Beast) [![Build status](https://ci.appveyor.com/api/projects/status/g0llpbvhpjuxjnlw/branch/master?svg=true)](https://ci.appveyor.com/project/vinniefalco/beast/branch/master) | [![codecov](https://codecov.io/gh/vinniefalco/Beast/branch/master/graph/badge.svg)](https://codecov.io/gh/vinniefalco/Beast/branch/master) | [![Documentation](https://img.shields.io/badge/documentation-master-brightgreen.svg)](http://vinniefalco.github.io/beast/) +[develop](https://github.com/vinniefalco/Beast/tree/develop) | [![Build Status](https://travis-ci.org/vinniefalco/Beast.svg?branch=develop)](https://travis-ci.org/vinniefalco/Beast) [![Build status](https://ci.appveyor.com/api/projects/status/g0llpbvhpjuxjnlw/branch/develop?svg=true)](https://ci.appveyor.com/project/vinniefalco/beast/branch/develop) | [![codecov](https://codecov.io/gh/vinniefalco/Beast/branch/develop/graph/badge.svg)](https://codecov.io/gh/vinniefalco/Beast/branch/develop) | [![Documentation](https://img.shields.io/badge/documentation-develop-brightgreen.svg)](http://vinniefalco.github.io/stage/beast/develop) ## Contents - [Introduction](#introduction) +- [Appearances](#appearances) - [Description](#description) - [Requirements](#requirements) - [Building](#building) - [Usage](#usage) - [Licence](#licence) - [Contact](#contact) +- [Contributing](#Contributing) ## Introduction -Beast is a header-only, cross-platform C++ library built on Boost.Asio and -Boost, containing two modules implementing widely used network protocols. -Beast.HTTP offers a universal model for describing, sending, and receiving -HTTP messages while Beast.WebSocket provides a complete implementation of -the WebSocket protocol. Their design achieves these goals: +Beast is a C++ header-only library serving as a foundation for writing +interoperable networking libraries by providing **low-level HTTP/1, +WebSocket, and networking protocol** vocabulary types and algorithms +using the consistent asynchronous model of Boost.Asio. -* **Symmetry.** Interfaces are role-agnostic; the same interfaces can be -used to build clients, servers, or both. +This library is designed for: -* **Ease of Use.** HTTP messages are modeled using simple, readily -accessible objects. Functions and classes used to send and receive HTTP -or WebSocket messages are designed to resemble Boost.Asio as closely as -possible. Users familiar with Boost.Asio will be immediately comfortable -using this library. +* **Symmetry:** Algorithms are role-agnostic; build clients, servers, or both. -* **Flexibility.** Interfaces do not mandate specific implementation -strategies; important decisions such as buffer or thread management are -left to users of the library. +* **Ease of Use:** Boost.Asio users will immediately understand Beast. -* **Performance.** The implementation performs competitively, making it a -realistic choice for building high performance network servers. +* **Flexibility:** Users make the important decisions such as buffer or + thread management. -* **Scalability.** Development of network applications that scale to thousands -of concurrent connections is possible with the implementation. +* **Performance:** Build applications handling thousands of connections or more. -* **Basis for further abstraction.** The interfaces facilitate the -development of other libraries that provide higher levels of abstraction. +* **Basis for Further Abstraction.** Components are well-suited for building upon. -Beast is used in [rippled](https://github.com/ripple/rippled), an -open source server application that implements a decentralized -cryptocurrency system. +## Appearances + +| CppCast 2017 | CppCon 2016 | +| ------------ | ----------- | +| Vinnie Falco | Beast | ## Description @@ -73,17 +60,20 @@ The library has been submitted to the ## Requirements -* Boost 1.58 or later -* C++11 or later +This library is for programmers familiar with Boost.Asio. Users +who wish to use asynchronous interfaces should already know how to +create concurrent network programs using callbacks or coroutines. + +* **C++11:** Robust support for most language features. +* **Boost:** Boost.Asio and some other parts of Boost. +* **OpenSSL:** Optional, for using TLS/Secure sockets. When using Microsoft Visual C++, Visual Studio 2015 Update 3 or later is required. -These components are optionally required in order to build the -tests and examples: +These components are required in order to build the tests and examples: -* OpenSSL (optional) -* CMake 3.7.2 or later (optional) -* Properly configured bjam/b2 (optional) +* CMake 3.7.2 or later +* Properly configured bjam/b2 ## Building @@ -106,24 +96,21 @@ instructions on how to do this for your particular build system. For the examples and tests, Beast provides build scripts for Boost.Build (bjam) and CMake. It is possible to generate Microsoft Visual Studio or Apple -Developers using Microsoft Visual Studio can generate Visual Studio -project files by executing these commands from the root of the repository: +Xcode project files using CMake by executing these commands from +the root of the repository: ``` +mkdir bin cd bin cmake .. # for 32-bit Windows builds - -cd ../bin64 -cmake .. # for Linux/Mac builds, OR -cmake -G"Visual Studio 14 2015 Win64" .. # for 64-bit Windows builds -``` - -When using Apple Xcode it is possible to generate Xcode project files -using these commands: - -``` -cd bin cmake -G Xcode .. # for Apple Xcode builds + +cd .. +mkdir bin64 +cd bin64 +cmake -G"Visual Studio 14 2015 Win64" .. # for 64-bit Windows builds (VS2015) +cmake -G"Visual Studio 15 2017 Win64" .. # for 64-bit Windows builds (VS2017) + ``` To build with Boost.Build, it is necessary to have the bjam executable @@ -141,13 +128,13 @@ The files in the repository are laid out thusly: ``` ./ - bin/ Holds executables and project files - bin64/ Holds 64-bit Windows executables and project files + bin/ Create this to hold executables and project files + bin64/ Create this to hold 64-bit Windows executables and project files doc/ Source code and scripts for the documentation include/ Add this to your compiler includes beast/ extras/ Additional APIs, may change - examples/ Self contained example programs + example/ Self contained example programs test/ Unit tests and benchmarks ``` @@ -155,76 +142,9 @@ The files in the repository are laid out thusly: ## Usage These examples are complete, self-contained programs that you can build -and run yourself (they are in the `examples` directory). +and run yourself (they are in the `example` directory). -Example WebSocket program: -```C++ -#include -#include -#include -#include -#include - -int main() -{ - // Normal boost::asio setup - std::string const host = "echo.websocket.org"; - boost::asio::io_service ios; - boost::asio::ip::tcp::resolver r{ios}; - boost::asio::ip::tcp::socket sock{ios}; - boost::asio::connect(sock, - r.resolve(boost::asio::ip::tcp::resolver::query{host, "80"})); - - // WebSocket connect and send message using beast - beast::websocket::stream ws{sock}; - ws.handshake(host, "/"); - ws.write(boost::asio::buffer(std::string("Hello, world!"))); - - // Receive WebSocket message, print and close using beast - beast::streambuf sb; - beast::websocket::opcode op; - ws.read(op, sb); - ws.close(beast::websocket::close_code::normal); - std::cout << beast::to_string(sb.data()) << "\n"; -} -``` - -Example HTTP program: -```C++ -#include -#include -#include -#include -#include - -int main() -{ - // Normal boost::asio setup - std::string const host = "boost.org"; - boost::asio::io_service ios; - boost::asio::ip::tcp::resolver r{ios}; - boost::asio::ip::tcp::socket sock{ios}; - boost::asio::connect(sock, - r.resolve(boost::asio::ip::tcp::resolver::query{host, "http"})); - - // Send HTTP request using beast - beast::http::request req; - req.method = "GET"; - req.url = "/"; - req.version = 11; - req.fields.replace("Host", host + ":" + - boost::lexical_cast(sock.remote_endpoint().port())); - req.fields.replace("User-Agent", "Beast"); - beast::http::prepare(req); - beast::http::write(sock, req); - - // Receive and print HTTP response using beast - beast::streambuf sb; - beast::http::response resp; - beast::http::read(sock, sb, resp); - std::cout << resp; -} -``` +http://vinniefalco.github.io/beast/beast/quick_start.html ## License @@ -236,3 +156,51 @@ http://www.boost.org/LICENSE_1_0.txt) Please report issues or questions here: https://github.com/vinniefalco/Beast/issues + + +--- + +## Contributing (We Need Your Help!) + +If you would like to contribute to Beast and help us maintain high +quality, consider performing code reviews on active pull requests. +Any feedback from users and stakeholders, even simple questions about +how things work or why they were done a certain way, carries value +and can be used to improve the library. Code review provides these +benefits: + +* Identify bugs +* Documentation proof-reading +* Adjust interfaces to suit use-cases +* Simplify code + +You can look through the Closed pull requests to get an idea of how +reviews are performed. To give a code review just sign in with your +GitHub account and then add comments to any open pull requests below, +don't be shy! +

https://github.com/vinniefalco/Beast/pulls

+ +Here are some resources to learn more about +code reviews: + +* Top 10 Pull Request Review Mistakes +* Best Kept Secrets of Peer Code Review (pdf) +* 11 Best Practices for Peer Code Review (pdf) +* Code Review Checklist – To Perform Effective Code Reviews +* Code review guidelines +* C++ Core Guidelines +* C++ Coding Standards (Sutter & Andrescu) + +Beast thrives on code reviews and any sort of feedback from users and +stakeholders about its interfaces. Even if you just have questions, +asking them in the code review or in issues provides valuable information +that can be used to improve the library - do not hesitate, no question +is insignificant or unimportant! + +While code reviews are the preferred form of donation, if you simply +must donate money to support the library, please do so +using Bitcoin sent to this address: +1DaPsDvv6MjFUSnsxXSHzeYKSjzrWrQY7T + + + diff --git a/src/beast/TODO.txt b/src/beast/TODO.txt deleted file mode 100644 index 13de4b341b..0000000000 --- a/src/beast/TODO.txt +++ /dev/null @@ -1,58 +0,0 @@ -* Add writer::prepare(msg&) interface to set Content-Type - -Boost.Http -* Use enum instead of bool in isRequest - -Docs: -* Include Example program listings in the docs -* Fix index in docs -* melpon sandbox? -* Implement cleanup-param to remove spaces around template arguments - e.g. in basic_streambuf move constructor members -* Don't put using namespace at file scope in examples, - do something like "using ba = boost::asio" instead. - -Core: -* Replace Jamroot with Jamfile -* Fix bidirectional buffers iterators operator->() -* Complete allocator testing in basic_streambuf - -WebSocket: -* Minimize sizeof(websocket::stream) -* Move check for message size limit to account for compression -* more invokable unit test coverage -* More control over the HTTP request and response during handshakes -* optimized versions of key/masking, choose prepared_key size -* Give callers control over the http request/response used during handshake -* Investigate poor autobahn results in Debug builds -* Fall through composed operation switch cases -* Use close_code::no_code instead of close_code::none -* Make request_type, response_type public APIs, - use in stream member function signatures - -HTTP: -* Define Parser concept in HTTP - - Need parse version of read() so caller can set parser options - like maximum size of headers, maximum body size, etc -* add bool should_close(message_v1 const&) to replace the use - of eof return value from write and async_write -* More fine grained parser errors -* HTTP parser size limit with test (configurable?) -* HTTP parser trailers with test -* Decode chunk encoding parameters -* URL parser, strong URL character checking in HTTP parser -* Fix prepare() calling content_length() without init() -* Complete allocator testing in basic_streambuf, basic_headers -* Custom HTTP error codes for various situations -* Branch prediction hints in parser -* Check basic_parser_v1 against rfc7230 for leading message whitespace -* Fix the order of message constructor parameters: - body first then headers (since body is constructed with arguments more often) -* Unit tests for char tables -* Remove status_code() from API when isRequest==true, et. al. -* Permit sending trailers and parameters in chunk-encoding chunks - -Future: - -* SOCKS proxy client and server implementations - diff --git a/src/beast/appveyor.yml b/src/beast/appveyor.yml new file mode 100644 index 0000000000..e6f93604fc --- /dev/null +++ b/src/beast/appveyor.yml @@ -0,0 +1,102 @@ +# Copyright 2016 Peter Dimov +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at http://boost.org/LICENSE_1_0.txt) + +#version: 1.0.{build}-{branch} +version: "{branch} (#{build})" + +shallow_clone: true + +platform: + #- x86 + - x64 + +configuration: + #- Debug + - Release + +install: + - cd .. + - git clone https://github.com/boostorg/boost.git boost + - cd boost +# - git checkout boost-1.64.0 + - xcopy /s /e /q %APPVEYOR_BUILD_FOLDER% libs\beast\ + - git submodule update --init tools/build + - git submodule update --init libs/config + - git submodule update --init tools/boostdep +# - python tools/boostdep/depinst/depinst.py beast + - git submodule update --init libs/any + - git submodule update --init libs/asio + - git submodule update --init libs/algorithm + - git submodule update --init libs/array + - git submodule update --init libs/assert + - git submodule update --init libs/atomic + - git submodule update --init libs/bind + - git submodule update --init libs/chrono + - git submodule update --init libs/concept_check + - git submodule update --init libs/config + - git submodule update --init libs/container + - git submodule update --init libs/context + - git submodule update --init libs/conversion + - git submodule update --init libs/core + - git submodule update --init libs/coroutine + - git submodule update --init libs/date_time + - git submodule update --init libs/detail + - git submodule update --init libs/endian + - git submodule update --init libs/exception + - git submodule update --init libs/filesystem + - git submodule update --init libs/foreach + - git submodule update --init libs/function + - git submodule update --init libs/function_types + - git submodule update --init libs/functional + - git submodule update --init libs/fusion + - git submodule update --init libs/integer + - git submodule update --init libs/intrusive + - git submodule update --init libs/io + - git submodule update --init libs/iostreams + - git submodule update --init libs/iterator + - git submodule update --init libs/lambda + - git submodule update --init libs/lexical_cast + - git submodule update --init libs/locale + - git submodule update --init libs/logic + - git submodule update --init libs/math + - git submodule update --init libs/move + - git submodule update --init libs/mpl + - git submodule update --init libs/numeric/conversion + - git submodule update --init libs/optional +# - git submodule update --init libs/phoenix + - git submodule update --init libs/pool + - git submodule update --init libs/predef + - git submodule update --init libs/preprocessor + - git submodule update --init libs/program_options + - git submodule update --init libs/proto + - git submodule update --init libs/random + - git submodule update --init libs/range + - git submodule update --init libs/ratio + - git submodule update --init libs/rational + - git submodule update --init libs/regex + - git submodule update --init libs/serialization + - git submodule update --init libs/smart_ptr +# - git submodule update --init libs/spirit + - git submodule update --init libs/static_assert + - git submodule update --init libs/system + - git submodule update --init libs/thread + - git submodule update --init libs/throw_exception + - git submodule update --init libs/tokenizer + - git submodule update --init libs/tti + - git submodule update --init libs/tuple + - git submodule update --init libs/type_index + - git submodule update --init libs/type_traits + - git submodule update --init libs/typeof + - git submodule update --init libs/unordered + - git submodule update --init libs/utility + - git submodule update --init libs/variant + - git submodule update --init libs/winapi + - bootstrap + - b2 headers + +build: off + +test_script: + - b2 libs/beast/example toolset=msvc-14.0 + - b2 libs/beast/test toolset=msvc-14.0 diff --git a/src/beast/doc/0_main.qbk b/src/beast/doc/0_main.qbk new file mode 100644 index 0000000000..90de1b15bb --- /dev/null +++ b/src/beast/doc/0_main.qbk @@ -0,0 +1,113 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[library Beast + [quickbook 1.6] + [copyright 2013 - 2017 Vinnie Falco] + [purpose Networking Protocol Library] + [license + Distributed under the Boost Software License, Version 1.0. + (See accompanying file LICENSE_1_0.txt or copy at + [@http://www.boost.org/LICENSE_1_0.txt]) + ] + [authors [Falco, Vinnie]] + [category template] + [category generic] +] + +[template mdash[] '''— '''] +[template indexterm1[term1] ''''''[term1]''''''] +[template indexterm2[term1 term2] ''''''[term1]''''''[term2]''''''] +[template repo_file[path] ''''''[path]''''''] +[template include_file[path][^<''''''[path]''''''>]] + +[def __N3747__ [@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3747.pdf [*N3747]]] +[def __N4588__ [@http://cplusplus.github.io/networking-ts/draft.pdf [*N4588]]] +[def __rfc6455__ [@https://tools.ietf.org/html/rfc6455 rfc6455]] +[def __rfc7230__ [@https://tools.ietf.org/html/rfc7230 rfc7230]] + +[def __Asio__ [@http://www.boost.org/doc/html/boost_asio.html Boost.Asio]] + +[def __asio_handler_invoke__ [@http://www.boost.org/doc/html/boost_asio/reference/asio_handler_invoke.html `asio_handler_invoke`]] +[def __asio_handler_allocate__ [@http://www.boost.org/doc/html/boost_asio/reference/asio_handler_allocate.html `asio_handler_allocate`]] +[def __io_service__ [@http://www.boost.org/doc/html/boost_asio/reference/io_service.html `io_service`]] +[def __socket__ [@http://www.boost.org/doc/html/boost_asio/reference/ip__tcp/socket.html `boost::asio::ip::tcp::socket`]] +[def __ssl_stream__ [@http://www.boost.org/doc/html/boost_asio/reference/ssl__stream.html `boost::asio::ssl::stream`]] +[def __streambuf__ [@http://www.boost.org/doc/html/boost_asio/reference/streambuf.html `boost::asio::streambuf`]] +[def __use_future__ [@http://www.boost.org/doc/html/boost_asio/reference/use_future_t.html `boost::asio::use_future`]] +[def __void_or_deduced__ [@http://www.boost.org/doc/html/boost_asio/reference/asynchronous_operations.html#boost_asio.reference.asynchronous_operations.return_type_of_an_initiating_function ['void-or-deduced]]] +[def __yield_context__ [@http://www.boost.org/doc/html/boost_asio/reference/yield_context.html `boost::asio::yield_context`]] + +[def __AsyncReadStream__ [@http://www.boost.org/doc/html/boost_asio/reference/AsyncReadStream.html [*AsyncReadStream]]] +[def __AsyncWriteStream__ [@http://www.boost.org/doc/html/boost_asio/reference/AsyncWriteStream.html [*AsyncWriteStream]]] +[def __CompletionHandler__ [@http://www.boost.org/doc/html/boost_asio/reference/CompletionHandler.html [*CompletionHandler]]] +[def __ConstBufferSequence__ [@http://www.boost.org/doc/html/boost_asio/reference/ConstBufferSequence.html [*ConstBufferSequence]]] +[def __Handler__ [@http://www.boost.org/doc/html/boost_asio/reference/Handler.html [*Handler]]] +[def __MutableBufferSequence__ [@http://www.boost.org/doc/html/boost_asio/reference/MutableBufferSequence.html [*MutableBufferSequence]]] +[def __SyncReadStream__ [@http://www.boost.org/doc/html/boost_asio/reference/SyncReadStream.html [*SyncReadStream]]] +[def __SyncWriteStream__ [@http://www.boost.org/doc/html/boost_asio/reference/SyncWriteStream.html [*SyncWriteStream]]] + +[def __async_initfn__ [@http://www.boost.org/doc/html/boost_asio/reference/asynchronous_operations.html initiating function]] + +[def __AsyncStream__ [link beast.concept.streams.AsyncStream [*AsyncStream]]] +[def __Body__ [link beast.concept.Body [*Body]]] +[def __BodyReader__ [link beast.concept.BodyReader [*BodyReader]]] +[def __BodyWriter__ [link beast.concept.BodyWriter [*BodyWriter]]] +[def __DynamicBuffer__ [link beast.concept.DynamicBuffer [*DynamicBuffer]]] +[def __Fields__ [link beast.concept.Fields [*Fields]]] +[def __FieldsReader__ [link beast.concept.FieldsReader [*FieldsReader]]] +[def __File__ [link beast.concept.File [*File]]] +[def __Stream__ [link beast.concept.streams [*Stream]]] +[def __SyncStream__ [link beast.concept.streams.SyncStream [*SyncStream]]] + +[def __basic_fields__ [link beast.ref.beast__http__basic_fields `basic_fields`]] +[def __basic_multi_buffer__ [link beast.ref.beast__basic_multi_buffer `basic_multi_buffer`]] +[def __basic_parser__ [link beast.ref.beast__http__basic_parser `basic_parser`]] +[def __buffer_body__ [link beast.ref.beast__http__buffer_body `buffer_body`]] +[def __fields__ [link beast.ref.beast__http__fields `fields`]] +[def __flat_buffer__ [link beast.ref.beast__flat_buffer `flat_buffer`]] +[def __header__ [link beast.ref.beast__http__header `header`]] +[def __message__ [link beast.ref.beast__http__message `message`]] +[def __multi_buffer__ [link beast.ref.beast__multi_buffer `multi_buffer`]] +[def __parser__ [link beast.ref.beast__http__parser `parser`]] +[def __serializer__ [link beast.ref.beast__http__serializer `serializer`]] +[def __static_buffer__ [link beast.ref.beast__static_buffer `static_buffer`]] +[def __static_buffer_n__ [link beast.ref.beast__static_buffer_n `static_buffer_n`]] + +[import ../example/common/detect_ssl.hpp] +[import ../example/doc/http_examples.hpp] +[import ../example/echo-op/echo_op.cpp] +[import ../example/http-client/http_client.cpp] +[import ../example/websocket-client/websocket_client.cpp] + +[import ../include/beast/http/file_body.hpp] + +[import ../test/exemplars.cpp] +[import ../test/core/doc_snippets.cpp] +[import ../test/http/doc_snippets.cpp] +[import ../test/websocket/doc_snippets.cpp] + +[include 1_intro.qbk] +[include 2_examples.qbk] +[include 3_0_core.qbk] +[include 5_00_http.qbk] +[include 6_0_http_examples.qbk] +[include 7_0_websocket.qbk] +[include 8_concepts.qbk] +[include 9_0_design.qbk] + +[section:quickref Reference] +[xinclude quickref.xml] +[endsect] + +[block'''This Page Intentionally Left Blank 1/2'''] +[section:ref This Page Intentionally Left Blank 2/2] +[include reference.qbk] +[endsect] +[block''''''] + +[xinclude index.xml] diff --git a/src/beast/doc/1_intro.qbk b/src/beast/doc/1_intro.qbk new file mode 100644 index 0000000000..3927ebddb5 --- /dev/null +++ b/src/beast/doc/1_intro.qbk @@ -0,0 +1,96 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section:intro Introduction] + +Beast is a C++ header-only library serving as a foundation for writing +interoperable networking libraries by providing [*low-level HTTP/1, +WebSocket, and networking protocol] vocabulary types and algorithms +using the consistent asynchronous model of __Asio__. + +This library is designed for: + +* [*Symmetry:] Algorithms are role-agnostic; build clients, servers, or both. + +* [*Ease of Use:] __Asio__ users will immediately understand Beast. + +* [*Flexibility:] Users make the important decisions such as buffer or + thread management. + +* [*Performance:] Build applications handling thousands of connections or more. + +* [*Basis for Further Abstraction.] Components are well-suited for building upon. + +Beast is not an HTTP client or HTTP server, but it can be used to build +those things. + +[heading Motivation] + +Beast empowers users to create their own libraries, clients, and servers +using HTTP/1 and WebSocket. Code will be easier and faster to implement, +understand, and maintain, because Beast takes care of the low-level +protocol details. +The HTTP and WebSocket protocols drive most of the World Wide Web. +Every web browser implements these protocols to load webpages and +to enable client side programs (often written in JavaScript) to +communicate interactively. C++ benefits greatly from having a +standardized implementation of these protocols. + +[heading Requirements] + +[important + This library is for programmers familiar with __Asio__. Users who + wish to use asynchronous interfaces should already know how to + create concurrent network programs using callbacks or coroutines. +] + +Beast requires: + +* [*C++11:] Robust support for most language features. +* [*Boost:] Beast only works with Boost, not stand-alone Asio +* [*OpenSSL:] Optional, for using TLS/Secure sockets. + +Supported compilers: msvc-14+, gcc 4.8+, clang 3.6+ + +Sources are [*header-only]. To link a program using Beast successfully, add the +[@http://www.boost.org/libs/system/doc/reference.html Boost.System] +library to the list of linked libraries. If you use coroutines +you'll also need the +[@http://www.boost.org/libs/coroutine/doc/html/index.html Boost.Coroutine] +library. Please visit the +[@http://www.boost.org/doc/ Boost documentation] +for instructions on how to do this for your particular build system. + +[heading Credits] + +Boost.Asio is the inspiration behind which all of the interfaces and +implementation strategies are built. Some parts of the documentation are +written to closely resemble the wording and presentation of Boost.Asio +documentation. Credit goes to +[@https://github.com/chriskohlhoff Christopher Kohlhoff] +for his wonderful Asio library and the ideas in __N4588__ which power Beast. + +Beast would not be possible without the support of +[@https://www.ripple.com Ripple] +during the library's early development, or the ideas, time and patience +contributed by +[@https://github.com/JoelKatz David Schwartz], +[@https://github.com/ximinez Edward Hennis], +[@https://github.com/howardhinnant Howard Hinnant], +[@https://github.com/miguelportilla Miguel Portilla], +[@https://github.com/nbougalis Nik Bougalis], +[@https://github.com/seelabs Scott Determan], +[@https://github.com/scottschurr Scott Schurr], +Many thanks to +[@https://github.com/K-ballo Agustín Bergé], +[@http://www.boost.org/users/people/glen_fernandes.html Glen Fernandes], +and +[@https://github.com/pdimov Peter Dimov] +for tirelessly answering questions on +[@https://cpplang.slack.com/ Cpplang-Slack]. + +[endsect] diff --git a/src/beast/doc/2_examples.qbk b/src/beast/doc/2_examples.qbk new file mode 100644 index 0000000000..31cd55650e --- /dev/null +++ b/src/beast/doc/2_examples.qbk @@ -0,0 +1,192 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section:quickstart Quick Start] +[block''''''] + +These complete programs are intended to quickly impress upon readers +the flavor of the library. Source code and build scripts for them are +located in the example/ directory. + +[section HTTP Client] + +Use HTTP to make a GET request to a website and print the response: + +File: [repo_file example/http-client/http_client.cpp] + +[example_http_client] + +[endsect] + +[section WebSocket Client] + +Establish a WebSocket connection, send a message and receive the reply: + +File: [repo_file example/websocket-client/websocket_client.cpp] + +[example_websocket_client] + +[endsect] + +[endsect] + + + +[section:examples Examples] +[block''''''] + +Source code and build scripts for these programs are located +in the example/ directory. + + + +[section HTTP Crawl] + +This example retrieves the page at each of the most popular domains +as measured by Alexa. + +* [repo_file example/http-crawl/http_crawl.cpp] + +[endsect] + + + +[section HTTP Client (with SSL)] + +This example demonstrates sending and receiving HTTP messages +over a TLS connection. Requires OpenSSL to build. + +* [repo_file example/http-client-ssl/http_client_ssl.cpp] + +[endsect] + + + +[section HTTP Server (Fast)] + +This example implements a very simple HTTP server with +some optimizations suitable for calculating benchmarks. + +* [repo_file example/http-server-fast/fields_alloc.hpp] +* [repo_file example/http-server-fast/http_server_fast.cpp] + +[endsect] + + + +[section HTTP Server (Small)] + +This example implements a very simple HTTP server +suitable as a starting point on an embedded device. + +* [repo_file example/http-server-small/http_server_small.cpp] + +[endsect] + + + +[section HTTP Server (Threaded)] + +This example implements a very simple HTTP server using +synchronous interfaces and using one thread per connection: + +* [repo_file example/http-server-threaded/http_server_threaded.cpp] + +[endsect] + + + +[section WebSocket Client (with SSL)] + +Establish a WebSocket connection over an encrypted TLS connection, +send a message and receive the reply. Requires OpenSSL to build. + +* [repo_file example/websocket-client-ssl/websocket_client_ssl.cpp] + +[endsect] + + + +[section WebSocket Server (Asynchronous)] + +This program implements a WebSocket echo server using asynchronous +interfaces and a configurable number of threads. + +* [repo_file example/websocket-server-async/websocket_server_async.cpp] + +[endsect] + + + +[section Documentation Samples] + +Here are all of the example functions and classes presented +throughout the documentation, they can be included and used +in your program without modification + +* [repo_file example/doc/http_examples.hpp] + +[endsect] + + + +[section Composed Operations] + +This program shows how to use Beast's network foundations to build a +composable asynchronous initiation function with associated composed +operation implementation. This is a complete, runnable version of +the example described in the Core Foundations document section. + +* [repo_file example/echo-op/echo_op.cpp] + +[endsect] + + + +[section Common Code] + +This code is reused between some of the examples. The header files +stand alone can be directly included in your projects. + +* [repo_file example/common/detect_ssl.hpp] +* [repo_file example/common/helpers.hpp] +* [repo_file example/common/mime_types.hpp] +* [repo_file example/common/rfc7231.hpp] +* [repo_file example/common/ssl_stream.hpp] +* [repo_file example/common/write_msg.hpp] + +[endsect] + + + +[section Server Framework] + +This is a complete program and framework of classes implementing +a general purpose server that users may copy to use as the basis +for writing their own servers. It serves both HTTP and WebSocket. + +* [repo_file example/server-framework/file_service.hpp] +* [repo_file example/server-framework/framework.hpp] +* [repo_file example/server-framework/http_async_port.hpp] +* [repo_file example/server-framework/http_base.hpp] +* [repo_file example/server-framework/http_sync_port.hpp] +* [repo_file example/server-framework/https_ports.hpp] +* [repo_file example/server-framework/main.cpp] +* [repo_file example/server-framework/multi_port.hpp] +* [repo_file example/server-framework/server.hpp] +* [repo_file example/server-framework/service_list.hpp] +* [repo_file example/server-framework/ssl_certificate.hpp] +* [repo_file example/server-framework/ws_async_port.hpp] +* [repo_file example/server-framework/ws_sync_port.hpp] +* [repo_file example/server-framework/ws_upgrade_service.hpp] +* [repo_file example/server-framework/wss_ports.hpp] + +[endsect] + + + +[endsect] diff --git a/src/beast/doc/3_0_core.qbk b/src/beast/doc/3_0_core.qbk new file mode 100644 index 0000000000..634c730ed8 --- /dev/null +++ b/src/beast/doc/3_0_core.qbk @@ -0,0 +1,33 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section:using_io Using I/O] + +This library makes I/O primitives used by the implementation publicly +available so users can take advantage of them in their own libraries. +These primitives include traits, buffers, buffer algorithms, files, +and helpers for implementing asynchronous operations compatible with +__Asio__ and described in __N3747__. This section lists these facilities +by group, with descriptions. + +[important + This documentation assumes familiarity with __Asio__. Sample + code and identifiers used throughout are written as if the + following declarations are in effect: + + [snippet_core_1a] + [snippet_core_1b] +] + +[include 3_1_asio.qbk] +[include 3_2_streams.qbk] +[include 3_3_buffers.qbk] +[include 3_4_files.qbk] +[include 3_5_composed.qbk] +[include 3_6_detect_ssl.qbk] + +[endsect] diff --git a/src/beast/doc/3_1_asio.qbk b/src/beast/doc/3_1_asio.qbk new file mode 100644 index 0000000000..9070dac7c1 --- /dev/null +++ b/src/beast/doc/3_1_asio.qbk @@ -0,0 +1,62 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section Asio Refresher] + +[warning + Beast does not manage sockets, make outgoing connections, + accept incoming connections, handle timeouts, close endpoints, + do name lookups, deal with TLS certificates, perform authentication, + or otherwise handle any aspect of connection management. This is + left to the interfaces already existing on the underlying streams. +] + +Library stream algorithms require a __socket__, __ssl_stream__, or other +__Stream__ object that has already established communication with an +endpoint. This example is provided as a reminder of how to work with +sockets: + +[snippet_core_2] + +Throughout this documentation identifiers with the following names have +special meaning: + +[table Global Variables +[[Name][Description]] +[[ + [@http://www.boost.org/doc/html/boost_asio/reference/io_service.html [*`ios`]] +][ + A variable of type + [@http://www.boost.org/doc/html/boost_asio/reference/io_service.html `boost::asio::io_service`] + which is running on one separate thread, and upon which a + [@http://www.boost.org/doc/html/boost_asio/reference/io_service__work.html `boost::asio::io_service::work`] + object has been constructed. +]] +[[ + [@http://www.boost.org/doc/html/boost_asio/reference/ip__tcp/socket.html [*`sock`]] +][ + A variable of type + [@http://www.boost.org/doc/html/boost_asio/reference/ip__tcp/socket.html `boost::asio::ip::tcp::socket`] + which has already been connected to a remote host. +]] +[[ + [@http://www.boost.org/doc/html/boost_asio/reference/ssl__stream.html [*`ssl_sock`]] +][ + A variable of type + [@http://www.boost.org/doc/html/boost_asio/reference/ssl__stream.html `boost::asio::ssl::stream`] + which is already connected and has handshaked with a remote host. +]] +[[ + [link beast.ref.beast__websocket__stream [*`ws`]] +][ + A variable of type + [link beast.ref.beast__websocket__stream `websocket::stream`] + which is already connected with a remote host. +]] +] + +[endsect] diff --git a/src/beast/doc/3_2_streams.qbk b/src/beast/doc/3_2_streams.qbk new file mode 100644 index 0000000000..1778dff501 --- /dev/null +++ b/src/beast/doc/3_2_streams.qbk @@ -0,0 +1,120 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section Stream Types] + +A __Stream__ is a communication channel where data is transferred as +an ordered sequence of octet buffers. Streams are either synchronous +or asynchronous, and may allow reading, writing, or both. Note that +a particular type may model more than one concept. For example, the +Asio types __socket__ and __ssl_stream__ support both __SyncStream__ +and __AsyncStream__. All stream algorithms in Beast are declared as +template functions using these concepts: + +[table Stream Concepts +[[Concept][Description]] +[ + [__SyncReadStream__] + [ + Supports buffer-oriented blocking reads. + ] +][ + [__SyncWriteStream__] + [ + Supports buffer-oriented blocking writes. + ] +][ + [__SyncStream__] + [ + A stream supporting buffer-oriented blocking reads and writes. + ] +][ + [__AsyncReadStream__] + [ + Supports buffer-oriented asynchronous reads. + ] +][ + [__AsyncWriteStream__] + [ + Supports buffer-oriented asynchronous writes. + ] +][ + [__AsyncStream__] + [ + A stream supporting buffer-oriented asynchronous reads and writes. + ] +] +] + +These template metafunctions check whether a given type meets the +requirements for the various stream concepts, and some additional +useful utilities. The library uses these type checks internally +and also provides them as public interfaces so users may use the +same techniques to augment their own code. The use of these type +checks helps provide more concise errors during compilation: + +[table Stream Type Checks +[[Name][Description]] +[[ + [link beast.ref.beast__get_lowest_layer `get_lowest_layer`] +][ + Returns `T::lowest_layer_type` if it exists, else returns `T`. +]] +[[ + [link beast.ref.beast__has_get_io_service `has_get_io_service`] +][ + Determine if the `get_io_service` member function is present, + and returns an __io_service__. +]] +[[ + [link beast.ref.beast__is_async_read_stream `is_async_read_stream`] +][ + Determine if a type meets the requirements of __AsyncReadStream__. +]] +[[ + [link beast.ref.beast__is_async_stream `is_async_stream`] +][ + Determine if a type meets the requirements of both __AsyncReadStream__ + and __AsyncWriteStream__. +]] +[[ + [link beast.ref.beast__is_async_write_stream `is_async_write_stream`] +][ + Determine if a type meets the requirements of __AsyncWriteStream__. +]] +[[ + [link beast.ref.beast__is_completion_handler `is_completion_handler`] +][ + Determine if a type meets the requirements of __CompletionHandler__, + and is callable with a specified signature. +]] +[[ + [link beast.ref.beast__is_sync_read_stream `is_sync_read_stream`] +][ + Determine if a type meets the requirements of __SyncReadStream__. +]] +[[ + [link beast.ref.beast__is_sync_stream `is_sync_stream`] +][ + Determine if a type meets the requirements of both __SyncReadStream__ + and __SyncWriteStream__. +]] +[[ + [link beast.ref.beast__is_sync_write_stream `is_sync_write_stream`] +][ + Determine if a type meets the requirements of __SyncWriteStream__. +]] +] + +Using the type checks with `static_assert` on function or class template +types will provide users with helpful error messages and prevent undefined +behaviors. This example shows how a template function which writes to a +synchronous stream may check its argument: + +[snippet_core_3] + +[endsect] diff --git a/src/beast/doc/3_3_buffers.qbk b/src/beast/doc/3_3_buffers.qbk new file mode 100644 index 0000000000..21d9df3ed0 --- /dev/null +++ b/src/beast/doc/3_3_buffers.qbk @@ -0,0 +1,161 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section Buffer Types] + +__Asio__ provides the __ConstBufferSequence__ and __MutableBufferSequence__ +concepts, whose models provide ranges of buffers, as well as the __streambuf__ +class which encapsulates memory storage that may be automatically resized as +required, where the memory is divided into an input sequence followed by an +output sequence. The Networking TS (__N4588__) generalizes this `streambuf` +interface into the __DynamicBuffer__ concept. Beast algorithms which require +resizable buffers accept dynamic buffer objects as templated parameters. +These metafunctions check if types match the buffer concepts: + +[table Buffer Type Checks +[[Name][Description]] +[[ + [link beast.ref.beast__is_dynamic_buffer `is_dynamic_buffer`] +][ + Determine if a type meets the requirements of __DynamicBuffer__. +]] +[[ + [link beast.ref.beast__is_const_buffer_sequence `is_const_buffer_sequence`] +][ + Determine if a type meets the requirements of __ConstBufferSequence__. +]] +[[ + [link beast.ref.beast__is_mutable_buffer_sequence `is_mutable_buffer_sequence`] +][ + Determine if a type meets the requirements of __MutableBufferSequence__. +]] +] + +Beast provides several dynamic buffer implementations for a variety +of scenarios: + +[table Dynamic Buffer Implementations +[[Name][Description]] +[[ + [link beast.ref.beast__buffers_adapter `buffers_adapter`] +][ + This wrapper adapts any __MutableBufferSequence__ into a + __DynamicBuffer__ with an upper limit on the total size of the input and + output areas equal to the size of the underlying mutable buffer sequence. + The implementation does not perform heap allocations. +]] +[[ + [link beast.ref.beast__drain_buffer `drain_buffer`] +][ + A drain buffer has a small internal buffer and maximum size that + uses no dynamic allocation. It always has a size of zero, and + silently discards its input. This buffer may be passed to functions + which store data in a dynamic buffer when the caller wishes to + efficiently discard the data. +]] +[[ + [link beast.ref.beast__flat_buffer `flat_buffer`] + [link beast.ref.beast__basic_flat_buffer `basic_flat_buffer`] +][ + Guarantees that input and output areas are buffer sequences with + length one. Upon construction an optional upper limit to the total + size of the input and output areas may be set. The basic container + is an + [@http://en.cppreference.com/w/cpp/concept/AllocatorAwareContainer [*AllocatorAwareContainer]]. +]] +[[ + [link beast.ref.beast__multi_buffer `multi_buffer`] + [link beast.ref.beast__basic_multi_buffer `basic_multi_buffer`] +][ + Uses a sequence of one or more character arrays of varying sizes. + Additional character array objects are appended to the sequence to + accommodate changes in the size of the character sequence. The basic + container is an + [@http://en.cppreference.com/w/cpp/concept/AllocatorAwareContainer [*AllocatorAwareContainer]]. +]] +[[ + [link beast.ref.beast__static_buffer `static_buffer`] + [link beast.ref.beast__static_buffer `static_buffer_n`] +][ + Provides the facilities of a dynamic buffer, subject to an upper + limit placed on the total size of the input and output areas defined + by a constexpr template parameter. The storage for the sequences are + kept in the class; the implementation does not perform heap allocations. +]] +] + +Network applications frequently need to manipulate buffer sequences. To +facilitate working with buffers the library treats these sequences as +a special type of range. Algorithms and wrappers are provided which +transform these ranges efficiently using lazy evaluation. No memory +allocations are used in the transformations; instead, they create +lightweight iterators over the existing, unmodified memory buffers. +Control of buffers is retained by the caller; ownership is not +transferred. + +[table Buffer Algorithms and Types +[[Name][Description]] +[[ + [link beast.ref.beast__buffer_cat `buffer_cat`] +][ + This functions returns a new buffer sequence which, when iterated, + traverses the sequence which would be formed if all of the input buffer + sequences were concatenated. With this routine, multiple calls to a + stream's `write_some` function may be combined into one, eliminating + expensive system calls. +]] +[[ + [link beast.ref.beast__buffer_cat_view `buffer_cat_view`] +][ + This class represents the buffer sequence formed by concatenating + two or more buffer sequences. This is type of object returned by + [link beast.ref.beast__buffer_cat `buffer_cat`]. +]] +[[ + [link beast.ref.beast__buffer_prefix `buffer_prefix`] +][ + This function returns a new buffer or buffer sequence which represents + a prefix of the original buffers. +]] +[[ + [link beast.ref.beast__buffer_prefix_view `buffer_prefix_view`] +][ + This class represents the buffer sequence formed from a prefix of + an existing buffer sequence. This is the type of buffer returned by + [link beast.ref.beast__buffer_prefix.overload3 `buffer_prefix`]. +]] +[[ + [link beast.ref.beast__consuming_buffers `consuming_buffers`] +][ + This class wraps the underlying memory of an existing buffer sequence + and presents a suffix of the original sequence. The length of the suffix + may be progressively shortened. This lets callers work with sequential + increments of a buffer sequence. +]] +] + +These two functions facilitate buffer interoperability with standard +output streams. + +[table Buffer Output Streams +[[Name][Description]] +[[ + [link beast.ref.beast__buffers `buffers`] +][ + This function wraps a __ConstBufferSequence__ so it may be + used with `operator<<` and `std::ostream`. +]] +[[ + [link beast.ref.beast__ostream `ostream`] +][ + This function returns a `std::ostream` which wraps a dynamic buffer. + Characters sent to the stream using `operator<<` are stored in the + dynamic buffer. +]] +] + +[endsect] diff --git a/src/beast/doc/3_4_files.qbk b/src/beast/doc/3_4_files.qbk new file mode 100644 index 0000000000..020875f0b4 --- /dev/null +++ b/src/beast/doc/3_4_files.qbk @@ -0,0 +1,38 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section:files Files] + +Often when implementing network algorithms such as servers, it is necessary +to interact with files on the system. Beast defines the __File__ concept +and several models to facilitate cross-platform interaction with the +underlying filesystem: + +[table File Types +[[Name][Description]] +[[ + [link beast.ref.beast__file_stdio `file_stdio`] +][ + This implementation of __File__ uses the C++ standard library + facilities obtained by including ``. +]] +[[ + [link beast.ref.beast__file_win32 `file_win32`] +][ + This implements a __File__ for the Win32 API. It provides low level + access to the native file handle when necessary. +]] +[[ + [link beast.ref.beast__file_posix `file_posix`] +][ + For POSIX systems, this class provides a suitable implementation + of __File__ which wraps the native file descriptor and provides + it if necessary. +]] +] + +[endsect] diff --git a/src/beast/doc/3_5_composed.qbk b/src/beast/doc/3_5_composed.qbk new file mode 100644 index 0000000000..4d3eaa1ef1 --- /dev/null +++ b/src/beast/doc/3_5_composed.qbk @@ -0,0 +1,236 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section Writing Composed Operations] +[block''''''] + +Asynchronous operations are started by calling a free function or member +function known as an asynchronous ['__async_initfn__]. This function accepts +parameters specific to the operation as well as a "completion token." The +token is either a completion handler, or a type defining how the caller is +informed of the asynchronous operation result. __Asio__ comes with the +special tokens __use_future__ and __yield_context__ for using futures +and coroutines respectively. This system of customizing the return value +and method of completion notification is known as the +['Extensible Asynchronous Model] described in __N3747__, and a built in +to __N4588__. Here is an example of an initiating function which reads a +line from the stream and echoes it back. This function is developed +further in the next section: + +[example_core_echo_op_1] + +Authors using Beast can reuse the library's primitives to create their +own initiating functions for performing a series of other, intermediate +asynchronous operations before invoking a final completion handler. +The set of intermediate actions produced by an initiating function is +known as a +[@http://blog.think-async.com/2009/08/composed-operations-coroutines-and-code.html ['composed operation]]. +To ensure full interoperability and well-defined behavior, __Asio__ imposes +requirements on the implementation of composed operations. These classes +and functions make it easier to develop initiating functions and their +composed operations: + +[table Asynchronous Helpers +[[Name][Description]] +[[ + [link beast.ref.beast__async_completion `async_completion`] +][ + This class aggregates the completion handler customization point and + the asynchronous initiation function return value customization point + into a single object which exposes the appropriate output types for the + given input types, and also contains boilerplate that is necessary to + implement an initiation function using the Extensible Model. +]] +[[ + [link beast.ref.beast__async_return_type `async_return_type`] +][ + This template alias determines the return value of an asynchronous + initiation function given the completion token and signature. It is used + by asynchronous initiation functions to meet the requirements of the + Extensible Asynchronous Model. +]] +[[ + [link beast.ref.beast__bind_handler `bind_handler`] +][ + This function returns a new, nullary completion handler which when + invoked with no arguments invokes the original completion handler with a + list of bound arguments. The invocation is made from the same implicit + or explicit strand as that which would be used to invoke the original + handler. This is accomplished by using the correct overload of + `asio_handler_invoke` associated with the original completion handler. + +]] +[[ + [link beast.ref.beast__handler_alloc `handler_alloc`] +][ + This class meets the requirements of [*Allocator], and uses any custom + memory allocation and deallocation hooks associated with a given handler. + It is useful for when a composed operation requires temporary dynamic + allocations to achieve its result. Memory allocated using this allocator + must be freed before the final completion handler is invoked. +]] +[[ + [link beast.ref.beast__handler_ptr `handler_ptr`] +][ + This is a smart pointer container used to manage the internal state of a + composed operation. It is useful when the state is non trivial. For example + when the state has non-copyable or expensive to copy types. The container + takes ownership of the final completion handler, and provides boilerplate + to invoke the final handler in a way that also deletes the internal state. + The internal state is allocated using the final completion handler's + associated allocator, benefiting from all handler memory management + optimizations transparently. +]] +[[ + [link beast.ref.beast__handler_type `handler_type`] +][ + This template alias converts a completion token and signature to the + correct completion handler type. It is used in the implementation of + asynchronous initiation functions to meet the requirements of the + Extensible Asynchronous Model. +]] +] + + + +[section Echo] + +This example develops an initiating function called [*echo]. +The operation will read up to the first newline on a stream, and +then write the same line including the newline back on the stream. +The implementation performs both reading and writing, and has a +non-trivially-copyable state. +First we define the input parameters and results, then declare our +initiation function. For our echo operation the only inputs are the +stream and the completion token. The output is the error code which +is usually included in all completion handler signatures. + +[example_core_echo_op_2] + +Now that we have a declaration, we will define the body of the function. +We want to achieve the following goals: perform static type checking on +the input parameters, set up the return value as per __N3747__, and launch +the composed operation by constructing the object and invoking it. + +[example_core_echo_op_3] + +The initiating function contains a few relatively simple parts. There is +the customization of the return value type, static type checking, building +the return value type using the helper, and creating and launching the +composed operation object. The [*`echo_op`] object does most of the work +here, and has a somewhat non-trivial structure. This structure is necessary +to meet the stringent requirements of composed operations (described in more +detail in the __Asio__ documentation). We will touch on these requirements +without explaining them in depth. + +Here is the boilerplate present in all composed operations written +in this style: + +[example_core_echo_op_4] + +Next is to implement the function call operator. Our strategy is to make our +composed object meet the requirements of a completion handler by being copyable +(also movable), and by providing the function call operator with the correct +signature. Rather than using `std::bind` or `boost::bind`, which destroys +the type information and therefore breaks the allocation and invocation +hooks, we will simply pass `std::move(*this)` as the completion handler +parameter for any operations that we initiate. For the move to work correctly, +care must be taken to ensure that no access to data members are made after the +move takes place. Here is the implementation of the function call operator for +this echo operation: + +[example_core_echo_op_5] + +This is the most important element of writing a composed operation, and +the part which is often neglected or implemented incorrectly. It is the +declaration and definition of the "handler hooks". There are four hooks: + +[table Handler Hooks +[[Name][Description]] +[[ + [@http://www.boost.org/doc/html/boost_asio/reference/asio_handler_invoke.html `asio_handler_invoke`] +][ + Default invoke function for handlers. This hooking function ensures + that the invoked method used for the final handler is accessible at + each intermediate step. +]] +[[ + [@http://www.boost.org/doc/html/boost_asio/reference/asio_handler_allocate.html `asio_handler_allocate`] +][ + Default allocation function for handlers. Implement `asio_handler_allocate` + and `asio_handler_deallocate` for your own handlers to provide custom + allocation for temporary objects. +]] +[[ + [@http://www.boost.org/doc/html/boost_asio/reference/asio_handler_deallocate.html `asio_handler_deallocate`] +][ + Default deallocation function for handlers. Implement `asio_handler_allocate` + and `asio_handler_deallocate` for your own handlers to provide custom + allocation for temporary objects. +]] +[[ + [@http://www.boost.org/doc/html/boost_asio/reference/asio_handler_is_continuation.html `asio_handler_is_continuation`] +][ + Default continuation function for handlers. Implement + `asio_handler_is_continuation` for your own handlers to indicate when + a handler represents a continuation. +]] +] + +Our composed operation stores the final handler and performs its own +intermediate asynchronous operations. To ensure that I/O objects, in this +case the stream, are accessed safely it is important to use the same method +to invoke intermediate handlers as that used to invoke the final handler. +Similarly, for the memory allocation hooks our composed operation should use +the same hooks as those used by the final handler. And finally for the +`asio_is_continuation` hook, we want to return `true` for any intermediate +asynchronous operations we perform after the first one, since those represent +continuations. For the first asynchronous operation we perform, the hook should +return `true` only if the final handler also represents a continuation. Our +implementation of the hooks will forward the call to the corresponding +overloads of the final handler: + +[example_core_echo_op_6] + +There are some common mistakes that should be avoided when writing +composed operations: + +* Type erasing the final handler. This will cause undefined behavior. + +* Not using `std::addressof` to get the address of the handler. + +* Forgetting to include a return statement after calling an + initiating function. + +* Calling a synchronous function by accident. In general composed + operations should not block for long periods of time, since this + ties up a thread running on the __io_service__. + +* Forgetting to overload `asio_handler_invoke` for the composed + operation. This will cause undefined behavior if someone calls + the initiating function with a strand-wrapped function object, + and there is more than thread running on the `io_service`. + +* For operations which complete immediately (i.e. without calling an + intermediate initiating function), forgetting to use `io_service::post` + to invoke the final handler. This breaks the following initiating + function guarantee: ['Regardless of whether the asynchronous operation + completes immediately or not, the handler will not be invoked from + within this function. Invocation of the handler will be performed + in a manner equivalent to using `boost::asio::io_service::post`]. + The function + [link beast.ref.beast__bind_handler `bind_handler`] + is provided for this purpose. + +A complete, runnable version of this example may be found in the examples +directory. + +[endsect] + + + +[endsect] diff --git a/src/beast/doc/3_6_detect_ssl.qbk b/src/beast/doc/3_6_detect_ssl.qbk new file mode 100644 index 0000000000..da6350b823 --- /dev/null +++ b/src/beast/doc/3_6_detect_ssl.qbk @@ -0,0 +1,67 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section Example: Detect SSL] + +In this example we will build a simple function to detect the presence +of the SSL handshake given an input buffer sequence. Then we build on +the example by adding a synchronous stream algorithm. Finally, we +implemement an asynchronous detection function using a composed operation. +This SSL detector may be used to allow a server to accept both SSL/TLS and +unencrypted connections at the same port. + +Here is the declaration for a function to detect the SSL client handshake. +The input to the function is simply a buffer sequence, no stream. This +allows the detection algorithm to be used elsewhere. + +[example_core_detect_ssl_1] + +The implementation checks the buffer for the presence of the SSL +Handshake message octet sequence and returns an apporopriate value: + +[example_core_detect_ssl_2] + +Now we define a stream operation. We start with the simple, +synchronous version which takes the stream and buffer as input: + +[example_core_detect_ssl_3] + +The synchronous algorithm is the model for building the asynchronous +operation which has more boilerplate. First, we declare the asynchronous +initiating function: + +[example_core_detect_ssl_4] + +The implementation of the initiating function is straightforward +and contains mostly boilerplate. It is to construct the return +type customization helper to obtain the actual handler, and +then create the composed operation and launch it. The actual +code for interacting with the stream is in the composed operation, +which is written as a separate class. + +[example_core_detect_ssl_5] + +Now we will declare our composed operation. There is a considerable +amount of necessary boilerplate to get this right, but the result +is worth the effort. + +[example_core_detect_ssl_6] + +The boilerplate is all done, and now we need to implement the function +call operator that turns this composed operation a completion handler +with the signature `void(error_code, std::size_t)` which is exactly +the signature needed when performing asynchronous reads. This function +is a transformation of the synchronous version of `detect_ssl` above, +but with the inversion of flow that characterizes code written in the +callback style: + +[example_core_detect_ssl_7] + +This SSL detector is used by the server framework in the example +directory. + +[endsect] diff --git a/src/beast/doc/5_00_http.qbk b/src/beast/doc/5_00_http.qbk new file mode 100644 index 0000000000..6a90c066a3 --- /dev/null +++ b/src/beast/doc/5_00_http.qbk @@ -0,0 +1,91 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section Using HTTP] + +[warning + Higher level functions such as Basic + Authentication, mime/multipart encoding, cookies, automatic handling + of redirects, gzipped transfer encodings, caching, or proxying (to name + a few) are not directly provided, but nothing stops users from creating + these features using Beast's HTTP message types. +] + +This library offers programmers simple and performant models of HTTP messages +and their associated operations including synchronous, asynchronous, and +buffer-oriented parsing and serialization of messages in the HTTP/1 wire +format using __Asio__. Specifically, the library provides: + +[variablelist +[ + [Message Containers] + [ + Complete HTTP messages are modeled using the __message__ class, + with possible user customizations. + ] +][ + [Stream Reading] + [ + The functions + [link beast.ref.beast__http__read `read`], + [link beast.ref.beast__http__read_header `read_header`], + [link beast.ref.beast__http__read_some `read_some`], + [link beast.ref.beast__http__async_read `async_read`], + [link beast.ref.beast__http__async_read_header `async_read_header`], and + [link beast.ref.beast__http__async_read_some `async_read_some`] + read HTTP/1 message data from a + [link beast.concept.streams stream]. +] +][ + [Stream Writing] + [ + The functions + [link beast.ref.beast__http__write `write`], + [link beast.ref.beast__http__write_header `write_header`], + [link beast.ref.beast__http__write_some `write_some`], + [link beast.ref.beast__http__async_write `async_write`], + [link beast.ref.beast__http__async_write_header `async_write_header`], and + [link beast.ref.beast__http__async_write_some `async_write_some`] + write HTTP/1 message data to a + [link beast.concept.streams stream]. + ] +][ + [Serialization] + [ + The __serializer__ produces a series of octet buffers + conforming to the __rfc7230__ wire representation of + a __message__. + ] +][ + [Parsing] + [ + The __parser__ attempts to convert a series of octet + buffers into a __message__. + ] +] +] + +[note + This documentation assumes some familiarity with __Asio__ and + the HTTP protocol specification described in __rfc7230__. Sample + code and identifiers mentioned in this section is written as if + these declarations are in effect: + + [http_snippet_1] +] + +[include 5_01_primer.qbk] +[include 5_02_message.qbk] +[include 5_03_streams.qbk] +[include 5_04_serializer_streams.qbk] +[include 5_05_parser_streams.qbk] +[include 5_06_serializer_buffers.qbk] +[include 5_07_parser_buffers.qbk] +[include 5_08_custom_body.qbk] +[include 5_09_custom_parsers.qbk] + +[endsect] diff --git a/src/beast/doc/5_01_primer.qbk b/src/beast/doc/5_01_primer.qbk new file mode 100644 index 0000000000..78bd5d051e --- /dev/null +++ b/src/beast/doc/5_01_primer.qbk @@ -0,0 +1,153 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section Protocol Primer] + +The HTTP protocol defines the +[@https://tools.ietf.org/html/rfc7230#section-2.1 client and server roles]: +clients send requests and servers send back responses. When a client and +server have established a connection, the client sends a series of requests +while the server sends back at least one response for each received request +in the order those requests were received. + +A request or response is an +[@https://tools.ietf.org/html/rfc7230#section-3 HTTP message] +(referred to hereafter as "message") having two parts: +a header with structured metadata and an optional variable-length body +holding arbitrary data. A serialized header is one or more text lines +where each line ends in a carriage return followed by linefeed (`"\r\n"`). +An empty line marks the end of the header. The first line in the header +is called the ['start-line]. The contents of the start line contents are +different for requests and responses. + +Every message contains a set of zero or more field name/value pairs, +collectively called "fields". The names and values are represented using +text strings with various requirements. A serialized field contains the +field name, then a colon followed by a space (`": "`), and finally the field +value with a trailing CRLF. + +[heading Requests] + +Clients send requests, which contain a +[@https://tools.ietf.org/html/rfc7230#section-3.1.1 method] +and +[@https://tools.ietf.org/html/rfc7230#section-5.3 request-target], +and +[@https://tools.ietf.org/html/rfc7230#section-2.6 HTTP-version]. +The method identifies the operation to be performed while the target +identifies the object on the server to which the operation applies. +The version is almost always 1.1, but older programs sometimes use 1.0. + +[table +[[Serialized Request][Description]] +[[ +``` + GET / HTTP/1.1\r\n + User-Agent: Beast\r\n + \r\n +``` +][ + This request has a method of "GET", a target of "/", and indicates + HTTP version 1.1. It contains a single field called "User-Agent" + whose value is "Beast". There is no message body. +]] +] + +[heading Responses] + +Servers send responses, which contain a +[@https://tools.ietf.org/html/rfc7231#section-6 status-code], +[@https://tools.ietf.org/html/rfc7230#section-3.1.2 reason-phrase], and +[@https://tools.ietf.org/html/rfc7230#section-2.6 HTTP-version]. +The reason phrase is +[@https://tools.ietf.org/html/rfc7230#section-3.1.2 obsolete]: +clients SHOULD ignore the reason-phrase content. Here is a response which +includes a body. The special +[@https://tools.ietf.org/html/rfc7230#section-3.3.2 Content-Length] +field informs the remote host of the size of the body which follows. + +[table +[[Serialized Response][Description]] +[[ +``` + HTTP/1.1 200 OK\r\n + Server: Beast\r\n + Content-Length: 13\r\n + \r\n + Hello, world! +``` +][ + This response has a + [@https://tools.ietf.org/html/rfc7231#section-6 200 status code] + meaning the operation requested completed successfully. The obsolete + reason phrase is "OK". It specifies HTTP version 1.1, and contains + a body 13 octets in size with the text "Hello, world!". +]] +] + +[heading Body] + +Messages may optionally carry a body. The size of the message body +is determined by the semantics of the message and the special fields +Content-Length and Transfer-Encoding. +[@https://tools.ietf.org/html/rfc7230#section-3.3 rfc7230 section 3.3] +provides a comprehensive description for how the body length is +determined. + +[heading Special Fields] + +Certain fields appearing in messages are special. The library understands +these fields when performing serialization and parsing, taking automatic +action as needed when the fields are parsed in a message and also setting +the fields if the caller requests it. + +[table Special Fields +[[Field][Description]] +[ + [ + [@https://tools.ietf.org/html/rfc7230#section-6.1 [*`Connection`]] + + [@https://tools.ietf.org/html/rfc7230#appendix-A.1.2 [*`Proxy-Connection`]] + ][ + This field allows the sender to indicate desired control options + for the current connection. Common values include "close", + "keep-alive", and "upgrade". + ] +][ + [ + [@https://tools.ietf.org/html/rfc7230#section-3.3.2 [*`Content-Length`]] + ][ + When present, this field informs the recipient about the exact + size in bytes of the body which follows the message header. + ] +][ + [ + [@https://tools.ietf.org/html/rfc7230#section-3.3.1 [*`Transfer-Encoding`]] + ][ + This optional field lists the names of the sequence of transfer codings + that have been (or will be) applied to the content payload to form + the message body. + + Beast understands the "chunked" coding scheme when it is the last + (outermost) applied coding. The library will automatically apply + chunked encoding when the content length is not known ahead of time + during serialization, and the library will automatically remove chunked + encoding from parsed messages when present. + ] +][ + [ + [@https://tools.ietf.org/html/rfc7230#section-6.7 [*`Upgrade`]] + ][ + The Upgrade header field provides a mechanism to transition from + HTTP/1.1 to another protocol on the same connection. For example, it + is the mechanism used by WebSocket's initial HTTP handshake to + establish a WebSocket connection. + ] +] +] + +[endsect] diff --git a/src/beast/doc/5_02_message.qbk b/src/beast/doc/5_02_message.qbk new file mode 100644 index 0000000000..f9f4a3decb --- /dev/null +++ b/src/beast/doc/5_02_message.qbk @@ -0,0 +1,231 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section Message Containers] + +Beast provides a single class template __message__ and some aliases which +model HTTP/1 and +[@https://tools.ietf.org/html/rfc7540 HTTP/2] +messages: + +[table Message +[[Name][Description]] +[[ + __message__ +][ + ``` + /// An HTTP message + template< + bool isRequest, // `true` for requests, `false` for responses + class Body, // Controls the container and algorithms used for the body + class Fields = fields> // The type of container to store the fields + class message; + ``` +]] +[[ + [link beast.ref.beast__http__request `request`] +][ + ``` + /// A typical HTTP request + template + using request = message; + ``` +]] +[[ + [link beast.ref.beast__http__response `response`] +][ + ``` + /// A typical HTTP response + template + using response = message; + ``` +]] +] + +The container offers value semantics including move and copy if supported +by __Body__ and __Fields__. User defined template function parameters can +accept any message, or can use partial specialization to accept just +requests or responses. The default __fields__ is a provided associative +container using the standard allocator and supporting modification and +inspection of fields. As per __rfc7230__, a non-case-sensitive comparison +is used for field names. User defined types for fields are possible. +The `Body` type determines the type of the container used to represent the +body as well as the algorithms for transferring buffers to and from the +the container. The library comes with a collection of common body types. +As with fields, user defined body types are possible. + +Sometimes it is desired to only work with a header. Beast provides a single +class template __header__ and some aliases to model HTTP/1 and HTTP/2 headers: + +[table Header +[[Name][Description]] +[[ + __header__ +][ + ``` + /// An HTTP header + template< + bool isRequest, // `true` for requests, `false` for responses + class Fields = fields> // The type of container to store the fields + class header; + ``` +]] +[[ + [link beast.ref.beast__http__request_header `request_header`] +][ + ``` + /// A typical HTTP request header + template + using request_header = header; + ``` +]] +[[ + [link beast.ref.beast__http__response_header `response_header`] +][ + ``` + /// A typical HTTP response header + template + using response_header = header; + ``` +]] +] + +Requests and responses share the version, fields, and body but have +a few members unique to the type. This is implemented by declaring the +header classes as partial specializations of `isRequest`. __message__ +is derived from __header__; a message may be passed as an argument to +a function taking a suitably typed header as a parameter. Additionally, +`header` is publicly derived from `Fields`; a message inherits all the +member functions of `Fields`. This diagram shows the inheritance +relationship between header and message, along with some of the +notable differences in members in each partial specialization: + +[$images/message.png [width 730px] [height 410px]] + +[heading:body Body Types] + +Beast defines the __Body__ concept, which determines both the type of +the [link beast.ref.beast__http__message.body `message::body`] member +(as seen in the diagram above) and may also include algorithms for +transferring buffers in and out. These algorithms are used during +parsing and serialization. Users may define their own body types which +meet the requirements, or use the ones that come with the library: + +[table +[[Name][Description]] +[[ + [link beast.ref.beast__http__buffer_body `buffer_body`] +][ + A body whose + [link beast.ref.beast__http__buffer_body__value_type `value_type`] + holds a raw pointer and size to a caller-provided buffer. + This allows for serialization of body data coming from + external sources, and incremental parsing of message body + content using a fixed size buffer. +]] +[[ + [link beast.ref.beast__http__dynamic_body `dynamic_body`] + + [link beast.ref.beast__http__basic_dynamic_body `basic_dynamic_body`] +][ + A body whose `value_type` is a __DynamicBuffer__. It inherits + the insertion complexity of the underlying choice of dynamic buffer. + Messages with this body type may be serialized and parsed. +]] +[[ + [link beast.ref.beast__http__empty_body `empty_body`] +][ + A special body with an empty `value_type` indicating that the + message has no body. Messages with this body may be serialized + and parsed; however, body octets received while parsing a message + with this body will generate a unique error. +]] +[[ + [link beast.ref.beast__http__file_body `file_body`] +][ + This body is represented by a file opened for either reading or + writing. Messages with this body may be serialized and parsed. + HTTP algorithms will use the open file for reading and writing, + for streaming and incremental sends and receives. +]] +[[ + [link beast.ref.beast__http__span_body `span_body`] +][ + A body whose `value_type` is a + [link beast.ref.beast__span `span`], + a non-owning reference to a single linear buffer of bytes. + Messages with this body type may be serialized and parsed. +]] +[[ + [link beast.ref.beast__http__basic_string_body `basic_string_body`] + + [link beast.ref.beast__http__string_body `string_body`] +][ + A body whose `value_type` is `std::basic_string` or `std::string`. + Insertion complexity is amortized constant time, while capacity + grows geometrically. Messages with this body type may be serialized + and parsed. This is the type of body used in the examples. +]] +[[ + [link beast.ref.beast__http__vector_body `vector_body`] +][ + A body whose `value_type` is `std::vector`. Insertion complexity + is amortized constant time, while capacity grows geometrically. + Messages with this body type may be serialized and parsed. +]] +] + +[heading Usage] + +These examples show how to create and fill in request and response +objects: Here we build an +[@https://tools.ietf.org/html/rfc7231#section-4.3.1 HTTP GET] +request with an empty message body: + +[table Create Request +[[Statements] [Serialized Result]] +[[ + [http_snippet_2] +][ +``` + GET /index.htm HTTP/1.1\r\n + Accept: text/html\r\n + User-Agent: Beast\r\n + \r\n +``` +]] +] + +In this code we create an HTTP response with a status code indicating success. +This message has a body with a non-zero length. The function +[link beast.ref.beast__http__message.prepare_payload `message::prepare_payload`] +automatically sets the Content-Length or Transfer-Encoding field +depending on the content and type of the `body` member. Use of this function +is optional; these fields may also be set explicitly. + +[table Create Response +[[Statements] [Serialized Result]] +[[ + [http_snippet_3] +][ +``` + HTTP/1.1 200 OK\r\n + Server: Beast\r\n + Content-Length: 13\r\n + \r\n + Hello, world! +``` +]] +] + +The implementation will automatically fill in the obsolete +[@https://tools.ietf.org/html/rfc7230#section-3.1.2 reason-phrase] +from the status code when serializing a message. Or it may +be set directly using +[link beast.ref.beast__http__header.reason.overload2 `header::reason`]. + +[endsect] diff --git a/src/beast/doc/5_03_streams.qbk b/src/beast/doc/5_03_streams.qbk new file mode 100644 index 0000000000..31cd8abc65 --- /dev/null +++ b/src/beast/doc/5_03_streams.qbk @@ -0,0 +1,107 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section Message Stream Operations] + +Beast provides synchronous and asynchronous algorithms to parse and +serialize HTTP/1 wire format messages on streams. These functions form +the message-oriented stream interface: + +[table Message Stream Operations +[[Name][Description]] +[[ + [link beast.ref.beast__http__read.overload3 [*read]] +][ + Read a __message__ from a __SyncReadStream__. +]] +[[ + [link beast.ref.beast__http__async_read.overload2 [*async_read]] +][ + Read a __message__ from an __AsyncReadStream__. +]] +[[ + [link beast.ref.beast__http__write.overload1 [*write]] +][ + Write a __message__ to a __SyncWriteStream__. +]] +[[ + [link beast.ref.beast__http__async_write [*async_write]] +][ + Write a __message__ to an __AsyncWriteStream__. +]] +] + +All synchronous stream operations come in two varieties. One which throws +an exception upon error, and another which accepts as the last parameter an +argument of type [link beast.ref.beast__error_code `error_code&`]. If an error +occurs this argument will be set to contain the error code. + + + +[heading Reading] + +Because a serialized header is not length-prefixed, algorithms which +parse messages from a stream may read past the end of a message for +efficiency. To hold this surplus data, all stream read operations use +a passed-in __DynamicBuffer__ which must be persisted between calls. +Each read operation may consume bytes remaining in the buffer, and +leave behind new bytes. In this example we declare the buffer and a +message variable, then read a complete HTTP request synchronously: + +[http_snippet_4] + +This example uses __flat_buffer__. Beast's __basic_parser__ is +optimized for structured HTTP data located in a single contiguous +(['flat]) memory buffer. When not using a flat buffer the implementation +may perform an additional memory allocations to restructure the input +into a single buffer for parsing. + +[tip + Other Implementations of __DynamicBuffer__ may avoid parser + memory allocation by always returning buffer sequences of + length one. +] + +Messages may also be read asynchronously. When performing asynchronous +stream read operations the stream, buffer, and message variables must +remain valid until the operation has completed. Beast asynchronous +initiation functions use Asio's completion handler model. This call +reads a message asynchronously and report the error code upon +completion: + +[http_snippet_5] + +If a read stream algorithm cannot complete its operation without exceeding +the maximum specified size of the dynamic buffer provided, the error +[link beast.ref.beast__http__error `buffer_overflow`] +is returned. This may be used to impose a limit on the maximum size of an +HTTP message header for protection from buffer overflow attacks. The +following code will print the error message: + +[http_snippet_6] + + + +[heading Writing] + +A set of free functions allow serialization of an entire HTTP message to +a stream. If a response has no declared content length and no chunked +transfer encoding, then the end of the message is indicated by the server +closing the connection. When sending such a response, Beast will return the +[link beast.ref.beast__http__error `error::end_of_stream`] +from the write algorithm to indicate +to the caller that the connection should be closed. This example +constructs and sends a response whose body length is determined by +the number of octets received prior to the server closing the connection: + +[http_snippet_7] + +The asynchronous version could be used instead: + +[http_snippet_8] + +[endsect] diff --git a/src/beast/doc/5_04_serializer_streams.qbk b/src/beast/doc/5_04_serializer_streams.qbk new file mode 100644 index 0000000000..62a556ac29 --- /dev/null +++ b/src/beast/doc/5_04_serializer_streams.qbk @@ -0,0 +1,150 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section Serializer Stream Operations] + +Non-trivial algorithms need to do more than send entire messages +at once, such as: + +* Send the header first, and the body later. + +* Set chunk extensions or trailers using a chunk decorator. + +* Send a message incrementally: bounded work in each I/O cycle. + +* Use a series of caller-provided buffers to represent the body. + +These tasks may be performed by using the serializer stream interfaces. +To use these interfaces, first construct a suitable object with +the message to be sent: + +[table Serializer +[[Name][Description]] +[[ + __serializer__ +][ + ``` + /// Provides buffer oriented HTTP message serialization functionality. + template< + bool isRequest, + class Body, + class Fields = fields, + class ChunkDecorator = no_chunk_decorator + > + class serializer; + ``` +]] +[[ + [link beast.ref.beast__http__request_serializer `request_serializer`] +][ + ``` + /// A serializer for HTTP/1 requests + template< + class Body, + class Fields = fields, + class ChunkDecorator = no_chunk_decorator> + using request_serializer = serializer; + ``` +]] +[[ + [link beast.ref.beast__http__response_serializer `response_serializer`] +][ + ``` + /// A serializer for HTTP/1 responses + template< + class Body, + class Fields = fields, + class ChunkDecorator = no_chunk_decorator> + using response_serializer = serializer; + ``` +]] +] + +The choices for template types must match the message passed on construction. +This code creates an HTTP response and the corresponding serializer: + +[http_snippet_10] + +The stream operations which work on serializers are: + +[table Serializer Stream Operations +[[Name][Description]] +[[ + [link beast.ref.beast__http__write.overload1 [*write]] +][ + Send everything in a __serializer__ to a __SyncWriteStream__. +]] +[[ + [link beast.ref.beast__http__async_write.overload1 [*async_write]] +][ + Send everything in a __serializer__ asynchronously to an __AsyncWriteStream__. +]] +[[ + [link beast.ref.beast__http__write_header.overload1 [*write_header]] +][ + Send only the header from a __serializer__ to a __SyncWriteStream__. +]] +[[ + [link beast.ref.beast__http__async_write_header [*async_write_header]] +][ + Send only the header from a __serializer__ asynchronously to an __AsyncWriteStream__. +]] +[[ + [link beast.ref.beast__http__write_some.overload1 [*write_some]] +][ + Send part of a __serializer__ to a __SyncWriteStream__. +]] +[[ + [link beast.ref.beast__http__async_write_some [*async_write_some]] +][ + Send part of a __serializer__ asynchronously to an __AsyncWriteStream__. +]] +] + +Here is an example of using a serializer to send a message on a stream +synchronously. This performs the same operation as calling `write(stream, m)`: + +[http_snippet_12] + +[heading Chunk Decorators] + +When the message used to construct the serializer indicates the chunked +transfer encoding, the serializer will automatically generate the proper +encoding in the output buffers. __rfc7230__ defines additional fields +called the +[@https://tools.ietf.org/html/rfc7230#section-4.1.1 chunk extensions] +in chunks with body octets, and the +[@https://tools.ietf.org/html/rfc7230#section-4.1.2 chunked trailer part] +for the final chunk. Applications that wish to emit chunk extensions +and trailers may instantiate the serializer with a "chunk decorator" type, +and pass an instance of the type upon construction. This decorator is +a function object which, when invoked with a __ConstBufferSequence__, +returns a +[link beast.ref.beast__string_view `string_view`] containing either the extensions +or the trailer. For chunks containing body data, the passed buffer will +contain one or more corresponding body octets. The decorator may use this +information as needed. For example, to compute a digest on the data and +store it as a chunk extension. For the trailers, the serializer will +invoke the decorator with a buffer sequence of size zero. Or more +specifically, with an object of type +[@http://www.boost.org/doc/html/boost_asio/reference/null_buffers.html `boost::asio::null_buffers`]. + +For body chunks the string returned by the decorator must follow the +[@https://tools.ietf.org/html/rfc7230#section-4.1.1 correct syntax] +for the entire chunk extension. For the trailer, the returned string +should consist of zero or more lines ending in a CRLF and containing +a field name/value pair in the format prescribed by __rfc7230__. It +is the responsibility of the decorator to manage returned string buffers. +The implementation guarantees it will not reference previous strings +after subsequent calls. + +This defines a decorator which sets an extension variable `x` equal +to the size of the chunk in bytes, and returns a single trailer field: + +[http_snippet_17] + +[endsect] diff --git a/src/beast/doc/5_05_parser_streams.qbk b/src/beast/doc/5_05_parser_streams.qbk new file mode 100644 index 0000000000..465e1a9fb7 --- /dev/null +++ b/src/beast/doc/5_05_parser_streams.qbk @@ -0,0 +1,138 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section Parser Stream Operations] + +Non-trivial algorithms need to do more than receive entire messages +at once, such as: + + +* Receive the header first and body later. + +* Receive a large body using a fixed-size buffer. + +* Receive a message incrementally: bounded work in each I/O cycle. + +* Defer the commitment to a __Body__ type until after reading the header. + +These types of operations require callers to manage the lifetime of +associated state, by constructing a class derived from __basic_parser__. +Beast comes with the derived instance __parser__ which creates complete +__message__ objects using the __basic_fields__ Fields container. + +[table Parser +[[Name][Description]] +[[ + __parser__ +][ + ``` + /// An HTTP/1 parser for producing a message. + template< + bool isRequest, // `true` to parse an HTTP request + class Body, // The Body type for the resulting message + class Allocator = std::allocator> // The type of allocator for the header + class parser + : public basic_parser<...>; + ``` +]] +[[ + [link beast.ref.beast__http__request_parser `request_parser`] +][ + ``` + /// An HTTP/1 parser for producing a request message. + template> + using request_parser = parser; + ``` +]] +[[ + [link beast.ref.beast__http__response_parser `response_parser`] +][ + ``` + /// An HTTP/1 parser for producing a response message. + template> + using response_parser = parser; + ``` +]] +] + +[note + The __basic_parser__ and classes derived from it handle octet streams + serialized in the HTTP/1 format described in __rfc7230__. +] + +The stream operations which work on parsers are: + +[table Parser Stream Operations +[[Name][Description]] +[[ + [link beast.ref.beast__http__read.overload1 [*read]] +][ + Read everything into a parser from a __SyncWriteStream__. +]] +[[ + [link beast.ref.beast__http__async_read.overload1 [*async_read]] +][ + Read everything into a parser asynchronously from an __AsyncWriteStream__. +]] +[[ + [link beast.ref.beast__http__read_header.overload1 [*read_header]] +][ + Read only the header octets into a parser from a __SyncWriteStream__. +]] +[[ + [link beast.ref.beast__http__async_read_header [*async_read_header]] +][ + Read only the header octets into a parser asynchronously from an __AsyncWriteStream__. +]] +[[ + [link beast.ref.beast__http__read_some.overload1 [*read_some]] +][ + Read some octets into a parser from a __SyncReadStream__. +]] +[[ + [link beast.ref.beast__http__async_read_some [*async_read_some]] +][ + Read some octets into a parser asynchronously from an __AsyncWriteStream__. +]] +] + +As with message stream operations, parser stream operations require a +persisted __DynamicBuffer__ for holding unused octets from the stream. +The basic parser implementation is optimized for the case where this dynamic +buffer stores its input sequence in a single contiguous memory buffer. It is +advised to use an instance of __flat_buffer__, __static_buffer__, or +__static_buffer_n__ for this purpose, although a user defined instance of +__DynamicBuffer__ which produces input sequences of length one is also suitable. + +The parser contains a message constructed internally. Arguments passed +to the parser's constructor are forwarded into the message container. +The caller can access the message inside the parser by calling +[link beast.ref.beast__http__parser.get `parser::get`]. +If the `Fields` and `Body` types are [*MoveConstructible], the caller +can take ownership of the message by calling +[link beast.ref.beast__http__parser.release `parser::release`]. In this example +we read an HTTP response with a string body using a parser, then print +the response: + +[http_snippet_13] + + + +[section Incremental Read] + +This function uses +[link beast.ref.beast__http__buffer_body `buffer_body`] +and parser stream operations to read a message body progressively +using a small, fixed-size buffer: + +[example_incremental_read] + +[endsect] + + + +[endsect] diff --git a/src/beast/doc/5_06_serializer_buffers.qbk b/src/beast/doc/5_06_serializer_buffers.qbk new file mode 100644 index 0000000000..8e2446e3b9 --- /dev/null +++ b/src/beast/doc/5_06_serializer_buffers.qbk @@ -0,0 +1,79 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section Buffer-Oriented Serializing] +[block''''''] + +An instance of __serializer__ can be invoked directly, without using +the provided stream operations. This could be useful for implementing +algorithms on objects whose interface does not conform to __Stream__. +For example, a +[@https://github.com/libuv/libuv *libuv* socket]. +The serializer interface is interactive; the caller invokes it repeatedly +to produce buffers until all of the buffers have been generated. Then the +serializer is destroyed. + +To obtain the serialized next buffer sequence, call +[link beast.ref.beast__http__serializer.next `serializer::next`]. +Then, call +[link beast.ref.beast__http__serializer.consume `serializer::consume`] +to indicate the number of bytes consumed. This updates the next +set of buffers to be returned, if any. +`serializer::next` takes an error code parameter and invokes a visitor +argument with the error code and buffer of unspecified type. In C++14 +this is easily expressed with a generic lambda. The function +[link beast.ref.beast__http__serializer.is_done `serializer::is_done`] +will return `true` when all the buffers have been produced. This C++14 +example prints the buffers to standard output: + +[http_snippet_14] + +Generic lambda expressions are only available in C++14 or later. A functor +with a templated function call operator is necessary to use C++11 as shown: + +[http_snippet_15] + +[heading Split Serialization] + +In some cases, such as the handling of the +[@https://tools.ietf.org/html/rfc7231#section-5.1.1 Expect: 100-continue] +field, it may be desired to first serialize the header, perform some other +action, and then continue with serialization of the body. This is +accomplished by calling +[link beast.ref.beast__http__serializer.split `serializer::split`] +with a boolean indicating that when buffers are produced, the last buffer +containing serialized header octets will not contain any octets corresponding +to the body. The function +[link beast.ref.beast__http__serializer.is_header_done `serializer::is_header_done`] +informs the caller whether the header been serialized fully. In this +C++14 example we print the header first, followed by the body: + +[http_snippet_16] + + + +[section Write To std::ostream] + +The standard library provides the type `std::ostream` for performing high +level write operations on character streams. The variable `std::cout` is +based on this output stream. This example uses the buffer oriented interface +of __serializer__ to write an HTTP message to a `std::ostream`: + +[example_http_write_ostream] + +[tip + Serializing to a `std::ostream` could be implemented using an alternate + strategy: adapt the `std::ostream` interface to a __SyncWriteStream__, + enabling use with the library's existing stream algorithms. This is + left as an exercise for the reader. +] + +[endsect] + + + +[endsect] diff --git a/src/beast/doc/5_07_parser_buffers.qbk b/src/beast/doc/5_07_parser_buffers.qbk new file mode 100644 index 0000000000..11be8576c4 --- /dev/null +++ b/src/beast/doc/5_07_parser_buffers.qbk @@ -0,0 +1,107 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section Buffer-Oriented Parsing] +[block''''''] + +A subclass of __basic_parser__ can be invoked directly, without using +the provided stream operations. This could be useful for implementing +algorithms on objects whose interface does not conform to __Stream__. +For example, a +[@http://zeromq.org/ *ZeroMQ* socket]. +The basic parser interface is interactive; the caller invokes the function +[link beast.ref.beast__http__basic_parser.put `basic_parser::put`] +repeatedly with buffers until an error occurs or the parsing is done. The +function +[link beast.ref.beast__http__basic_parser.put_eof `basic_parser::put_eof`] +Is used when the caller knows that there will never be more data (for example, +if the underlying connection is closed), + +[heading Parser Options] + +The parser provides a few options which may be set before parsing begins: + +[table Parser Options +[[Name][Default][Description]] +[[ + [link beast.ref.beast__http__basic_parser.eager.overload2 `eager`] +][ + `false` +][ + Normally the parser returns after successfully parsing a structured + element (header, chunk header, or chunk body) even if there are octets + remaining in the input. This is necessary when attempting to parse the + header first, or when the caller wants to inspect information which may + be invalidated by subsequent parsing, such as a chunk extension. The + `eager` option controls whether the parser keeps going after parsing + structured element if there are octets remaining in the buffer and no + error occurs. This option is automatically set or cleared during certain + stream operations to improve performance with no change in functionality. +]] +[[ + [link beast.ref.beast__http__basic_parser.skip.overload2 `skip`] +][ + `false` +][ + This option controls whether or not the parser expects to see an HTTP + body, regardless of the presence or absence of certain fields such as + Content-Length or a chunked Transfer-Encoding. Depending on the request, + some responses do not carry a body. For example, a 200 response to a + [@https://tools.ietf.org/html/rfc7231#section-4.3.6 CONNECT] request + from a tunneling proxy, or a response to a + [@https://tools.ietf.org/html/rfc7231#section-4.3.2 HEAD] request. + In these cases, callers may use this function inform the parser that + no body is expected. The parser will consider the message complete + after the header has been received. +]] +[[ + [link beast.ref.beast__http__basic_parser.body_limit `body_limit`] +][ + 1MB/8MB +][ + This function sets the maximum allowed size of the content body. + When a body larger than the specified size is detected, an error + is generated and parsing terminates. This setting helps protect + servers from resource exhaustion attacks. The default limit when + parsing requests is 1MB, and for parsing responses 8MB. +]] +[[ + [link beast.ref.beast__http__basic_parser.header_limit `header_limit`] +][ + 8KB +][ + This function sets the maximum allowed size of the header + including all field name, value, and delimiter characters + and also including the CRLF sequences in the serialized + input. +]] +] + + + +[section Read From std::istream] + +The standard library provides the type `std::istream` for performing high +level read operations on character streams. The variable `std::cin` is based +on this input stream. This example uses the buffer oriented interface of +__basic_parser__ to build a stream operation which parses an HTTP message +from a `std::istream`: + +[example_http_read_istream] + +[tip + Parsing from a `std::istream` could be implemented using an alternate + strategy: adapt the `std::istream` interface to a __SyncReadStream__, + enabling use with the library's existing stream algorithms. This is + left as an exercise for the reader. +] + +[endsect] + + + +[endsect] diff --git a/src/beast/doc/5_08_custom_body.qbk b/src/beast/doc/5_08_custom_body.qbk new file mode 100644 index 0000000000..b209ac5d03 --- /dev/null +++ b/src/beast/doc/5_08_custom_body.qbk @@ -0,0 +1,158 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section Custom Body Types] +[block''''''] + +User-defined types are possible for the message body, where the type meets the +__Body__ requirements. This simplified class declaration +shows the customization points available to user-defined body types: +``` +/// Defines a Body type +struct body +{ + /// This determines the type of the `message::body` member + using value_type = ...; + + /// An optional function, returns the body's payload size + static + std::uint64_t + size(value_type const& v); + + /// The algorithm used for extracting buffers + class reader; + + /// The algorithm used for inserting buffers + class writer; +} +``` + +The meaning of the nested types is as follows + +[table Body Type Members +[[Name][Description]] +[ + [`value_type`] + [ + Determines the type of the + [link beast.ref.beast__http__message.body `message::body`] + member. + ] +][ + [`reader`] + [ + An optional nested type meeting the requirements of __BodyReader__, + which provides the algorithm for converting the body representation + to a forward range of buffer sequences. + If present this body type may be used with a __serializer__. + ] +][ + [`writer`] + [ + An optional nested type meeting the requirements of __BodyWriter__, + which provides the algorithm for storing a forward range of buffer + sequences in the body representation. + If present, this body type may be used with a __parser__. + ] +] +] + +[heading Value Type] + +The `value_type` nested type allows the body to define the declaration of +the body type as it appears in the message. This can be any type. For +example, a body's value type may specify `std::vector` or even +`std::list`. A custom body may even set the value type to +something that is not a container for body octets, such as a +[@http://www.boost.org/libs/filesystem/doc/reference.html#class-path `boost::filesystem::path`]. +Or, a more structured container may be chosen. This declares a body's +value type as a JSON tree structure produced from a +[@http://www.boost.org/doc/html/property_tree/parsers.html#property_tree.parsers.json_parser `json_parser`]: +``` +#include +#include + +struct Body +{ + using value_type = boost::property_tree::ptree; + + class reader; + + class writer; + + // Optional member + static + std::uint64_t + size(value_type const&); +}; +``` + +As long as a suitable reader or writer is available to provide the +algorithm for transferring buffers in and out of the value type, +those bodies may be serialized or parsed. + + + +[section File Body] + +Use of the flexible __Body__ concept customization point enables authors to +preserve the self-contained nature of the __message__ object while allowing +domain specific behaviors. Common operations for HTTP servers include sending +responses which deliver file contents, and allowing for file uploads. In this +example we build the `basic_file_body` type which supports both reading and +writing to a file on the file system. The interface is a class templated +on the type of file used to access the file system, which must meet the +requirements of __File__. + +First we declare the type with its nested types: + +[example_http_file_body_1] + +We will start with the definition of the `value_type`. Our strategy +will be to store the file object directly in the message container +through the `value_type` field. To use this body it will be necessary +to call `msg.body.file().open()` first with the required information +such as the path and open mode. This ensures that the file exists +throughout the operation and prevent the race condition where the +file is removed from the file system in between calls. + +[example_http_file_body_2] + +Our implementation of __BodyReader__ will contain a small buffer +from which the file contents are read. The buffer is provided to +the implementation on each call until everything has been read in. + +[example_http_file_body_3] + +And here are the definitions for the functions we have declared: + +[example_http_file_body_4] + +Files can be read now, and the next step is to allow writing to files +by implementing the __BodyWriter__. The style is similar to the reader, +except that buffers are incoming instead of outgoing. Here's the +declaration: + +[example_http_file_body_5] + +Finally, here is the implementation of the writer member functions: + +[example_http_file_body_6] + +We have created a full featured body type capable of reading and +writing files on the filesystem, integrating seamlessly with the +HTTP algorithms and message container. The body type works with +any file implementation meeting the requirements of __File__ so +it may be transparently used with solutions optimized for particular +platforms. Example HTTP servers which use file bodies are available +in the example directory. + +[endsect] + + + +[endsect] diff --git a/src/beast/doc/5_09_custom_parsers.qbk b/src/beast/doc/5_09_custom_parsers.qbk new file mode 100644 index 0000000000..610dc16739 --- /dev/null +++ b/src/beast/doc/5_09_custom_parsers.qbk @@ -0,0 +1,39 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section Custom Parsers] + +While the parsers included in the library will handle a broad number of +use-cases, the __basic_parser__ interface can be subclassed to implement +custom parsing strategies: the basic parser processes the incoming octets +into elements according to the HTTP/1 protocol specification, while the +derived class decides what to do with those elements. In particular, users +who create exotic containers for [*Fields] may need to also create their +own parser. Custom parsers will work with all of the stream read operations +that work on parsers, as those algorithms use only the basic parser +interface. Some use cases for implementing custom parsers are: + +* Inspect incoming header fields and keep or discard them. + +* Use a container provided by an external interface. + +* Store header data in a user-defined __Fields__ type. + +The basic parser uses the Curiously Recurring Template Pattern +([@https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern CRTP]). +To declare your user defined parser, derive it from __basic_parser__. +The interface to the parser is event-driven. Member functions of the derived +class (termed "callbacks" in this context) are invoked with parsed elements +as they become available, requiring either the `friend` declaration as shown +above or that the member functions are declared public (not recommended). +Buffers provided by the parser are non-owning references; it is the +responsibility of the derived class to copy any information it needs before +returning from the callback. + +[example_http_custom_parser] + +[endsect] diff --git a/src/beast/doc/6_0_http_examples.qbk b/src/beast/doc/6_0_http_examples.qbk new file mode 100644 index 0000000000..de7b50618f --- /dev/null +++ b/src/beast/doc/6_0_http_examples.qbk @@ -0,0 +1,143 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section More Examples] + +These examples in this section are working functions that may be found +in the examples directory. They demonstrate the usage of the library for +a variety of scenarios. + + + +[section Change Body Type] + +Sophisticated servers may wish to defer the choice of the Body template type +until after the header is available. Then, a body type may be chosen +depending on the header contents. For example, depending on the verb, +target path, or target query parameters. To accomplish this, a parser +is declared to read in the header only, using a trivial body type such as +[link beast.ref.beast__http__empty_body `empty_body`]. Then, a new parser is constructed +from this existing parser where the body type is conditionally determined +by information from the header or elsewhere. + +This example illustrates how a server may make the commitment of a body +type depending on the method verb: + +[example_http_defer_body] + +[endsect] + + + +[section Expect 100-continue (Client)] + +The Expect field with the value "100-continue" in a request is special. It +indicates that the after sending the message header, a client desires an +immediate informational response before sending the the message body, which +presumably may be expensive to compute or large. This behavior is described in +[@https://tools.ietf.org/html/rfc7231#section-5.1.1 rfc7231 section 5.1.1]. +Invoking the 100-continue behavior is implemented easily in a client by +constructing a __serializer__ to send the header first, then receiving +the server response, and finally conditionally send the body using the same +serializer instance. A synchronous, simplified version (no timeout) of +this client action looks like this: + +[example_http_send_expect_100_continue] + +[endsect] + + + +[section Expect 100-continue (Server)] + +The Expect field with the value "100-continue" in a request is special. It +indicates that the after sending the message header, a client desires an +immediate informational response before sending the the message body, which +presumably may be expensive to compute or large. This behavior is described in +[@https://tools.ietf.org/html/rfc7231#section-5.1.1 rfc7231 section 5.1.1]. +Handling the Expect field can be implemented easily in a server by constructing +a __parser__ to read the header first, then send an informational HTTP +response, and finally read the body using the same parser instance. A +synchronous version of this server action looks like this: + +[example_http_receive_expect_100_continue] + +[endsect] + + + +[section HEAD request (Client)] + +The +[@https://tools.ietf.org/html/rfc7231#section-4.3.2 HEAD request] +method indicates to the server that the client wishes to receive the +entire header that would be delivered if the method was GET, except +that the body is omitted. + +[example_http_do_head_request] + +[endsect] + + + +[section HEAD response (Server)] + +When a server receives a +[@https://tools.ietf.org/html/rfc7231#section-4.3.2 HEAD request], +the response should contain the entire header that would be delivered +if the method was GET, except that the body is omitted. + +[example_http_do_head_response] + +[endsect] + + + +[section HTTP Relay] + +An HTTP proxy acts as a relay between client and server. The proxy reads a +request from the client and sends it to the server, possibly adjusting some +of the headers and representation of the body along the way. Then, the +proxy reads a response from the server and sends it back to the client, +also with the possibility of changing the headers and body representation. + +The example that follows implements a synchronous HTTP relay. It uses a +fixed size buffer, to avoid reading in the entire body so that the upstream +connection sees a header without unnecessary latency. This example brings +together all of the concepts discussed so far, it uses both a __serializer__ +and a __parser__ to achieve its goal: + +[example_http_relay] + +[endsect] + + + +[section Send Child Process Output] + +Sometimes it is necessary to send a message whose body is not conveniently +described by a single container. For example, when implementing an HTTP relay +function a robust implementation needs to present body buffers individually +as they become available from the downstream host. These buffers should be +fixed in size, otherwise creating the unnecessary and inefficient burden of +reading the complete message body before forwarding it to the upstream host. + +To enable these use-cases, the body type __buffer_body__ is provided. This +body uses a caller-provided pointer and size instead of an owned container. +To use this body, instantiate an instance of the serializer and fill in +the pointer and size fields before calling a stream write function. + +This example reads from a child process and sends the output back in an +HTTP response. The output of the process is sent as it becomes available: + +[example_http_send_cgi_response] + +[endsect] + + + +[endsect] diff --git a/src/beast/doc/7_0_websocket.qbk b/src/beast/doc/7_0_websocket.qbk new file mode 100644 index 0000000000..7e2e03a5a3 --- /dev/null +++ b/src/beast/doc/7_0_websocket.qbk @@ -0,0 +1,38 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section Using WebSocket] + +The WebSocket Protocol enables two-way communication between a client +running untrusted code in a controlled environment to a remote host that has +opted-in to communications from that code. The protocol consists of an opening +handshake followed by basic message framing, layered over TCP. The goal of +this technology is to provide a mechanism for browser-based applications +needing two-way communication with servers without relying on opening multiple +HTTP connections. + +Beast provides developers with a robust WebSocket implementation built on +Boost.Asio with a consistent asynchronous model using a modern C++ approach. + +[note + This documentation assumes familiarity with __Asio__ and + the protocol specification described in __rfc6455__. + Sample code and identifiers appearing in this section is written + as if these declarations are in effect: + + [ws_snippet_1] +] + +[include 7_1_streams.qbk] +[include 7_2_connect.qbk] +[include 7_3_client.qbk] +[include 7_4_server.qbk] +[include 7_5_messages.qbk] +[include 7_6_control.qbk] +[include 7_7_notes.qbk] + +[endsect] diff --git a/src/beast/doc/7_1_streams.qbk b/src/beast/doc/7_1_streams.qbk new file mode 100644 index 0000000000..002f09321a --- /dev/null +++ b/src/beast/doc/7_1_streams.qbk @@ -0,0 +1,64 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section Creating Streams] + +The interface to the WebSocket implementation is a single template class +[link beast.ref.beast__websocket__stream `stream`] +which wraps an existing network transport object or other type of +octet oriented stream. The wrapped object is called the "next layer" +and must meet the requirements of __SyncStream__ if synchronous +operations are performed, __AsyncStream__ if asynchronous operations +are performed, or both. Any arguments supplied during construction of +the stream wrapper are passed to next layer's constructor. + +Here we declare a websocket stream over a TCP/IP socket with ownership +of the socket. The `io_service` argument is forwarded to the wrapped +socket's constructor: + +[ws_snippet_2] + +[heading Using SSL] + +To use WebSockets over SSL, use an instance of the `boost::asio::ssl::stream` +class template as the template type for the stream. The required `io_service` +and `ssl::context` arguments are forwarded to the wrapped stream's constructor: + +[wss_snippet_1] +[wss_snippet_2] + +[important + Code which declares websocket stream objects using Asio SSL types + must include the file [include_file beast/websocket/ssl.hpp]. +] + +[heading Non-owning References] + +If a socket type supports move construction, a websocket stream may be +constructed around the already existing socket by invoking the move +constructor signature: + +[ws_snippet_3] + +Or, the wrapper can be constructed with a non-owning reference. In +this case, the caller is responsible for managing the lifetime of the +underlying socket being wrapped: + +[ws_snippet_4] + +Once the WebSocket stream wrapper is created, the wrapped object may be +accessed by calling +[link beast.ref.beast__websocket__stream.next_layer.overload1 `stream::next_layer`]: + +[ws_snippet_5] + +[warning + Initiating operations on the next layer while websocket + operations are being performed may result in undefined behavior. +] + +[endsect] diff --git a/src/beast/doc/7_2_connect.qbk b/src/beast/doc/7_2_connect.qbk new file mode 100644 index 0000000000..a7f8f233a5 --- /dev/null +++ b/src/beast/doc/7_2_connect.qbk @@ -0,0 +1,32 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section Establishing Connections] + +Connections are established by invoking functions directly on the next layer +object. For example, to make an outgoing connection using a standard TCP/IP +socket: + +[ws_snippet_6] + +Similarly, to accept an incoming connection using a standard TCP/IP +socket, pass the next layer object to the acceptor: + +[ws_snippet_7] + +When using SSL, which itself wraps a next layer object that is usually a +TCP/IP socket, multiple calls to retrieve the next layer may be required. +In this example, the websocket stream wraps the SSL stream which wraps +the TCP/IP socket: + +[wss_snippet_3] + +[note + Examples use synchronous interfaces for clarity of exposition. +] + +[endsect] diff --git a/src/beast/doc/7_3_client.qbk b/src/beast/doc/7_3_client.qbk new file mode 100644 index 0000000000..65a0e651c4 --- /dev/null +++ b/src/beast/doc/7_3_client.qbk @@ -0,0 +1,95 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section Handshaking (Clients)] + +A WebSocket session begins when a client sends the HTTP/1 +[@https://tools.ietf.org/html/rfc7230#section-6.7 Upgrade] +request for +[@https://tools.ietf.org/html/rfc6455#section-1.3 websocket], +and the server sends an appropriate response indicating that +the request was accepted and that the connection has been upgraded. +The Upgrade request must include the +[@https://tools.ietf.org/html/rfc7230#section-5.4 Host] +field, and the +[@https://tools.ietf.org/html/rfc7230#section-5.3 target] +of the resource to request. The stream member functions +[link beast.ref.beast__websocket__stream.handshake.overload1 `handshake`] and +[link beast.ref.beast__websocket__stream.async_handshake.overload1 `async_handshake`] +are used to send the request with the required host and target strings. + +[ws_snippet_8] + +The implementation will create and send a request that typically +looks like this: + +[table WebSocket Upgrade HTTP Request +[[Serialized Octets][Description]] +[[ +``` + GET / HTTP/1.1 + Host: localhost + Upgrade: websocket + Connection: upgrade + Sec-WebSocket-Key: 2pGeTR0DsE4dfZs2pH+8MA== + Sec-WebSocket-Version: 13 + User-Agent: Beast +``` +][ + The host and target parameters become part of the Host field + and request-target in the resulting HTTP request. The key is + generated by the implementation. Callers may add fields or + modify fields by providing a ['decorator], described below. +]]] + +[heading Decorators] + +If the caller wishes to add or modify fields, the member functions +[link beast.ref.beast__websocket__stream.handshake_ex `handshake_ex`] and +[link beast.ref.beast__websocket__stream.async_handshake_ex `async_handshake_ex`] +are provided which allow an additional function object, called a +['decorator], to be passed. The decorator is invoked to modify +the HTTP Upgrade request as needed. This example sets a subprotocol +on the request: + +[ws_snippet_9] + +The HTTP Upgrade request produced by the previous call will look thusly: + +[table Decorated WebSocket Upgrade HTTP Request +[[Serialized Octets][Description]] +[[ + ``` + GET / HTTP/1.1 + Host: localhost + Upgrade: websocket + Connection: upgrade + Sec-WebSocket-Key: 2pGeTR0DsE4dfZs2pH+8MA== + Sec-WebSocket-Version: 13 + Sec-WebSocket-Protocol: xmpp;ws-chat + User-Agent: Beast + ``` +][ + Undefined behavior results if the decorator modifies the fields + specific to perform the WebSocket Upgrade , such as the Upgrade + and Connection fields. +]]] + +[heading Filtering] + +When a client receives an HTTP Upgrade response from the server indicating +a successful upgrade, the caller may wish to perform additional validation +on the received HTTP response message. For example, to check that the +response to a basic authentication challenge is valid. To achieve this, +overloads of the handshake member function allow the caller to store the +received HTTP message in an output reference argument as +[link beast.ref.beast__websocket__response_type `response_type`] +as follows: + +[ws_snippet_10] + +[endsect] diff --git a/src/beast/doc/7_4_server.qbk b/src/beast/doc/7_4_server.qbk new file mode 100644 index 0000000000..f32b3105e1 --- /dev/null +++ b/src/beast/doc/7_4_server.qbk @@ -0,0 +1,116 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section Handshaking (Servers)] + +A +[link beast.ref.beast__websocket__stream `stream`] +automatically handles receiving and processing the HTTP response to the +handshake request. The call to handshake is successful if a HTTP response +is received with the 101 "Switching Protocols" status code. On failure, +an error is returned or an exception is thrown. Depending on the keep alive +setting, the connection may remain open for a subsequent handshake attempt. + +Performing a handshake for an incoming websocket upgrade request operates +similarly. If the handshake fails, an error is returned or exception thrown: + +[ws_snippet_11] + +Successful WebSocket Upgrade responses generated by the implementation will +typically look like this: + +[table Decorated WebSocket Upgrade HTTP Request +[[Serialized Octets][Description]] +[[ + ``` + HTTP/1.1 101 Switching Protocols + Upgrade: websocket + Connection: upgrade + Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= + Server: Beast/40 + ``` +][ + The Sec-WebSocket-Accept field value is generated from the + request in a fashion specified by the WebSocket protocol. +]]] + +[heading Decorators] + +If the caller wishes to add or modify fields, the member functions +[link beast.ref.beast__websocket__stream.accept_ex `accept_ex`] and +[link beast.ref.beast__websocket__stream.async_accept_ex `async_accept_ex`] +are provided which allow an additional function object, called a +['decorator], to be passed. The decorator is invoked to modify +the HTTP Upgrade request as needed. This example sets the Server +field on the response: + +[ws_snippet_12] + +The HTTP Upgrade response produced by the previous call looks like this: + +[table Decorated WebSocket Upgrade HTTP Request +[[Serialized Octets][Description]] +[[ + ``` + HTTP/1.1 101 Switching Protocols + Upgrade: websocket + Connection: upgrade + Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= + Server: AcmeServer + ``` +][ + When the Upgrade request fails, the implementation will still invoke + the decorator to modify the response. In this case, the response + object will have a status code other than 101. + + Undefined behavior results when the upgrade request is successful + and the decorator modifies the fields specific to perform the + WebSocket Upgrade, such as the Upgrade and Connection fields. +]]] + +[heading Passing HTTP Requests] + +When implementing an HTTP server that also supports WebSocket, the +server usually reads the HTTP request from the client. To detect when +the incoming HTTP request is a WebSocket Upgrade request, the function +[link beast.ref.beast__websocket__is_upgrade `is_upgrade`] may be used. + +Once the caller determines that the HTTP request is a WebSocket Upgrade, +additional overloads of +[link beast.ref.beast__websocket__stream.accept `accept`], +[link beast.ref.beast__websocket__stream.accept_ex `accept_ex`], +[link beast.ref.beast__websocket__stream.async_accept `async_accept`], and +[link beast.ref.beast__websocket__stream.async_accept_ex `async_accept_ex`] +are provided which receive the entire HTTP request header as an object +to perform the handshake. In this example, the request is first read +in using the HTTP algorithms, and then passed to a newly constructed +stream: + +[ws_snippet_13] + +[heading Buffered Handshakes] + +Sometimes a server implementation wishes to read octets on the stream +in order to route the incoming request. For example, a server may read +the first 6 octets after accepting an incoming connection to determine +if a TLS protocol is being negotiated, and choose a suitable implementation +at run-time. In the case where the server wishes to accept the incoming +request as an HTTP WebSocket Upgrade request, additional overloads of +[link beast.ref.beast__websocket__stream.accept `accept`], +[link beast.ref.beast__websocket__stream.accept_ex `accept_ex`], +[link beast.ref.beast__websocket__stream.async_accept `async_accept`], and +[link beast.ref.beast__websocket__stream.async_accept_ex `async_accept_ex`] +are provided which receive the additional buffered octets and consume +them as part of the handshake. + +In this example, the server reads the initial HTTP message into the +specified dynamic buffer as an octet sequence in the buffer's output +area, and later uses those octets to attempt an HTTP WebSocket Upgrade: + +[ws_snippet_14] + +[endsect] diff --git a/src/beast/doc/7_5_messages.qbk b/src/beast/doc/7_5_messages.qbk new file mode 100644 index 0000000000..9518d37a64 --- /dev/null +++ b/src/beast/doc/7_5_messages.qbk @@ -0,0 +1,36 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section Send and Receive Messages] + +After the WebSocket handshake is accomplished, callers may send and receive +messages using the message oriented interface. This interface requires that +all of the buffers representing the message are known ahead of time: + +[ws_snippet_15] + +[important + Calls to [link beast.ref.beast__websocket__stream.set_option `set_option`] + must be made from the same implicit or explicit strand as that used + to perform other operations. +] + +[heading Frames] + +Some use-cases make it impractical or impossible to buffer the entire +message ahead of time: + +* Streaming multimedia to an endpoint. +* Sending a message that does not fit in memory at once. +* Providing incremental results as they become available. + +For these cases, the frame oriented interface may be used. This +example reads and echoes a complete message using this interface: + +[ws_snippet_16] + +[endsect] diff --git a/src/beast/doc/7_6_control.qbk b/src/beast/doc/7_6_control.qbk new file mode 100644 index 0000000000..fb1de84dcb --- /dev/null +++ b/src/beast/doc/7_6_control.qbk @@ -0,0 +1,113 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section Control Frames] + +Control frames are small (less than 128 bytes) messages entirely contained +in an individual WebSocket frame. They may be sent at any time by either +peer on an established connection, and can appear in between continuation +frames for a message. There are three types of control frames: ping, pong, +and close. + +A sent ping indicates a request that the sender wants to receive a pong. A +pong is a response to a ping. Pongs may be sent unsolicited, at any time. +One use for an unsolicited pong is to inform the remote peer that the +session is still active after a long period of inactivity. A close frame +indicates that the remote peer wishes to close the WebSocket connection. +The connection is considered gracefully closed when each side has sent +and received a close frame. + +During read operations, Beast automatically reads and processes control +frames. If a control callback is registered, the callback is notified of +the incoming control frame. The implementation will respond to pings +automatically. The receipt of a close frame initiates the WebSocket +close procedure, eventually resulting in the error code +[link beast.ref.beast__websocket__error `error::closed`] +being delivered to the caller in a subsequent read operation, assuming +no other error takes place. + +A consequence of this automatic behavior is that caller-initiated read +operations can cause socket writes. However, these writes will not +compete with caller-initiated write operations. For the purposes of +correctness with respect to the stream invariants, caller-initiated +read operations still only count as a read. This means that callers can +have a simultaneously active read, write, and ping/pong operation in +progress, while the implementation also automatically handles control +frames. + +[heading Control Callback] + +Ping, pong, and close messages are control frames which may be sent at +any time by either peer on an established WebSocket connection. They +are sent using the functions +[link beast.ref.beast__websocket__stream.ping `ping`], +[link beast.ref.beast__websocket__stream.pong `pong`]. +and +[link beast.ref.beast__websocket__stream.close `close`]. +To be notified of control frames, callers may register a +['control callback] using +[link beast.ref.beast__websocket__stream.control_callback `control_callback`]. +The object provided with this option should be callable with the following +signature: + +[ws_snippet_17] + +When a control callback is registered, it will be invoked for all pings, +pongs, and close frames received through either synchronous read functions +or asynchronous read functions. The type of frame and payload text are +passed as parameters to the control callback. If the frame is a close +frame, the close reason may be obtained by calling +[link beast.ref.beast__websocket__stream.reason `reason`]. + +Unlike regular completion handlers used in calls to asynchronous initiation +functions, the control callback only needs to be set once. The callback is +not reset after being called. The same callback is used for both synchronous +and asynchronous reads. The callback is passive; in order to be called, +a stream read operation must be active. + +[note + When an asynchronous read function receives a control frame, the + control callback is invoked in the same manner as that used to + invoke the final completion handler of the corresponding read + function. +] + +[heading Close Frames] + +The WebSocket protocol defines a procedure and control message for initiating +a close of the session. Handling of close initiated by the remote end of the +connection is performed automatically. To manually initiate a close, use +the +[link beast.ref.beast__websocket__stream.close `close`] function: + +[ws_snippet_18] + +When the remote peer initiates a close by sending a close frame, Beast +will handle it for you by causing the next read to return `error::closed`. +When this error code is delivered, it indicates to the application that +the WebSocket connection has been closed cleanly, and that the TCP/IP +connection has been closed. After initiating a close, it is necessary to +continue reading messages until receiving the error `error::closed`. This +is because the remote peer may still be sending message and control frames +before it receives and responds to the close frame. + +[important + To receive the + [link beast.ref.beast__websocket__error `error::closed`] + error, a read operation is required. +] + +[heading Auto-fragment] + +To ensure timely delivery of control frames, large messages can be broken up +into smaller sized frames. The automatic fragment option turns on this +feature, and the write buffer size option determines the maximum size of +the fragments: + +[ws_snippet_19] + +[endsect] diff --git a/src/beast/doc/7_7_notes.qbk b/src/beast/doc/7_7_notes.qbk new file mode 100644 index 0000000000..2444072c8d --- /dev/null +++ b/src/beast/doc/7_7_notes.qbk @@ -0,0 +1,55 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section Notes] + +Because calls to read data may return a variable amount of bytes, the +interface to calls that read data require an object that meets the requirements +of __DynamicBuffer__. This concept is modeled on __streambuf__. + +The implementation does not perform queueing or buffering of messages. If +desired, these features should be provided by callers. The impact of this +design is that library users are in full control of the allocation strategy +used to store data and the back-pressure applied on the read and write side +of the underlying TCP/IP connection. + +[heading Asynchronous Operations] + +Asynchronous versions are available for all functions: + +[ws_snippet_20] + +Calls to asynchronous initiation functions support the extensible asynchronous +model developed by the Boost.Asio author, allowing for traditional completion +handlers, stackful or stackless coroutines, and even futures: + +[ws_snippet_21] + +[heading The io_service] + +The creation and operation of the __io_service__ associated with the +underlying stream is left to the callers, permitting any implementation +strategy including one that does not require threads for environments +where threads are unavailable. Beast WebSocket itself does not use +or require threads. + +[heading Thread Safety] + +Like a regular __Asio__ socket, a +[link beast.ref.beast__websocket__stream `stream`] +is not thread safe. Callers are responsible for synchronizing operations on +the socket using an implicit or explicit strand, as per the Asio documentation. +The asynchronous interface supports one active read and one active write +simultaneously. Undefined behavior results if two or more reads or two or +more writes are attempted concurrently. Caller initiated WebSocket ping, pong, +and close operations each count as an active write. + +The implementation uses composed asynchronous operations internally; a high +level read can cause both reads and writes to take place on the underlying +stream. This behavior is transparent to callers. + +[endsect] diff --git a/src/beast/doc/8_concepts.qbk b/src/beast/doc/8_concepts.qbk new file mode 100644 index 0000000000..7d1f23831b --- /dev/null +++ b/src/beast/doc/8_concepts.qbk @@ -0,0 +1,22 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section:concept Concepts] + +This section describes all of the concepts defined by the library. + +[include concept/Body.qbk] +[include concept/BodyReader.qbk] +[include concept/BodyWriter.qbk] +[include concept/BufferSequence.qbk] +[include concept/DynamicBuffer.qbk] +[include concept/Fields.qbk] +[include concept/FieldsReader.qbk] +[include concept/File.qbk] +[include concept/Streams.qbk] + +[endsect] diff --git a/src/beast/doc/9_0_design.qbk b/src/beast/doc/9_0_design.qbk new file mode 100644 index 0000000000..8b4c0a0dfc --- /dev/null +++ b/src/beast/doc/9_0_design.qbk @@ -0,0 +1,58 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section Design Choices] + +The implementations were originally driven by business needs of cryptocurrency +server applications (e.g. [@https://github.com/ripple/rippled rippled]), +written in C++. These needs were not met by existing solutions so Beast +was written from scratch as a solution. Beast's design philosophy avoids +flaws exhibited by other libraries: + +* Don't try to do too much. + +* Don't sacrifice performance. + +* Mimic __Asio__; familiarity breeds confidence. + +* Role-symmetric interfaces; client and server the same (or close to it). + +* Leave important decisions, such as allocating memory or + managing flow control, to the user. + +Beast uses the __DynamicBuffer__ concept presented in the Networking TS +(__N4588__), and relies heavily on the __ConstBufferSequence__ and +__MutableBufferSequence__ concepts for passing buffers to functions. +The authors have found the dynamic buffer and buffer sequence interfaces to +be optimal for interacting with Asio, and for other tasks such as incremental +parsing of data in buffers (for example, parsing websocket frames stored +in a [link beast.ref.beast__static_buffer `static_buffer`]). + +During the development of Beast the authors have studied other software +packages and in particular the comments left during the Boost Review process +of other packages offering similar functionality. In this section and the +FAQs that follow we attempt to answer those questions that are also applicable +to Beast. + +For HTTP we model the message to maximize flexibility of implementation +strategies while allowing familiar verbs such as [*`read`] and [*`write`]. +The HTTP interface is further driven by the needs of the WebSocket module, +as a WebSocket session requires a HTTP Upgrade handshake exchange at the +start. Other design goals: + +* Keep it simple. + +* Stay low level; don't invent a whole web server or client. + +* Allow for customizations, if the user needs it. + +[include 9_1_http_message.qbk] +[include 9_2_http_comparison.qbk] +[include 9_3_websocket_zaphoyd.qbk] +[include 9_4_faq.qbk] + +[endsect] diff --git a/src/beast/doc/9_1_http_message.qbk b/src/beast/doc/9_1_http_message.qbk new file mode 100644 index 0000000000..af1f36c67c --- /dev/null +++ b/src/beast/doc/9_1_http_message.qbk @@ -0,0 +1,340 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section HTTP Message Container] + +In this section we describe the problem of modeling HTTP messages and explain +how the library arrived at its solution, with a discussion of the benefits +and drawbacks of the design choices. The goal for creating a message model +is to create a container with value semantics, possibly movable and/or +copyable, that contains all the information needed to serialize, or all +of the information captured during parsing. More formally, given: + +* `m` is an instance of an HTTP message container + +* `x` is a series of octets describing a valid HTTP message in + the serialized format described in __rfc7230__. + +* `S(m)` is a serialization function which produces a series of octets + from a message container. + +* `P(x)` is a parsing function which produces a message container from + a series of octets. + +These relations are true: + +* `S(m) == x` + +* `P(S(m)) == m` + +We would also like our message container to have customization points +permitting the following: allocator awareness, user-defined containers +to represent header fields, and user-defined types and algorithms to +represent the body. And finally, because requests and responses have +different fields in the ['start-line], we would like the containers for +requests and responses to be represented by different types for function +overloading. + +Here is our first attempt at declaring some message containers: + +[table +[[ +``` +/// An HTTP request +template +struct request +{ + int version; + std::string method; + std::string target; + Fields fields; + + typename Body::value_type body; +}; +``` +][ +``` +/// An HTTP response +template +struct response +{ + int version; + int status; + std::string reason; + Fields fields; + + typename Body::value_type body; +}; +``` +]] +] + +These containers are capable of representing everything in the model +of HTTP requests and responses described in __rfc7230__. Request and +response objects are different types. The user can choose the container +used to represent the fields. And the user can choose the [*Body] type, +which is a concept defining not only the type of `body` member but also +the algorithms used to transfer information in and out of that member +when performing serialization and parsing. + +However, a problem arises. How do we write a function which can accept +an object that is either a request or a response? As written, the only +obvious solution is to make the message a template type. Additional traits +classes would then be needed to make sure that the passed object has a +valid type which meets the requirements. These unnecessary complexities +are bypassed by making each container a partial specialization: +``` +/// An HTTP message +template +struct message; + +/// An HTTP request +template +struct message +{ + int version; + std::string method; + std::string target; + Fields fields; + + typename Body::value_type body; +}; + +/// An HTTP response +template +struct message +{ + int version; + int status; + std::string reason; + Fields fields; + + typename Body::value_type body; +}; +``` + +Now we can declare a function which takes any message as a parameter: +``` +template +void f(message& msg); +``` + +This function can manipulate the fields common to requests and responses. +If it needs to access the other fields, it can use overloads with +partial specialization, or in C++17 a `constexpr` expression: +``` +template +void f(message& msg) +{ + if constexpr(isRequest) + { + // call msg.method(), msg.target() + } + else + { + // call msg.result(), msg.reason() + } +} +``` + +Often, in non-trivial HTTP applications, we want to read the HTTP header +and examine its contents before choosing a type for [*Body]. To accomplish +this, there needs to be a way to model the header portion of a message. +And we'd like to do this in a way that allows functions which take the +header as a parameter, to also accept a type representing the whole +message (the function will see just the header part). This suggests +inheritance, by splitting a new base class off of the message: +``` +/// An HTTP message header +template +struct header; +``` + +Code which accesses the fields has to laboriously mention the `fields` +member, so we'll not only make `header` a base class but we'll make +a quality of life improvement and derive the header from the fields +for notational convenience. In order to properly support all forms +of construction of [*Fields] there will need to be a set of suitable +constructor overloads (not shown): +``` +/// An HTTP request header +template +struct header : Fields +{ + int version; + std::string method; + std::string target; +}; + +/// An HTTP response header +template +struct header : Fields +{ + int version; + int status; + std::string reason; +}; + +/// An HTTP message +template +struct message : header +{ + typename Body::value_type body; + + /// Construct from a `header` + message(header&& h); +}; + +``` + +Note that the `message` class now has a constructor allowing messages +to be constructed from a similarly typed `header`. This handles the case +where the user already has the header and wants to make a commitment to the +type for [*Body]. A function can be declared which accepts any header: +``` +template +void f(header& msg); +``` + +Until now we have not given significant consideration to the constructors +of the `message` class. But to achieve all our goals we will need to make +sure that there are enough constructor overloads to not only provide for +the special copy and move members if the instantiated types support it, +but also allow the fields container and body container to be constructed +with arbitrary variadic lists of parameters. This allows the container +to fully support allocators. + +The solution used in the library is to treat the message like a `std::pair` +for the purposes of construction, except that instead of `first` and `second` +we have the `Fields` base class and `message::body` member. This means that +single-argument constructors for those fields should be accessible as they +are with `std::pair`, and that a mechanism identical to the pair's use of +`std::piecewise_construct` should be provided. Those constructors are too +complex to repeat here, but interested readers can view the declarations +in the corresponding header file. + +There is now significant progress with our message container but a stumbling +block remains. There is no way to control the allocator for the `std::string` +members. We could add an allocator to the template parameter list of the +header and message classes, use it for those strings. This is unsatisfying +because of the combinatorial explosion of constructor variations needed to +support the scheme. It also means that request messages could have [*four] +different allocators: two for the fields and body, and two for the method +and target strings. A better solution is needed. + +To get around this we make an interface modification and then add +a requirement to the [*Fields] type. First, the interface change: +``` +/// An HTTP request header +template +struct header : Fields +{ + int version; + + verb method() const; + string_view method_string() const; + void method(verb); + void method(string_view); + + string_view target(); const; + void target(string_view); + +private: + verb method_; +}; + +/// An HTTP response header +template +struct header : Fields +{ + int version; + int result; + string_view reason() const; + void reason(string_view); +}; +``` + +The start-line data members are replaced traditional accessors using +non-owning references to string buffers. The method is stored using +a simple integer instead of the entire string, for the case where +the method is recognized from the set of known verb strings. + +Now we add a requirement to the fields type: management of the +corresponding string is delegated to the [*Fields] container, which can +already be allocator aware and constructed with the necessary allocator +parameter via the provided constructor overloads for `message`. The +delegation implementation looks like this (only the response header +specialization is shown): +``` +/// An HTTP response header +template +struct header : Fields +{ + int version; + int status; + + string_view + reason() const + { + return this->reason_impl(); // protected member of Fields + } + + void + reason(string_view s) + { + this->reason_impl(s); // protected member of Fields + } +}; +``` + +Now that we've accomplished our initial goals and more, there are a few +more quality of life improvements to make. Users will choose different +types for `Body` far more often than they will for `Fields`. Thus, we +swap the order of these types and provide a default. Then, we provide +type aliases for requests and responses to soften the impact of using +`bool` to choose the specialization: + +``` +/// An HTTP header +template +struct header; + +/// An HTTP message +template +struct message; + +/// An HTTP request +template +using request = message; + +/// An HTTP response +template +using response = message; +``` + +This allows concise specification for the common cases, while +allowing for maximum customization for edge cases: +``` +request req; + +response res; +``` + +This container is also capable of representing complete HTTP/2 messages. +Not because it was explicitly designed for, but because the IETF wanted to +preserve message compatibility with HTTP/1. Aside from version specific +fields such as Connection, the contents of HTTP/1 and HTTP/2 messages are +identical even though their serialized representation is considerably +different. The message model presented in this library is ready for HTTP/2. + +In conclusion, this representation for the message container is well thought +out, provides comprehensive flexibility, and avoids the necessity of defining +additional traits classes. User declarations of functions that accept headers +or messages as parameters are easy to write in a variety of ways to accomplish +different results, without forcing cumbersome SFINAE declarations everywhere. + +[endsect] diff --git a/src/beast/doc/9_2_http_comparison.qbk b/src/beast/doc/9_2_http_comparison.qbk new file mode 100644 index 0000000000..e181fcb51a --- /dev/null +++ b/src/beast/doc/9_2_http_comparison.qbk @@ -0,0 +1,454 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section HTTP Comparison to Other Libraries] + +There are a few C++ published libraries which implement some of the HTTP +protocol. We analyze the message model chosen by those libraries and discuss +the advantages and disadvantages relative to Beast. + +The general strategy used by the author to evaluate external libraries is +as follows: + +* Review the message model. Can it represent a complete request or + response? What level of allocator support is present? How much + customization is possible? + +* Review the stream abstraction. This is the type of object, such as + a socket, which may be used to parse or serialize (i.e. read and write). + Can user defined types be specified? What's the level of conformance to + to Asio or Networking-TS concepts? + +* Check treatment of buffers. Does the library manage the buffers + or can users provide their own buffers? + +* How does the library handle corner cases such as trailers, + Expect: 100-continue, or deferred commitment of the body type? + +[note + Declarations examples from external libraries have been edited: + portions have been removed for simplification. +] + + + +[heading cpp-netlib] + +[@https://github.com/cpp-netlib/cpp-netlib/tree/092cd570fb179d029d1865aade9f25aae90d97b9 [*cpp-netlib]] +is a network programming library previously intended for Boost but not +having gone through formal review. As of this writing it still uses the +Boost name, namespace, and directory structure although the project states +that Boost acceptance is no longer a goal. The library is based on Boost.Asio +and bills itself as ['"a collection of network related routines/implementations +geared towards providing a robust cross-platform networking library"]. It +cites ['"Common Message Type"] as a feature. As of the branch previous +linked, it uses these declarations: +``` +template +struct basic_message { + public: + typedef Tag tag; + + typedef typename headers_container::type headers_container_type; + typedef typename headers_container_type::value_type header_type; + typedef typename string::type string_type; + + headers_container_type& headers() { return headers_; } + headers_container_type const& headers() const { return headers_; } + + string_type& body() { return body_; } + string_type const& body() const { return body_; } + + string_type& source() { return source_; } + string_type const& source() const { return source_; } + + string_type& destination() { return destination_; } + string_type const& destination() const { return destination_; } + + private: + friend struct detail::directive_base; + friend struct detail::wrapper_base >; + + mutable headers_container_type headers_; + mutable string_type body_; + mutable string_type source_; + mutable string_type destination_; +}; +``` + +This container is the base class template used to represent HTTP messages. +It uses a "tag" type style specializations for a variety of trait classes, +allowing for customization of the various parts of the message. For example, +a user specializes `headers_container` to determine what container type +holds the header fields. We note some problems with the container declaration: + +* The header and body containers may only be default-constructed. + +* No stateful allocator support. + +* There is no way to defer the commitment of the type for `body_` to + after the headers are read in. + +* The message model includes a "source" and "destination." This is + extraneous metadata associated with the connection which is not part + of the HTTP protocol specification and belongs elsewhere. + +* The use of `string_type` (a customization point) for source, + destination, and body suggests that `string_type` models a + [*ForwardRange] whose `value_type` is `char`. This representation + is less than ideal, considering that the library is built on + Boost.Asio. Adapting a __DynamicBuffer__ to the required forward + range destroys information conveyed by the __ConstBufferSequence__ + and __MutableBufferSequence__ used in dynamic buffers. The consequence + is that cpp-netlib implementations will be less efficient than an + equivalent __N4588__ conforming implementation. + +* The library uses specializations of `string` to change the type + of string used everywhere, including the body, field name and value + pairs, and extraneous metadata such as source and destination. The + user may only choose a single type: field name, field values, and + the body container will all use the same string type. This limits + utility of the customization point. The library's use of the string + trait is limited to selecting between `std::string` and `std::wstring`. + We do not find this use-case compelling given the limitations. + +* The specialized trait classes generate a proliferation of small + additional framework types. To specialize traits, users need to exit + their namespace and intrude into the `boost::network::http` namespace. + The way the traits are used in the library limits the usefulness + of the traits to trivial purpose. + +* The `string customization point constrains user defined body types + to few possible strategies. There is no way to represent an HTTP message + body as a filename with accompanying algorithms to store or retrieve data + from the file system. + +The design of the message container in this library is cumbersome +with its system of customization using trait specializations. The +use of these customizations is extremely limited due to the way they +are used in the container declaration, making the design overly +complex without corresponding benefit. + + + +[heading Boost.HTTP] + +[@https://github.com/BoostGSoC14/boost.http/tree/45fc1aa828a9e3810b8d87e669b7f60ec100bff4 [*boost.http]] +is a library resulting from the 2014 Google Summer of Code. It was submitted +for a Boost formal review and rejected in 2015. It is based on Boost.Asio, +and development on the library has continued to the present. As of the branch +previously linked, it uses these message declarations: +``` +template +struct basic_message +{ + typedef Headers headers_type; + typedef Body body_type; + + headers_type &headers(); + + const headers_type &headers() const; + + body_type &body(); + + const body_type &body() const; + + headers_type &trailers(); + + const headers_type &trailers() const; + +private: + headers_type headers_; + body_type body_; + headers_type trailers_; +}; + +typedef basic_message> message; + +template +struct is_message>: public std::true_type {}; +``` + +* This container cannot model a complete message. The ['start-line] items + (method and target for requests, reason-phrase for responses) are + communicated out of band, as is the ['http-version]. A function that + operates on the message including the start line requires additional + parameters. This is evident in one of the + [@https://github.com/BoostGSoC14/boost.http/blob/45fc1aa828a9e3810b8d87e669b7f60ec100bff4/example/basic_router.cpp#L81 example programs]. + The `500` and `"OK"` arguments represent the response ['status-code] and + ['reason-phrase] respectively: + ``` + ... + http::message reply; + ... + self->socket.async_write_response(500, string_ref("OK"), reply, yield); + ``` + +* `headers_`, `body_`, and `trailers_` may only be default-constructed, + since there are no explicitly declared constructors. + +* There is no way to defer the commitment of the [*Body] type to after + the headers are read in. This is related to the previous limitation + on default-construction. + +* No stateful allocator support. This follows from the previous limitation + on default-construction. Buffers for start-line strings must be + managed externally from the message object since they are not members. + +* The trailers are stored in a separate object. Aside from the combinatorial + explosion of the number of additional constructors necessary to fully + support arbitrary forwarded parameter lists for each of the headers, body, + and trailers members, the requirement to know in advance whether a + particular HTTP field will be located in the headers or the trailers + poses an unnecessary complication for general purpose functions that + operate on messages. + +* The declarations imply that `std::vector` is a model of [*Body]. + More formally, that a body is represented by the [*ForwardRange] + concept whose `value_type` is an 8-bit integer. This representation + is less than ideal, considering that the library is built on + Boost.Asio. Adapting a __DynamicBuffer__ to the required forward range + destroys information conveyed by the __ConstBufferSequence__ and + __MutableBufferSequence__ used in dynamic buffers. The consequence is + that Boost.HTTP implementations will be less efficient when dealing + with body containers than an equivalent __N4588__ conforming + implementation. + +* The [*Body] customization point constrains user defined types to + very limited implementation strategies. For example, there is no way + to represent an HTTP message body as a filename with accompanying + algorithms to store or retrieve data from the file system. + +This representation addresses a narrow range of use cases. It has +limited potential for customization and performance. It is more difficult +to use because it excludes the start line fields from the model. + + + +[heading C++ REST SDK (cpprestsdk)] + +[@https://github.com/Microsoft/cpprestsdk/tree/381f5aa92d0dfb59e37c0c47b4d3771d8024e09a [*cpprestsdk]] +is a Microsoft project which ['"...aims to help C++ developers connect to and +interact with services"]. It offers the most functionality of the libraries +reviewed here, including support for Websocket services using its websocket++ +dependency. It can use native APIs such as HTTP.SYS when building Windows +based applications, and it can use Boost.Asio. The WebSocket module uses +Boost.Asio exclusively. + +As cpprestsdk is developed by a large corporation, it contains quite a bit +of functionality and necessarily has more interfaces. We will break down +the interfaces used to model messages into more manageable pieces. This +is the container used to store the HTTP header fields: +``` +class http_headers +{ +public: + ... + +private: + std::map m_headers; +}; +``` + +This declaration is quite bare-bones. We note the typical problems of +most field containers: + +* The container may only be default-constructed. + +* No support for allocators, stateful or otherwise. + +* There are no customization points at all. + +Now we analyze the structure of +the larger message container. The library uses a handle/body idiom. There +are two public message container interfaces, one for requests (`http_request`) +and one for responses (`http_response`). Each interface maintains a private +shared pointer to an implementation class. Public member function calls +are routed to the internal implementation. This is the first implementation +class, which forms the base class for both the request and response +implementations: +``` +namespace details { + +class http_msg_base +{ +public: + http_headers &headers() { return m_headers; } + + _ASYNCRTIMP void set_body(const concurrency::streams::istream &instream, const utf8string &contentType); + + /// Set the stream through which the message body could be read + void set_instream(const concurrency::streams::istream &instream) { m_inStream = instream; } + + /// Set the stream through which the message body could be written + void set_outstream(const concurrency::streams::ostream &outstream, bool is_default) { m_outStream = outstream; m_default_outstream = is_default; } + + const pplx::task_completion_event & _get_data_available() const { return m_data_available; } + +protected: + /// Stream to read the message body. + concurrency::streams::istream m_inStream; + + /// stream to write the msg body + concurrency::streams::ostream m_outStream; + + http_headers m_headers; + bool m_default_outstream; + + /// The TCE is used to signal the availability of the message body. + pplx::task_completion_event m_data_available; +}; +``` + +To understand these declarations we need to first understand that cpprestsdk +uses the asynchronous model defined by Microsoft's +[@https://msdn.microsoft.com/en-us/library/dd504870.aspx [*Concurrency Runtime]]. +Identifiers from the [@https://msdn.microsoft.com/en-us/library/jj987780.aspx [*`pplx` namespace]] +define common asynchronous patterns such as tasks and events. The +`concurrency::streams::istream` parameter and `m_data_available` data member +indicates a lack of separation of concerns. The representation of HTTP messages +should not be conflated with the asynchronous model used to serialize or +parse those messages in the message declarations. + +The next declaration forms the complete implementation class referenced by the +handle in the public interface (which follows after): +``` +/// Internal representation of an HTTP request message. +class _http_request final : public http::details::http_msg_base, public std::enable_shared_from_this<_http_request> +{ +public: + _ASYNCRTIMP _http_request(http::method mtd); + + _ASYNCRTIMP _http_request(std::unique_ptr server_context); + + http::method &method() { return m_method; } + + const pplx::cancellation_token &cancellation_token() const { return m_cancellationToken; } + + _ASYNCRTIMP pplx::task reply(const http_response &response); + +private: + + // Actual initiates sending the response, without checking if a response has already been sent. + pplx::task _reply_impl(http_response response); + + http::method m_method; + + std::shared_ptr m_progress_handler; +}; + +} // namespace details +``` + +As before, we note that the implementation class for HTTP requests concerns +itself more with the mechanics of sending the message asynchronously than +it does with actually modeling the HTTP message as described in __rfc7230__: + +* The constructor accepting `std::unique_ptrmethod(); } + + void set_method(const http::method &method) const { _m_impl->method() = method; } + + /// Extract the body of the request message as a string value, checking that the content type is a MIME text type. + /// A body can only be extracted once because in some cases an optimization is made where the data is 'moved' out. + pplx::task extract_string(bool ignore_content_type = false) + { + auto impl = _m_impl; + return pplx::create_task(_m_impl->_get_data_available()).then([impl, ignore_content_type](utility::size64_t) { return impl->extract_string(ignore_content_type); }); + } + + /// Extracts the body of the request message into a json value, checking that the content type is application/json. + /// A body can only be extracted once because in some cases an optimization is made where the data is 'moved' out. + pplx::task extract_json(bool ignore_content_type = false) const + { + auto impl = _m_impl; + return pplx::create_task(_m_impl->_get_data_available()).then([impl, ignore_content_type](utility::size64_t) { return impl->_extract_json(ignore_content_type); }); + } + + /// Sets the body of the message to the contents of a byte vector. If the 'Content-Type' + void set_body(const std::vector &body_data); + + /// Defines a stream that will be relied on to provide the body of the HTTP message when it is + /// sent. + void set_body(const concurrency::streams::istream &stream, const utility::string_t &content_type = _XPLATSTR("application/octet-stream")); + + /// Defines a stream that will be relied on to hold the body of the HTTP response message that + /// results from the request. + void set_response_stream(const concurrency::streams::ostream &stream); + { + return _m_impl->set_response_stream(stream); + } + + /// Defines a callback function that will be invoked for every chunk of data uploaded or downloaded + /// as part of the request. + void set_progress_handler(const progress_handler &handler); + +private: + friend class http::details::_http_request; + friend class http::client::http_client; + + std::shared_ptr _m_impl; +}; +``` + +It is clear from this declaration that the goal of the message model in +this library is driven by its use-case (interacting with REST servers) +and not to model HTTP messages generally. We note problems similar to +the other declarations: + +* There are no compile-time customization points at all. The only + customization is in the `concurrency::streams::istream` and + `concurrency::streams::ostream` reference parameters. Presumably, + these are abstract interfaces which may be subclassed by users + to achieve custom behaviors. + +* The extraction of the body is conflated with the asynchronous model. + +* No way to define an allocator for the container used when extracting + the body. + +* A body can only be extracted once, limiting the use of this container + when using a functional programming style. + +* Setting the body requires either a vector or a `concurrency::streams::istream`. + No user defined types are possible. + +* The HTTP request container conflates HTTP response behavior (see the + `set_response_stream` member). Again this is likely purpose-driven but + the lack of separation of concerns limits this library to only the + uses explicitly envisioned by the authors. + +The general theme of the HTTP message model in cpprestsdk is "no user +definable customizations". There is no allocator support, and no +separation of concerns. It is designed to perform a specific set of +behaviors. In other words, it does not follow the open/closed principle. + +Tasks in the Concurrency Runtime operate in a fashion similar to +`std::future`, but with some improvements such as continuations which +are not yet in the C++ standard. The costs of using a task based +asynchronous interface instead of completion handlers is well +documented: synchronization points along the call chain of composed +task operations which cannot be optimized away. See: +[@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3747.pdf +[*A Universal Model for Asynchronous Operations]] (Kohlhoff). + +[endsect] diff --git a/src/beast/doc/9_3_websocket_zaphoyd.qbk b/src/beast/doc/9_3_websocket_zaphoyd.qbk new file mode 100644 index 0000000000..80fbe793b1 --- /dev/null +++ b/src/beast/doc/9_3_websocket_zaphoyd.qbk @@ -0,0 +1,445 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section Comparison to Zaphoyd Studios WebSocket++] + +[variablelist + +[[ + How does this compare to [@https://www.zaphoyd.com/websocketpp websocketpp], + an alternate header-only WebSocket implementation? +][ +[variablelist + +[[1. Synchronous Interface][ + +Beast offers full support for WebSockets using a synchronous interface. It +uses the same style of interfaces found in Boost.Asio: versions that throw +exceptions, or versions that return the error code in a reference parameter: + +[table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L774 Beast]] + [websocketpp] + ][ + [``` + template + void + read(DynamicBuffer& dynabuf) + ```] + [ + // + ] +]]]] + +[[2. Connection Model][ + +websocketpp supports multiple transports by utilizing a trait, the `config::transport_type` +([@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/asio/connection.hpp#L60 asio transport example]) +To get an idea of the complexity involved with implementing a transport, +compare the asio transport to the +[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/iostream/connection.hpp#L59 `iostream` transport] +(a layer that allows websocket communication over a `std::iostream`). + +In contrast, Beast abstracts the transport by defining just one [*`NextLayer`] +template argument The type requirements for [*`NextLayer`] are +already familiar to users as they are documented in Asio: +__AsyncReadStream__, __AsyncWriteStream__, __SyncReadStream__, __SyncWriteStream__. + +The type requirements for instantiating `beast::websocket::stream` versus +`websocketpp::connection` with user defined types are vastly reduced +(18 functions versus 2). Note that websocketpp connections are passed by +`shared_ptr`. Beast does not use `shared_ptr` anywhere in its public interface. +A `beast::websocket::stream` is constructible and movable in a manner identical +to a `boost::asio::ip::tcp::socket`. Callers can put such objects in a +`shared_ptr` if they want to, but there is no requirement to do so. + +[table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp Beast]] + [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L234 websocketpp]] + ][ + [``` + template + class stream + { + NextLayer next_layer_; + ... + } + ```] + [``` + template + class connection + : public config::transport_type::transport_con_type + , public config::connection_base + { + public: + typedef lib::shared_ptr ptr; + ... + } + ```] +]]]] + +[[3. Client and Server Role][ + +websocketpp provides multi-role support through a hierarchy of +different classes. A `beast::websocket::stream` is role-agnostic, it +offers member functions to perform both client and server handshakes +in the same class. The same types are used for client and server +streams. + +[table + [ + [Beast] + [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/roles/server_endpoint.hpp#L39 websocketpp], + [@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/roles/client_endpoint.hpp#L42 also]] + ][ + [ + // + ] + [``` + template + class client : public endpoint,config>; + template + class server : public endpoint,config>; + ```] +]]]] + +[[4. Thread Safety][ + +websocketpp uses mutexes to protect shared data from concurrent +access. In contrast, Beast does not use mutexes anywhere in its +implementation. Instead, it follows the Asio pattern. Calls to +asynchronous initiation functions use the same method to invoke +intermediate handlers as the method used to invoke the final handler, +through the __asio_handler_invoke__ mechanism. + +The only requirement in Beast is that calls to asynchronous initiation +functions are made from the same implicit or explicit strand. For +example, if the `io_service` associated with a `beast::websocket::stream` +is single threaded, this counts as an implicit strand and no performance +costs associated with mutexes are incurred. + +[table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/impl/read_frame_op.ipp#L118 Beast]] + [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/iostream/connection.hpp#L706 websocketpp]] + ][ + [``` + template + friend + void asio_handler_invoke(Function&& f, read_frame_op* op) + { + return boost_asio_handler_invoke_helpers::invoke(f, op->d_->h); + } + ```] + [``` + mutex_type m_read_mutex; + ```] +]]]] + +[[5. Callback Model][ + +websocketpp requires a one-time call to set the handler for each event +in its interface (for example, upon message receipt). The handler is +represented by a `std::function` equivalent. Its important to recognize +that the websocketpp interface performs type-erasure on this handler. + +In comparison, Beast handlers are specified in a manner identical to +Boost.Asio. They are function objects which can be copied or moved but +most importantly they are not type erased. The compiler can see +through the type directly to the implementation, permitting +optimization. Furthermore, Beast follows the Asio rules for treatment +of handlers. It respects any allocation, continuation, or invocation +customizations associated with the handler through the use of argument +dependent lookup overloads of functions such as `asio_handler_allocate`. + +The Beast completion handler is provided at the call site. For each +call to an asynchronous initiation function, it is guaranteed that +there will be exactly one final call to the handler. This functions +exactly the same way as the asynchronous initiation functions found in +Boost.Asio, allowing the composition of higher level abstractions. + +[table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L834 Beast]] + [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L281 websocketpp], + [@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L473 also]] + ][ + [``` + template< + class DynamicBuffer, // Supports user defined types + class ReadHandler // Handler is NOT type-erased + > + typename async_completion< // Return value customization + ReadHandler, // supports futures and coroutines + void(error_code) + >::result_type + async_read( + DynamicBuffer& dynabuf, + ReadHandler&& handler); + ```] + [``` + typedef lib::function< + void(connection_hdl,message_ptr) + > message_handler; + void set_message_handler(message_handler h); + ```] +]]]] + +[[6. Extensible Asynchronous Model][ + +Beast fully supports the +[@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3896.pdf Extensible Asynchronous Model] +developed by Christopher Kohlhoff, author of Boost.Asio (see Section 8). + +Beast websocket asynchronous interfaces may be used seamlessly with +`std::future` stackful/stackless coroutines, or user defined customizations. + +[table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/impl/stream.ipp#L378 Beast]] + [websocketpp] + ][ + [``` + beast::async_completion< + ReadHandler, + void(error_code)> completion{handler}; + read_op< + DynamicBuffer, decltype(completion.handler)>{ + completion.handler, *this, op, buffer}; + + return completion.result.get(); // Customization point + ```] + [ + // + ] +]]]] + +[[7. Message Buffering][ + +websocketpp defines a message buffer, passed in arguments by +`shared_ptr`, and an associated message manager which permits +aggregation and reuse of memory. The implementation of +`websocketpp::message` uses a `std::string` to hold the payload. If an +incoming message is broken up into multiple frames, the string may be +reallocated for each continuation frame. The `std::string` always uses +the standard allocator, it is not possible to customize the choice of +allocator. + +Beast allows callers to specify the object for receiving the message +or frame data, which is of any type meeting the requirements of +__DynamicBuffer__ (modeled after `boost::asio::streambuf`). + +Beast comes with the class __basic_multi_buffer__, an efficient +implementation of the __DynamicBuffer__ concept which makes use of multiple +allocated octet arrays. If an incoming message is broken up into +multiple pieces, no reallocation occurs. Instead, new allocations are +appended to the sequence when existing allocations are filled. Beast +does not impose any particular memory management model on callers. The +__basic_multi_buffer__ provided by beast supports standard allocators through +a template argument. Use the __DynamicBuffer__ that comes with beast, +customize the allocator if you desire, or provide your own type that +meets the requirements. + +[table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L774 Beast]] + [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/message_buffer/message.hpp#L78 websocketpp]] + ][ + [``` + template + read(DynamicBuffer& dynabuf); + ```] + [``` + template class con_msg_manager> + class message { + public: + typedef lib::shared_ptr ptr; + ... + std::string m_payload; + ... + }; + ```] +]]]] + +[[8. Sending Messages][ + +When sending a message, websocketpp requires that the payload is +packaged in a `websocketpp::message` object using `std::string` as the +storage, or it requires a copy of the caller provided buffer by +constructing a new message object. Messages are placed onto an +outgoing queue. An asynchronous write operation runs in the background +to clear the queue. No user facing handler can be registered to be +notified when messages or frames have completed sending. + +Beast doesn't allocate or make copies of buffers when sending data. The +caller's buffers are sent in-place. You can use any object meeting the +requirements of +[@http://www.boost.org/doc/html/boost_asio/reference/ConstBufferSequence.html ConstBufferSequence], +permitting efficient scatter-gather I/O. + +The [*ConstBufferSequence] interface allows callers to send data from +memory-mapped regions (not possible in websocketpp). Callers can also +use the same buffers to send data to multiple streams, for example +broadcasting common subscription data to many clients at once. For +each call to `async_write` the completion handler is called once when +the data finishes sending, in a manner identical to `boost::asio::async_write`. + +[table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L1048 Beast]] + [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L672 websocketpp]] + ][ + [``` + template + void + write(ConstBufferSequence const& buffers); + ```] + [``` + lib::error_code send(std::string const & payload, + frame::opcode::value op = frame::opcode::text); + ... + lib::error_code send(message_ptr msg); + ```] +]]]] + +[[9. Streaming Messages][ + +websocketpp requires that the entire message fit into memory, and that +the size is known ahead of time. + +Beast allows callers to compose messages in individual frames. This is +useful when the size of the data is not known ahead of time or if it +is not desired to buffer the entire message in memory at once before +sending it. For example, sending periodic output of a database query +running on a coroutine. Or sending the contents of a file in pieces, +without bringing it all into memory. + +[table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L1151 Beast]] + [websocketpp] + ][ + [``` + template + void + write_frame(bool fin, + ConstBufferSequence const& buffers); + ```] + [ + // + ] +]]]] + +[[10. Flow Control][ + +The websocketpp read implementation continuously reads asynchronously +from the network and buffers message data. To prevent unbounded growth +and leverage TCP/IP's flow control mechanism, callers can periodically +turn this 'read pump' off and back on. + +In contrast a `beast::websocket::stream` does not independently begin +background activity, nor does it buffer messages. It receives data only +when there is a call to an asynchronous initiation function (for +example `beast::websocket::stream::async_read`) with an associated handler. +Applications do not need to implement explicit logic to regulate the +flow of data. Instead, they follow the traditional model of issuing a +read, receiving a read completion, processing the message, then +issuing a new read and repeating the process. + +[table + [ + [Beast] + [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L728 websocketpp]] + ][ + [ + // + ] + [``` + lib::error_code pause_reading(); + lib::error_code resume_reading(); + ```] +]]]] + +[[11. Connection Establishment][ + +websocketpp offers the `endpoint` class which can handle binding and +listening to a port, and spawning connection objects. + +Beast does not reinvent the wheel here, callers use the interfaces +already in `boost::asio` for receiving incoming connections resolving +host names, or establishing outgoing connections. After the socket (or +`boost::asio::ssl::stream`) is connected, the `beast::websocket::stream` +is constructed around it and the WebSocket handshake can be performed. + +Beast users are free to implement their own "connection manager", but +there is no requirement to do so. + +[table + [ + [[@http://www.boost.org/doc/html/boost_asio/reference/async_connect.html Beast], + [@http://www.boost.org/doc/html/boost_asio/reference/basic_socket_acceptor/async_accept.html also]] + [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/asio/endpoint.hpp#L52 websocketpp]] + ][ + [``` + #include + ```] + [``` + template + class endpoint : public config::socket_type; + ```] +]]]] + +[[12. WebSocket Handshaking][ + +Callers invoke `beast::websocket::accept` to perform the WebSocket +handshake, but there is no requirement to use this function. Advanced +users can perform the WebSocket handshake themselves. Beast WebSocket +provides the tools for composing the request or response, and the +Beast HTTP interface provides the container and algorithms for sending +and receiving HTTP/1 messages including the necessary HTTP Upgrade +request for establishing the WebSocket session. + +Beast allows the caller to pass the incoming HTTP Upgrade request for +the cases where the caller has already received an HTTP message. +This flexibility permits novel and robust implementations. For example, +a listening socket that can handshake in multiple protocols on the +same port. + +Sometimes callers want to read some bytes on the socket before reading +the WebSocket HTTP Upgrade request. Beast allows these already-received +bytes to be supplied to an overload of the accepting function to permit +sophisticated features. For example, a listening socket that can +accept both regular WebSocket and Secure WebSocket (SSL) connections. + +[table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L501 Beast], + [@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L401 also]] + [websocketpp] + ][ + [``` + template + void + accept(ConstBufferSequence const& buffers); + + template + void + accept(http::header> const& req); + ```] + [ + // + ] +]]]] + +] +]] + +] + +[endsect] diff --git a/src/beast/doc/9_4_faq.qbk b/src/beast/doc/9_4_faq.qbk new file mode 100644 index 0000000000..99ba6c20af --- /dev/null +++ b/src/beast/doc/9_4_faq.qbk @@ -0,0 +1,283 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section FAQ] + +To set realistic expectations and prevent a litany of duplicate review +statements, these notes address the most common questions and comments +about Beast and other HTTP libraries that have gone through formal review. + +[variablelist +[[ + "Beast requires too much user code to do anything!" +][ + It is not the intention of the library to provide turn-key + solutions for specific HTTP or WebSocket use-cases. + Instead, it is a sensible protocol layering on top of + Boost.Asio which retains the Boost.Asio memory + management style and asynchronous model. +]] +[[ + "Beast does not offer an HTTP server?" +][ + Beast has a functional HTTP server in the example directory. The + server supports both HTTP and WebSocket using synchronous and + asynchronous shared or dedicated ports. In addition, the server + supports encrypted TLS connections if OpenSSL is available, on + dedicated ports. And the server comes with a "multi-port", a + flexible single port which supports both encrypted and unencrypted + connections, both HTTP and WebSocket, all on the same port. The + server is not part of Beast's public interfaces, as that + functionality is outside the scope of the library. The author + feels that attempting to broaden the scope of the library will + reduce its appeal for standardization. +]] +[[ + "Beast does not offer an HTTP client?" +][ + "I just want to download a resource using HTTP" is a common + cry from users and reviewers. Such functionality is beyond + the scope of Beast. Building a full featured HTTP client is + a difficult task and large enough to deserve its own library. + There are many things to deal with such as the various message + body encodings, complex parsing of headers, difficult header + semantics such as Range and Cache-Control, redirection, + Expect:100-continue, connection retrying, domain name + resolution, TLS, and much, much more. It is the author's + position that Boost first needs a common set of nouns and + verbs for manipulating HTTP at the protocol level; Beast + provides that language. +]] +[[ + "There's no HTTP/2 support yet!" +][ + Many reviewers feel that HTTP/2 support is an essential feature of + a HTTP library. The authors agree that HTTP/2 is important but also + feel that the most sensible implementation is one that does not re-use + the same network reading and writing interface for 2 as that for 1.0 + and 1.1. + + The Beast HTTP message model was designed with the new protocol + in mind and should be evaluated in that context. There are plans + to add HTTP/2 in the future, but there is no rush to do so. + Users can work with HTTP/1 now; we should not deny them that + functionality today to wait for a newer protocol tomorrow. + It is the author's position that there is sufficient value in + Beast's HTTP/1-only implementation that the lack of HTTP/2 + should not be a barrier to acceptance. + + The Beast HTTP message model is suitable for HTTP/2 and can be re-used. + The IETF HTTP Working Group adopted message compatibility with HTTP/1.x + as an explicit goal. A parser can simply emit full headers after + decoding the compressed HTTP/2 headers. The stream ID is not logically + part of the message but rather message metadata and should be + communicated out-of-band (see below). HTTP/2 sessions begin with a + traditional HTTP/1.1 Upgrade similar in fashion to the WebSocket + upgrade. An HTTP/2 implementation can use existing Beast.HTTP primitives + to perform this handshake. +]] +[[ + "This should work with standalone-Asio!" +][ + Beast uses more than Boost.Asio, it depends on various other parts + of Boost. The standalone Asio is currently farther ahead than the + Boost version. Keeping Beast maintained against both versions of + Asio is beyond the resources of the author at the present time. + Compatibility with non-Boost libraries should not be an acceptance + criteria. Beast is currently designed to be a part of Boost: + nothing more, nothing less. Looking at the bigger picture, it + is the author's goal to propose this library for standardization. + A logical track for achieving this is as follows: + + [ordered_list + [ + Boost library acceptance. + ][ + Port to the Boost.Asio version of Networking-TS (This has to wait + until Boost's version of Asio is updated). + ][ + Wait for Networking-TS to become an official part of C++. + ][ + Port to the standard library versions of networking (gcc, clang, msvc). + ][ + Develop proposed language (This can happen concurrently with steps 3 and 4) + ]] +]] +[[ + "You need benchmarks!" +][ + The energy invested in Beast went into the design of the interfaces, + not performance. That said, the most sensitive parts of Beast have + been optimized or designed with optimization in mind. The slow parts + of WebSocket processing have been optimized, and the HTTP parser design + is lifted from another extremely popular project which has performance + as a design goal (see [@https://github.com/h2o/picohttpparser]). + + From: [@http://www.boost.org/development/requirements.html] + + "Aim first for clarity and correctness; optimization should + be only a secondary concern in most Boost libraries." + + As the library matures it will undergo optimization passes; benchmarks + will logically accompany this process. There is a small benchmarking + program included in the tests which compares the performance of + Beast's parser to the NodeJS reference parser, as well as some + benchmarks which compare the performance of various Beast dynamic + buffer implementations against Asio's. +]] +[[ + "Beast is a terrible name!" +][ + The name "Boost.Http" or "Boost.WebSocket" would mislead users into + believing they could perform an HTTP request on a URL or put up a + WebSocket client or server in a couple of lines of code. Where + would the core utilities go? Very likely it would step on the + owner of Boost.Asio's toes to put things in the boost/asio + directory; at the very least, it would create unrequested, + additional work for the foreign repository. + + "Beast" is sufficiently vague as to not suggest any particular + functionality, while acting as a memorable umbrella term for a + family of low level containers and algorithms. People in the know + or with a need for low-level network protocol operations will + have no trouble finding it, and the chances of luring a novice + into a bad experience are greatly reduced. + There is precedent for proper names: "Hana", "Fusion", "Phoenix", + and "Spirit" come to mind. Is "Beast" really any worse than say, + "mp11" for example? + Beast also already has a growing body of users and attention from + the open source community, the name Beast comes up in reddit posts + and StackOverflow as the answer to questions about which HTTP or + WebSocket library to use. +]] + + + +[[ + "Some more advanced examples, e.g. including TLS with client/server + certificates would help." +][ + The server-framework example demonstrates how to implement a server + that supports TLS using certificates. There are also websocket and + HTTP client examples which use TLS. Furthermore, management of + certificates is beyond the scope of the public interfaces of the + library. Asio already provides documentation, interfaces, and + examples for performing these tasks - Beast does not intend to + reinvent them or to redundantly provide this information. +]] + +[[ + "A built-in HTTP router?" +][ + We presume this means a facility to match expressions against the URI + in HTTP requests, and dispatch them to calling code. The authors feel + that this is a responsibility of higher level code. Beast does + not try to offer a web server. That said, the server-framework + example has a concept of request routing called a Service. Two + services are provided, one for serving files and the other for + handling WebSocket upgrade requests. +]] + +[[ + "HTTP Cookies? Forms/File Uploads?" +][ + Cookies, or managing these types of HTTP headers in general, is the + responsibility of higher levels. Beast just tries to get complete + messages to and from the calling code. It deals in the HTTP headers just + enough to process the message body and leaves the rest to callers. However, + for forms and file uploads the symmetric interface of the message class + allows HTTP requests to include arbitrary body types including those needed + to upload a file or fill out a form. +]] + +[[ + "...supporting TLS (is this a feature? If not this would be a show-stopper), + etc." +][ + Beast works with the Stream concept, so it automatically works with the + `boost::asio::ssl::stream` that you have already set up through Asio. +]] + +[[ + "There should also be more examples of how to integrate the http service + with getting files from the file system, generating responses CGI-style" +][ + The design goal for the library is to not try to invent a web server. + We feel that there is a strong need for a basic implementation that + models the HTTP message and provides functions to send and receive them + over Asio. Such an implementation should serve as a building block upon + which higher abstractions such as the aforementioned HTTP service or + cgi-gateway can be built. + + There are several HTTP servers in the example directory which deliver + files, as well as some tested and compiled code snippets which can be + used as a starting point for interfacing with other processes. +]] + +[[ + "You should send a 100-continue to ask for the rest of the body if required." +][ + Deciding on whether to send the "Expect: 100-continue" header or + how to handle it on the server side is the caller's responsibility; + Beast provides the functionality to send or inspect the header before + sending or reading the body. +]] + + + +[[ + "I would also like to see instances of this library being used + in production. That would give some evidence that the design + works in practice." +][ + Beast has already been on public servers receiving traffic and handling + hundreds of millions of dollars' worth of financial transactions daily. + The servers run [*rippled], open source software + ([@https://github.com/ripple/rippled repository]) + implementing the + [@https://ripple.com/files/ripple_consensus_whitepaper.pdf [*Ripple Consensus Protocol]], + technology provided by [@http://ripple.com Ripple]. + + Furthermore, the repository has grown significantly in popularity in + 2017. There are many users, and some of them participate directly in + the repository by reporting issues, performing testing, and in some + cases submitting pull requests with code contributions. +]] + + + +[[ + What about WebSocket message compression? +][ + Beast WebSocket supports the permessage-deflate extension described in + [@https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-00 draft-ietf-hybi-permessage-compression-00]. + The library comes with a header-only, C++11 port of ZLib's "deflate" codec + used in the implementation of the permessage-deflate extension. +]] +[[ + Where is the WebSocket TLS/SSL interface? +][ + The `websocket::stream` wraps the socket or stream that you provide + (for example, a `boost::asio::ip::tcp::socket` or a + `boost::asio::ssl::stream`). You establish your TLS connection using the + interface on `ssl::stream` like shown in all of the Asio examples, then + construct your `websocket::stream` around it. It works perfectly fine; + Beast comes with an `ssl_stream` wrapper in the example directory which + allows the SSL stream to be moved, overcoming an Asio limitation. + + The WebSocket implementation [*does] provide support for shutting down + the TLS connection through the use of the ADL compile-time virtual functions + [link beast.ref.beast__websocket__teardown `teardown`] and + [link beast.ref.beast__websocket__async_teardown `async_teardown`]. These will + properly close the connection as per rfc6455 and overloads are available + for TLS streams. Callers may provide their own overloads of these functions + for user-defined next layer types. +]] + +] + +[endsect] diff --git a/src/beast/doc/Jamfile.v2 b/src/beast/doc/Jamfile similarity index 77% rename from src/beast/doc/Jamfile.v2 rename to src/beast/doc/Jamfile index 39e9ecfbbb..24b2cf7e40 100644 --- a/src/beast/doc/Jamfile.v2 +++ b/src/beast/doc/Jamfile @@ -17,13 +17,13 @@ using doxygen ; import quickbook ; -path-constant here : . ; +path-constant out : . ; install stylesheets : $(broot)/doc/src/boostbook.css : - $(here)/html + $(out)/html ; explicit stylesheets ; @@ -32,10 +32,9 @@ install images : [ glob $(broot)/doc/src/images/*.png ] images/beast.png - images/body.png images/message.png : - $(here)/html/images + $(out)/html/images ; explicit images ; @@ -44,28 +43,14 @@ install callouts : [ glob $(broot)/doc/src/images/callouts/*.png ] : - $(here)/html/images/callouts + $(out)/html/images/callouts ; explicit callout ; -install examples - : - [ glob - ../examples/*.cpp - ../examples/*.hpp - ../examples/ssl/*.cpp - ../examples/ssl/*.hpp - ] - : - $(here)/html/examples - ; - -explicit examples ; - xml doc : - master.qbk + 0_main.qbk : temp $(broot)/tools/boostbook/dtd @@ -86,11 +71,10 @@ boostbook boostdoc toc.section.depth=8 # How deep should recursive sections appear in the TOC? toc.max.depth=8 # How many levels should be created for each TOC? generate.section.toc.level=8 # Control depth of TOC generation in sections - generate.toc="chapter nop section nop" + generate.toc="chapter toc,title section nop reference nop" $(broot)/tools/boostbook/dtd : temp - examples images stylesheets ; diff --git a/src/beast/doc/README.md b/src/beast/doc/README.md deleted file mode 100644 index 02a9c555eb..0000000000 --- a/src/beast/doc/README.md +++ /dev/null @@ -1,72 +0,0 @@ -# Building documentation - -## Specifying Files - -To specify the source files for which to build documentation, modify `INPUT` -and its related fields in `doc/source.dox`. Note that the `INPUT` paths are -relative to the `doc/` directory. - -## Install Dependencies - -### Windows - -Install these dependencies: - -1. Install [Doxygen](http://www.stack.nl/~dimitri/doxygen/download.html) -2. Download the following zip files from [xsltproc](https://www.zlatkovic.com/pub/libxml/) - (Alternate download: ftp://ftp.zlatkovic.com/libxml/), - and extract the `bin\` folder contents into any folder in your path. - * iconv - * libxml2 - * libxslt - * zlib -3. Download [Boost](http://www.boost.org/users/download/) - 1. Extract the compressed file contents to your (new) `$BOOST_ROOT` location. - 2. Open a command prompt or shell in the `$BOOST_ROOT`. - 3. `./bootstrap.bat` - 4. If it is not already there, add your `$BOOST_ROOT` to your environment `$PATH`. - -### MacOS - -1. Install doxygen: - * Use homebrew to install: `brew install doxygen`. The executable will be - installed in `/usr/local/bin` which is already in your path. - * Alternatively, install from here: [doxygen](http://www.stack.nl/~dimitri/doxygen/download.html). - You'll then need to make doxygen available to your command line. You can - do this by adding a symbolic link from `/usr/local/bin` to the doxygen - executable. For example, `$ ln -s /Applications/Doxygen.app/Contents/Resources/doxygen /usr/local/bin/doxygen` -2. Install [Boost](http://www.boost.org/users/download/) - 1. Extract the compressed file contents to your (new) `$BOOST_ROOT` location. - 2. Open a command prompt or shell in the `$BOOST_ROOT`. - 3. `$ ./bootstrap.bat` - 4. If it is not already there, add your `$BOOST_ROOT` to your environment - `$PATH`. This makes the `b2` command available to the command line. -3. That should be all that's required. In OS X 10.11, at least, libxml2 and - libxslt come pre-installed. - -### Linux - -1. Install [Docker](https://docs.docker.com/engine/installation/) -2. Build Docker image. From the Beast root folder: -``` -sudo docker build -t beast-docs doc/ -``` - -## Do it - -### Windows & MacOS - -From the Beast root folder: -``` -cd doc -./makeqbk.sh && b2 -``` -The output will be in `doc/html`. - -### Linux - -From the Beast root folder: -``` -sudo docker run -v $PWD:/opt/beast --rm beast-docs -``` -The output will be in `doc/html`. diff --git a/src/beast/doc/concept/Body.qbk b/src/beast/doc/concept/Body.qbk new file mode 100644 index 0000000000..5421286afe --- /dev/null +++ b/src/beast/doc/concept/Body.qbk @@ -0,0 +1,105 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section:Body Body] + +A [*Body] type is supplied as a template argument to the __message__ class. It +controls both the type of the data member of the resulting message object, and +the algorithms used during parsing and serialization. + +In this table: + +* `X` is a type meeting the requirements of [*Body]. + +* `m` is a value of type `message` where `b` is a `bool` value + and `F` is a type meeting the requirements of [*Fields]. + +[table Body requirements +[[expression] [type] [semantics, pre/post-conditions]] +[ + [`X::value_type`] + [] + [ + The type of the `message::body` member. + If this is not movable or not copyable, the containing message + will be not movable or not copyable. + ] +][ + [`X::writer`] + [] + [ + If present, indicates that the body can hold a message body + parsing result. The type must meet the requirements of + __BodyWriter__. The implementation constructs an object of + this type to obtain buffers into which parsed body octets + are placed. + ] +][ + [`X::reader`] + [] + [ + If present, indicates that the body is serializable. The type + must meet the requirements of __BodyReader__. The implementation + constructs an object of this type to obtain buffers representing + the message body for serialization. + ] +][ + [`X::size(X::value_type v)`] + [`std::uint64_t`] + [ + This static member function is optional. It returns the payload + size of `v` not including any chunked transfer encoding. The + function shall not exit via an exception. + + When this function is present: + + * The function shall not fail + + * A call to + [link beast.ref.beast__http__message.payload_size `message::payload_size`] + will return the same value as `size`. + + * A call to + [link beast.ref.beast__http__message.prepare_payload `message::prepare_payload`] + will remove "chunked" from the Transfer-Encoding field if it appears + as the last encoding, and will set the Content-Length field to the + returned value. + + Otherwise, when the function is omitted: + + * A call to + [link beast.ref.beast__http__message.payload_size `message::payload_size`] + will return `boost::none`. + + * A call to + [link beast.ref.beast__http__message.prepare_payload `message::prepare_payload`] + will erase the Content-Length field, and add "chunked" as the last + encoding in the Transfer-Encoding field if it is not already present. + ] +][ + [`is_body`] + [`std::true_type`] + [ + An alias for `std::true_type` for `X`, otherwise an alias + for `std::false_type`. + ] +] +] + +[heading Exemplar] + +[concept_Body] + +[heading Models] + +* [link beast.ref.beast__http__basic_dynamic_body `basic_dynamic_body`] +* [link beast.ref.beast__http__buffer_body `buffer_body`] +* [link beast.ref.beast__http__dynamic_body `dynamic_body`] +* [link beast.ref.beast__http__empty_body `empty_body`] +* [link beast.ref.beast__http__string_body `string_body`] + +[endsect] diff --git a/src/beast/doc/concept/BodyReader.qbk b/src/beast/doc/concept/BodyReader.qbk new file mode 100644 index 0000000000..ec13aa1632 --- /dev/null +++ b/src/beast/doc/concept/BodyReader.qbk @@ -0,0 +1,117 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section:BodyReader BodyReader] + +A [*BodyReader] provides an online algorithm to obtain a sequence of zero +or more buffers from a body during serialization. The implementation creates +an instance of this type when needed, and calls into it one or more times to +retrieve buffers holding body octets. The interface of [*BodyReader] is +intended to obtain buffers for these scenarios: + +* A body that does not entirely fit in memory. +* A body produced incrementally from coroutine output. +* A body represented by zero or more buffers already in memory. +* A body whose size is not known ahead of time. +* Body data generated dynamically from other threads. +* Body data computed algorithmically. + +In this table: + +* `X` denotes a type meeting the requirements of [*BodyReader]. + +* `B` denotes a __Body__ where + `std::is_same::value == true`. + +* `a` denotes a value of type `X`. + +* `m` denotes a possibly const value of type `message&` where + `std::is_same:value == true`. + +* `ec` is a value of type [link beast.ref.beast__error_code `error_code&`]. + +* `R` is the type `boost::optional>`. + +[heading Associated Types] + +* __Body__ + +* [link beast.ref.beast__http__is_body_reader `is_body_reader`] + +[heading BodyReader requirements] +[table Valid Expressions +[[Expression] [Type] [Semantics, Pre/Post-conditions]] +[ + [`X::const_buffers_type`] + [] + [ + A type which meets the requirements of __ConstBufferSequence__. + This is the type of buffer returned by `X::get`. + ] +][ + [`X(m);`] + [] + [ + Constructible from `m`. The lifetime of `m` is guaranteed + to end no earlier than after the `X` is destroyed. + The reader shall not access the contents of `m` before the + first call to `init`, permitting lazy construction of the + message. + + The constructor may optionally require that `m` is const, which + has these consequences: + + * If `X` requires that `m` is a const reference, then serializers + constructed for messages with this body type will also require a + const reference to a message, otherwise: + + * If `X` requires that `m` is a non-const reference, then serializers + constructed for messages with this body type will aso require + a non-const reference to a message. + ] +][ + [`a.init(ec)`] + [] + [ + Called once to fully initialize the object before any calls to + `get`. The message body becomes valid before entering this function, + and remains valid until the reader is destroyed. + The function will ensure that `!ec` is `true` if there was + no error or set to the appropriate error code if there was one. + ] +][ + [`a.get(ec)`] + [`R`] + [ + Called one or more times after `init` succeeds. This function + returns `boost::none` if all buffers representing the body have + been returned in previous calls or if it sets `ec` to indicate an + error. Otherwise, if there are buffers remaining the function + should return a pair with the first element containing a non-zero + length buffer sequence representing the next set of octets in + the body, while the second element is a `bool` meaning `true` + if there may be additional buffers returned on a subsequent call, + or `false` if the buffer returned on this call is the last + buffer representing the body. + The function will ensure that `!ec` is `true` if there was + no error or set to the appropriate error code if there was one. + ] +] +] + +[heading Exemplar] + +[concept_BodyReader] + +[heading Models] + +* [link beast.ref.beast__http__basic_dynamic_body.reader `basic_dynamic_body::reader`] +* [link beast.ref.beast__http__basic_file_body__reader `basic_file_body::reader`] +* [link beast.ref.beast__http__basic_string_body.reader `basic_string_body::reader`] +* [link beast.ref.beast__http__empty_body.reader `empty_body::reader`] + +[endsect] diff --git a/src/beast/doc/concept/BodyWriter.qbk b/src/beast/doc/concept/BodyWriter.qbk new file mode 100644 index 0000000000..54c93fdcba --- /dev/null +++ b/src/beast/doc/concept/BodyWriter.qbk @@ -0,0 +1,119 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section:BodyWriter BodyWriter] + +A [*BodyWriter] provides an online algorithm to transfer a series of zero +or more buffers containing parsed body octets into a message container. The +__parser__ creates an instance of this type when needed, and calls into +it zero or more times to transfer buffers. The interface of [*BodyWriter] +is intended to allow the conversion of buffers into these scenarios for +representation: + +* Storing a body in a dynamic buffer +* Storing a body in a user defined container with a custom allocator +* Transformation of incoming body data before storage, for example + to compress it first. +* Saving body data to a file + +In this table: + +* `X` denotes a type meeting the requirements of [*BodyWriter]. + +* `B` denotes a __Body__ where + `std::is_same::value == true`. + +* `a` denotes a value of type `X`. + +* `b` is an object whose type meets the requirements of __ConstBufferSequence__ + +* `m` denotes a value of type `message&` where + `std::is_same::value == true`. + +* `n` is a value of type `boost::optional`. + +* `ec` is a value of type [link beast.ref.beast__error_code `error_code&`]. + +[heading Associated Types] + +* __Body__ +* [link beast.ref.beast__http__is_body_writer `is_body_writer`] + +[table Writer requirements +[[expression] [type] [semantics, pre/post-conditions]] +[ + [`X(m);`] + [] + [ + Constructible from `m`. The lifetime of `m` is guaranteed to + end no earlier than after the `X` is destroyed. The constructor + will be called after a complete header is stored in `m`, and + before parsing body octets for messages indicating that a body + is present The writer shall not access the contents of `m` before + the first call to `init`, permitting lazy construction of the + message. + + The function will ensure that `!ec` is `true` if there was + no error or set to the appropriate error code if there was one. + ] +][ + [`a.init(n, ec)`] + [] + [ + Called once to fully initialize the object before any calls to + `put`. The message body is valid before entering this function, + and remains valid until the writer is destroyed. + The value of `n` will be set to the content length of the + body if known, otherwise `n` will be equal to `boost::none`. + Implementations of [*BodyWriter] may use this information to + optimize allocation. + + The function will ensure that `!ec` is `true` if there was + no error or set to the appropriate error code if there was one. + ] +][ + [`a.put(b,ec)`] + [`std::size_t`] + [ + This function is called to append some or all of the buffers + specified by `b` into the body representation. The number of + bytes inserted from `b` is returned. If the number of bytes + inserted is less than the total input, the remainder of the + input will be presented in the next call to `put`. + The function will ensure that `!ec` is `true` if there was + no error or set to the appropriate error code if there was one. + ] +][ + [`a.finish(ec)`] + [] + [ + This function is called when no more body octets are remaining. + The function will ensure that `!ec` is `true` if there was + no error or set to the appropriate error code if there was one. + ] +][ + [`is_body_writer`] + [`std::true_type`] + [ + An alias for `std::true_type` for `B`, otherwise an alias + for `std::false_type`. + ] +] +] + +[heading Exemplar] + +[concept_BodyWriter] + +[heading Models] + +* [link beast.ref.beast__http__basic_dynamic_body.writer `basic_dynamic_body::writer`] +* [link beast.ref.beast__http__basic_file_body__reader `basic_file_body::writer`] +* [link beast.ref.beast__http__basic_string_body.writer `basic_string_body::writer`] +* [link beast.ref.beast__http__empty_body.writer `empty_body::writer`] + +[endsect] diff --git a/src/beast/doc/concept/BufferSequence.qbk b/src/beast/doc/concept/BufferSequence.qbk new file mode 100644 index 0000000000..812d92ffb5 --- /dev/null +++ b/src/beast/doc/concept/BufferSequence.qbk @@ -0,0 +1,15 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section:BufferSequence BufferSequence] + +A [*BufferSequence] is a type meeting either of the following requirements: + +* __ConstBufferSequence__ +* __MutableBufferSequence__ + +[endsect] diff --git a/src/beast/doc/types/DynamicBuffer.qbk b/src/beast/doc/concept/DynamicBuffer.qbk similarity index 81% rename from src/beast/doc/types/DynamicBuffer.qbk rename to src/beast/doc/concept/DynamicBuffer.qbk index 792c25914d..c9abd7df36 100644 --- a/src/beast/doc/types/DynamicBuffer.qbk +++ b/src/beast/doc/concept/DynamicBuffer.qbk @@ -5,7 +5,7 @@ file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] -[section:DynamicBuffer DynamicBuffer requirements] +[section:DynamicBuffer DynamicBuffer] A dynamic buffer encapsulates memory storage that may be automatically resized as required, where the memory is divided into an input sequence followed by an @@ -19,7 +19,8 @@ The interface to this concept is intended to permit the following implementation strategies: * A single contiguous octet array, which is reallocated as necessary to - accommodate changes in the size of the octet sequence. + accommodate changes in the size of the octet sequence. This is the + implementation approach currently offered by __flat_buffer__. * A sequence of one or more octet arrays, where each array is of the same size. Additional octet array objects are appended to the sequence to @@ -28,19 +29,19 @@ implementation strategies: * A sequence of one or more octet arrays of varying sizes. Additional octet array objects are appended to the sequence to accommodate changes in the size of the character sequence. This is the implementation approach - currently offered by [link beast.ref.basic_streambuf `basic_streambuf`]. + currently offered by __multi_buffer__. -In the table below: +In this table: * `X` denotes a dynamic buffer class. * `a` denotes a value of type `X`. * `c` denotes a (possibly const) value of type `X`. * `n` denotes a value of type `std::size_t`. -* `T` denotes a type meeting the requirements for [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/ConstBufferSequence.html `ConstBufferSequence`]. -* `U` denotes a type meeting the requirements for [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/MutableBufferSequence.html `MutableBufferSequence`]. +* `T` denotes a type meeting the requirements for __ConstBufferSequence__. +* `U` denotes a type meeting the requirements for __MutableBufferSequence__. [table DynamicBuffer requirements -[[operation] [type] [semantics, pre/post-conditions]] +[[expression] [type] [semantics, pre/post-conditions]] [ [`X::const_buffers_type`] [`T`] @@ -122,4 +123,14 @@ In the table below: ] ] +[heading Models] + +* [link beast.ref.beast__basic_flat_buffer `basic_flat_buffer`] +* [link beast.ref.beast__basic_multi_buffer `basic_multi_buffer`] +* [link beast.ref.beast__drain_buffer `drain_buffer`] +* [link beast.ref.beast__flat_buffer `flat_buffer`] +* [link beast.ref.beast__multi_buffer `multi_buffer`] +* [link beast.ref.beast__static_buffer `static_buffer`] +* [link beast.ref.beast__static_buffer_n `static_buffer_n`] + [endsect] diff --git a/src/beast/doc/concept/Fields.qbk b/src/beast/doc/concept/Fields.qbk new file mode 100644 index 0000000000..a66a4282c0 --- /dev/null +++ b/src/beast/doc/concept/Fields.qbk @@ -0,0 +1,225 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section:Fields Fields] + +An instance of [*Fields] is a container for holding HTTP header fields +and their values. The implementation also calls upon the container to +store the request target and non-standard strings for method and obsolete +reason phrase as needed. Types which meet these requirements can always +be serialized. + +[heading Associated Types] + +* __FieldsReader__ + +* [link beast.ref.beast__http__is_fields `is_fields`] + +[heading Requirements] + +In this table: + +* `F` denotes a type that meets the requirements of [*Fields]. + +* `R` denotes a type meeting the requirements of __FieldsReader__. + +* `a` denotes a value of type `F`. + +* `c` denotes a (possibly const) value of type `F`. + +* `b` is a value of type `bool` + +* `n` is a value of type `boost::optional`. + +* `s` is a value of type [link beast.ref.beast__string_view `string_view`]. + +* `v` is a value of type `unsigned int` representing the HTTP-version. + +[table Valid expressions +[[Expression] [Type] [Semantics, Pre/Post-conditions]] +[ + [`F::reader`] + [`R`] + [ + A type which meets the requirements of __FieldsReader__. + ] +][ + [`c.get_method_impl()`] + [`string_view`] + [ + Returns the method text. + The implementation only calls this function for request + headers when retrieving the method text previously set + with a call to `set_method_impl` using a non-empty string. + ] +][ + [`c.get_target_impl()`] + [`string_view`] + [ + Returns the target string. + The implementation only calls this function for request headers. + ] +][ + [`c.get_reason_impl()`] + [`string_view`] + [ + Returns the obsolete request text. + The implementation only calls this for response headers when + retrieving the reason text previously set with a call to + `set_reason_impl` using a non-empty string. + ] +][ + [`c.get_chunked_impl()`] + [`bool`] + [ + Returns `true` if the + [@https://tools.ietf.org/html/rfc7230#section-3.3.1 [*Transfer-Encoding]] + field value indicates that the payload is chunk encoded. Both + of these conditions must be true: + [itemized_list + [ + The Transfer-Encoding field is present in the message. + ][ + The last item the value of the field is "chunked". + ]] + ] +][ + [`c.get_keep_alive_impl(v)`] + [`bool`] + [ + Returns `true` if the semantics of the + [@https://tools.ietf.org/html/rfc7230#section-6.1 [*Connection]] + field and version indicate that the connection should remain + open after the corresponding response is transmitted or received: + + [itemized_list + [ + If `(v < 11)` the function returns `true` if the "keep-alive" + token is present in the Connection field value. Otherwise the + function returns `false`. + ][ + If `(v == 11)`, the function returns `false` if the "close" + token is present in the Connection field value. Otherwise the + function returns `true`. + ]] + ] +][ + [`a.set_method_impl(s)`] + [] + [ + Stores a copy of `s` as the method text, or erases the previously + stored value if `s` is empty. + The implementation only calls this function for request headers. + This function may throw `std::invalid_argument` if the operation + is not supported by the container. + ] +][ + [`a.set_target_impl(s)`] + [] + [ + Stores a copy of `s` as the target, or erases the previously + stored value if `s` is empty. + The implementation only calls this function for request headers. + This function may throw `std::invalid_argument` if the operation + is not supported by the container. + ] +][ + [`a.set_reason_impl(s)`] + [] + [ + Stores a copy of `s` as the reason text, or erases the previously + stored value of the reason text if `s` is empty. + The implementation only calls this function for request headers. + This function may throw `std::invalid_argument` if the operation + is not supported by the container. + ] +][ + [`a.set_chunked_impl(b)`] + [] + [ + Adjusts the + [@https://tools.ietf.org/html/rfc7230#section-3.3.1 [*Transfer-Encoding]] + field as follows: + + [itemized_list + [ + If `b` is `true`, the "chunked" token is appended + to the list of encodings if it does not already appear + last in the list. + If the Transfer-Encoding field is absent, the field will + be inserted to the container with the value "chunked". + ][ + If `b` is `false, the "chunked" token is removed from the + list of encodings if it appears last in the list. + If the result of the removal leaves the list of encodings + empty, the Transfer-Encoding field shall not appear when + the associated __FieldsReader__ serializes the fields. + ]] + ] + +][ + [`a.set_content_length_impl(n)`] + [] + [ + Adjusts the + [@https://tools.ietf.org/html/rfc7230#section-3.3.2 [*Content-Length]] + field as follows: + + [itemized_list + [ + If `n` contains a value, the Content-Length field + will be set to the text representation of the value. + Any previous Content-Length fields are removed from + the container. + ][ + If `n` does not contain a value, any present Content-Length + fields are removed from the container. + ]] + ] + +][ + [`a.set_keep_alive_impl(v,b)`] + [] + [ + Adjusts the + [@https://tools.ietf.org/html/rfc7230#section-6.1 [*Connection]] + field value depending on the values of `v` and `b`. The field + value is treated as + [@https://tools.ietf.org/html/rfc7230#section-6.1 ['connection-option]] + (rfc7230). + + [itemized_list + [ + If `(v < 11 && b)`, then all "close" tokens present in the + value are removed, and the "keep-alive" token is added to + the valueif it is not already present. + ][ + If `(v < 11 && ! b)`, then all "close" and "keep-alive" + tokens present in the value are removed. + + ][ + If `(v == 11 && b)`, then all "keep-alive" and "close" + tokens present in the value are removed. + ][ + If `(v == 11 && ! b)`, then all "keep-alive" tokens present + in the value are removed, and the "close" token is added to + the value if it is not already present. + ]] + ] + +]] + +[heading Exemplar] + +[concept_Fields] + +[heading Models] + +* [link beast.ref.beast__http__basic_fields `basic_fields`] +* [link beast.ref.beast__http__fields `fields`] + +[endsect] diff --git a/src/beast/doc/concept/FieldsReader.qbk b/src/beast/doc/concept/FieldsReader.qbk new file mode 100644 index 0000000000..f7fa5fd45f --- /dev/null +++ b/src/beast/doc/concept/FieldsReader.qbk @@ -0,0 +1,84 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section:FieldsReader FieldsReader] + +A [*FieldsReader] provides a algorithm to obtain a sequence of buffers +representing the complete serialized HTTP/1 header for a set of fields. +The implementation constructs an instance of this type when needed, and +calls into it once to retrieve the buffers. + +[heading Associated Types] + +* __FieldsReader__ + +[heading Requirements] + +In this table: + +* `X` denotes a type that meets the requirements of [*FieldsReader]. + +* `F` denotes a __Fields__ where + `std::is_same::value == true`. + +* `a` is a value of type `X`. + +* `f` is a value of type `F`. + +* `v` is an `unsigned` value representing the HTTP version. + +* `c` is an `unsigned` representing the HTTP status-code. + +* `m` is a value of type [link beast.ref.beast__http__verb `verb`]. + +[table Valid expressions +[[expression][type][semantics, pre/post-conditions]] +[ + [`X::const_buffers_type`] + [] + [ + A type which meets the requirements of __ConstBufferSequence__. + This is the type of buffer returned by `X::get`. + ] +][ + [`X(f,v,m)`] + [] + [ + The implementation calls this constructor to indicate + that the fields being serialized form part of an HTTP + request. The lifetime of `f` is guaranteed + to end no earlier than after the `X` is destroyed. + ] +][ + [`X(f,v,c)`] + [] + [ + The implementation calls this constructor to indicate + that the fields being serialized form part of an HTTP + response. The lifetime of `f` is guaranteed + to end no earlier than after the `X` is destroyed. + ] +][ + [`a.get()`] + [`X::const_buffers_type`] + [ + Called once after construction, this function returns + a constant buffer sequence containing the serialized + representation of the HTTP request or response including + the final carriage return linefeed sequence (`"\r\n"`). + ] +]] + +[heading Exemplar] + +[concept_FieldsReader] + +[heading Models] + +* [link beast.ref.beast__http__basic_fields.reader `basic_fields::reader`] + +[endsect] diff --git a/src/beast/doc/concept/File.qbk b/src/beast/doc/concept/File.qbk new file mode 100644 index 0000000000..35decc654d --- /dev/null +++ b/src/beast/doc/concept/File.qbk @@ -0,0 +1,173 @@ +[/ + Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section:File File] + +The [*File] concept abstracts access to files in the underlying file system. +To support other platform interfaces, users may author their own [*File] +types which meet these requirements. + +In this table: + +* `F` is a [*File] type +* `f` is an instance of `F` +* `p` is a value of type `char const*` which points to a null + terminated utf-8 encoded string. +* `m` is an instance of [link beast.ref.beast__file_mode `file_mode`] +* `n` is a number of bytes, convertible to `std::size_t` +* `o` is a byte offset in the file, convertible to `std::uint64_t` +* `b` is any non-const pointer to memory +* `c` is any possibly-const pointer to memory +* `ec` is a reference of type [link beast.ref.beast__error_code `error_code`] + +[heading Associated Types] + +* [link beast.ref.beast__file_mode `file_mode`] +* [link beast.ref.beast__is_file `is_file`] + +[heading File Requirements] +[table Valid Expressions +[[Operation] [Return Type] [Semantics, Pre/Post-conditions]] +[ + [`F()`] + [ ] + [ + Default constructable + ] +] +[ + [`f.~F()`] + [ ] + [ + Destructible. + If `f` refers to an open file, it is first closed + as if by a call to `close` with the error ignored. + ] +] +[ + [`f.is_open()`] + [`bool`] + [ + Returns `true` if `f` refers to an open file, `false` otherwise. + ] +] +[ + [`f.close(ec)`] + [] + [ + If `f` refers to an open file, thie function attempts to + close the file. + Regardless of whether an error occurs or not, a subsequent + call to `f.is_open()` will return `false`. + The function will ensure that `!ec` is `true` if there was + no error or set to the appropriate error code if an error + occurred. + ] +] +[ + [`f.open(p,m,ec)`] + [] + [ + Attempts to open the file at the path specified by `p` + with the mode specified by `m`. + Upon success, a subsequent call to `f.is_open()` will + return `true`. + If `f` refers to an open file, it is first closed + as if by a call to `close` with the error ignored. + The function will ensure that `!ec` is `true` if there was + no error or set to the appropriate error code if an error + occurred. + + ] +] +[ + [`f.size(ec)`] + [`std::uint64_t`] + [ + If `f` refers to an open file, this function attempts to + determine the file size and return its value. + If `f` does not refer to an open file, the function will + set `ec` to `errc::invalid_argument` and return 0. + The function will ensure that `!ec` is `true` if there was + no error or set to the appropriate error code if an error + occurred. + ] +] +[ + [`f.pos(ec)`] + [`std::uint64_t`] + [ + If `f` refers to an open file, this function attempts to + determine the current file offset and return it. + If `f` does not refer to an open file, the function will + set `ec` to `errc::invalid_argument` and return 0. + The function will ensure that `!ec` is `true` if there was + no error or set to the appropriate error code if an error + occurred. + ] +] +[ + [`f.seek(o,ec)`] + [] + [ + Attempts to reposition the current file offset to the value + `o`, which represents a byte offset relative to the beginning + of the file. + If `f` does not refer to an open file, the function will + set `ec` to `errc::invalid_argument` and return immediately. + The function will ensure that `!ec` is `true` if there was + no error or set to the appropriate error code if an error + occurred. + ] +] +[ + [`f.read(b,n,ec)`] + [`std::size_t`] + [ + Attempts to read `n` bytes starting at the current file offset + from the open file referred to by `f`. + Bytes read are stored in the memory buffer at address `b` which + must be at least `n` bytes in size. + The function advances the file offset by the amount read, and + returns the number of bytes actually read, which may be less + than `n`. + If `f` does not refer to an open file, the function will + set `ec` to `errc::invalid_argument` and return immediately. + The function will ensure that `!ec` is `true` if there was + no error or set to the appropriate error code if an error + occurred. + ] +] +[ + [`f.write(c,n,ec)`] + [`std::size_t`] + [ + Attempts to write `n` bytes from the buffer pointed to by `c` to + the current file offset of the open file referred to by `f`. + The memory buffer at `c` must point to storage of at least `n` + bytes meant to be copied to the file. + The function advances the file offset by the amount written, + and returns the number of bytes actually written, which may be + less than `n`. + If `f` does not refer to an open file, the function will + set `ec` to `errc::invalid_argument` and return immediately. + The function will ensure that `!ec` is `true` if there was + no error or set to the appropriate error code if an error + occurred. + ] +] +] + +[heading Exemplar] + +[concept_File] + +[heading Models] + +* [link beast.ref.beast__file_stdio `file_stdio`] + +[endsect] diff --git a/src/beast/doc/concept/Streams.qbk b/src/beast/doc/concept/Streams.qbk new file mode 100644 index 0000000000..d55322daa6 --- /dev/null +++ b/src/beast/doc/concept/Streams.qbk @@ -0,0 +1,34 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section:streams Stream] + +Stream types represent objects capable of performing synchronous or +asynchronous I/O. They are based on concepts from `boost::asio`. + +[heading:Stream Stream] + +A type modeling [*Stream] meets either or both of the following requirements: + +* [*AsyncStream] +* [*SyncStream] + +[heading:AsyncStream AsyncStream] + +A type modeling [*AsyncStream] meets the following requirements: + +* __AsyncReadStream__ +* __AsyncWriteStream__ + +[heading:SyncStream SyncStream] + +A type modeling [*SyncStream] meets the following requirements: + +* __SyncReadStream__ +* __SyncWriteStream__ + +[endsect] diff --git a/src/beast/doc/design.qbk b/src/beast/doc/design.qbk deleted file mode 100644 index ff925b4ab2..0000000000 --- a/src/beast/doc/design.qbk +++ /dev/null @@ -1,654 +0,0 @@ -[/ - Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) - - Distributed under the Boost Software License, Version 1.0. (See accompanying - file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -] - -[section:design Design Choices] - -[block ''' - - HTTP FAQ - WebSocket FAQ - Comparison to Zaphoyd Studios WebSocket++ - -'''] - -The implementations are driven by business needs of cryptocurrency server -applications (e.g. [@https://ripple.com Ripple]) written in C++. These -needs were not met by existing solutions so Beast was written from scratch -as a solution. Beast's design philosophy avoids flaws exhibited by other -libraries: - -* Don't try to do too much. - -* Don't sacrifice performance. - -* Mimic Boost.Asio; familiarity breeds confidence. - -* Role-symmetric interfaces; client and server the same (or close to it). - -* Leave important decisions to the user, such as allocating memory or - managing flow control. - -Beast uses the __DynamicBuffer__ concept presented in the Networking TS -(__N4588__), and relies heavily on the Boost.Asio __ConstBufferSequence__ -and __MutableBufferSequence__ concepts for passing buffers to functions. -The authors have found the dynamic buffer and buffer sequence interfaces to -be optimal for interacting with Asio, and for other tasks such as incremental -parsing of data in buffers (for example, parsing websocket frames stored -in a [link beast.ref.static_streambuf `static_streambuf`]). - -During the development of Beast the authors have studied other software -packages and in particular the comments left during the Boost Review process -of other packages offering similar functionality. In this section and the -FAQs that follow we attempt to answer those questions that are also applicable -to Beast. - -[variablelist -[[ - "I would also like to see instances of this library being used - in production. That would give some evidence that the design - works in practice." -][ - Beast.HTTP and Beast.WebSocket are production ready and currently - running on public servers receiving traffic and handling millions of - dollars worth of financial transactions daily. The servers run [*rippled], - open source software ([@https://github.com/ripple/rippled repository]) - implementing the - [@https://ripple.com/files/ripple_consensus_whitepaper.pdf [*Ripple Consensus Protocol]], - technology provided by [@http://ripple.com Ripple]. -]] - -] - - - -[section:http HTTP FAQ] - -For HTTP we model the message to maximize flexibility of implementation -strategies while allowing familiar verbs such as [*`read`] and [*`write`]. -The HTTP interface is further driven by the needs of the WebSocket module, -as a WebSocket session requires a HTTP Upgrade handshake exchange at the -start. Other design goals: - -* Keep it simple. - -* Stay low level; don't invent a whole web server or client. - -* Allow for customizations, if the user needs it. - -[variablelist - -[[ - "Some more advanced examples, e.g. including TLS with client/server - certificates would help." -][ - The HTTP interface doesn't try to reinvent the wheel, it just uses - the `boost::asio::ip::tcp::socket` or `boost::asio::ssl::stream` that - you set up beforehand. Callers use the interfaces already existing - on those objects to make outgoing connections, accept incoming connections, - or establish TLS sessions with certificates. We find the available Asio - examples for performing these tasks sufficient. -]] - -[[ - "A built-in router?" -][ - We presume this means a facility to match expressions against the URI - in HTTP requests, and dispatch them to calling code. The authors feel - that this is a responsibility of higher level code. Beast.HTTP does - not try to offer a web server. -]] - -[[ - "Cookies? Forms/File Uploads?" -][ - Cookies, or managing these types of HTTP headers in general, is the - responsibility of higher levels. Beast.HTTP just tries to get complete - messages to and from the calling code. It deals in the HTTP headers just - enough to process the message body and leaves the rest to callers. However, - for forms and file uploads the symmetric interface of the message class - allows HTTP requests to include arbitrary body types including those needed - to upload a file or fill out a form. -]] - -[[ - "...supporting TLS (is this a feature? If not this would be a show-stopper), - etc." -][ - Beast.HTTP does not provide direct facilities for implementing TLS - connections; however, the interfaces already existing on the - `boost::asio::ssl::stream` are available and can be used to establish - secure connections. Then, functions like `http::read` or `http::async_write` - can work with those encrypted connections with no problem. -]] - -[[ - "There should also be more examples of how to integrate the http service - with getting files from the file system, generating responses CGI-style" -][ - The design goal for the library is to not try to invent a web server. - We feel that there is a strong need for a basic implementation that - models the HTTP message and provides functions to send and receive them - over Asio. Such an implementation should serve as a building block upon - which higher abstractions such as the aforementioned HTTP service or - cgi-gateway can be built. - - One of the example programs implements a simple HTTP server that - delivers files from the filesystem. -]] - -[[ - "You should send a 100-continue to ask for the rest of the body if required." -][ - The Beast interface needs to support this functionality (by allowing this - special case of partial message parsing and serialization). Specifically, - it should let callers read the request up to just before the body, - and let callers write the request up to just before the body. However, - making use of this behavior should be up to callers (since Beast is low - level). -]] - -[[ - "What about HTTP/2?" -][ - Many reviewers feel that HTTP/2 support is an essential feature of - a HTTP library. The authors agree that HTTP/2 is important but also - feel that the most sensible implementation is one that does not re-use - the same network reading and writing interface for 2 as that for 1.0 - and 1.1. - - The Beast.HTTP message model is suitable for HTTP/2 and can be re-used. - The IETF HTTP Working Group adopted message compatiblity with HTTP/1.x - as an explicit goal. A parser can simply emit full headers after - decoding the compressed HTTP/2 headers. The stream ID is not logically - part of the message but rather message metadata and should be - communicated out-of-band (see below). HTTP/2 sessions begin with a - traditional HTTP/1.1 Upgrade similar in fashion to the WebSocket - upgrade. An HTTP/2 implementation can use existing Beast.HTTP primitives - to perform this handshake. - - Free functions for HTTP/2 sessions are not possible because of the - requirement to maintain per-session state. For example, to decode the - compressed headers. Or to remember and respect the remote peer's window - settings. The authors propose that a HTTP/2 implementation be written - as a separate class template, similar to the `websocket::stream` but with - additional interfaces to support version 2 features. We feel that - Beast.HTTP offers enough useful functionality to justify inclusion, - so that developers can take advantage of it right away instead of - waiting. -]] - -] - -[endsect] - - - -[section:websocket WebSocket FAQ] - -[variablelist - -[[ - What about message compression? -][ - Beast WebSocket supports the permessage-deflate extension described in - [@https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-00 draft-ietf-hybi-permessage-compression-00]. - The library comes with a header-only, C++11 port of ZLib's "deflate" codec - used in the implementation of the permessage-deflate extension. -]] - -[[ - Where is the TLS/SSL interface? -][ - The `websocket::stream` wraps the socket or stream that you provide - (for example, a `boost::asio::ip::tcp::socket` or a - `boost::asio::ssl::stream`). You establish your TLS connection using the - interface on `ssl::stream` like shown in all of the Asio examples, then - construct your `websocket::stream` around it. It works perfectly fine; - Beast.WebSocket doesn't try to reinvent the wheel or put a fresh coat of - interface paint on the `ssl::stream`. - - The WebSocket implementation [*does] provide support for shutting down - the TLS connection through the use of the ADL compile-time virtual functions - [link beast.ref.websocket__teardown `teardown`] and - [link beast.ref.websocket__async_teardown `async_teardown`]. These will - properly close the connection as per rfc6455 and overloads are available - for TLS streams. Callers may provide their own overloads of these functions - for user-defined next layer types. -]] - -] - -[endsect] - - - -[section:websocketpp Comparison to Zaphoyd Studios WebSocket++] - -[variablelist - -[[ - How does this compare to [@https://www.zaphoyd.com/websocketpp websocketpp], - an alternate header-only WebSocket implementation? -][ - [variablelist - - [[1. Synchronous Interface][ - - Beast offers full support for WebSockets using a synchronous interface. It - uses the same style of interfaces found in Boost.Asio: versions that throw - exceptions, or versions that return the error code in a reference parameter: - - [table - [ - [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L774 Beast]] - [websocketpp] - ][ - [``` - template - void - read(opcode& op, DynamicBuffer& dynabuf) - ```] - [ - // - ] - ]]]] - - [[2. Connection Model][ - - websocketpp supports multiple transports by utilizing a trait, the `config::transport_type` - ([@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/asio/connection.hpp#L60 asio transport example]) - To get an idea of the complexity involved with implementing a transport, - compare the asio transport to the - [@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/iostream/connection.hpp#L59 `iostream` transport] - (a layer that allows websocket communication over a `std::iostream`). - - In contrast, Beast abstracts the transport by defining just one [*`NextLayer`] - template argument The type requirements for [*`NextLayer`] are - already familiar to users as they are documented in Asio: - __AsyncReadStream__, __AsyncWriteStream__, __SyncReadStream__, __SyncWriteStream__. - - The type requirements for instantiating `beast::websocket::stream` versus - `websocketpp::connection` with user defined types are vastly reduced - (18 functions versus 2). Note that websocketpp connections are passed by - `shared_ptr`. Beast does not use `shared_ptr` anywhere in its public interface. - A `beast::websocket::stream` is constructible and movable in a manner identical - to a `boost::asio::ip::tcp::socket`. Callers can put such objects in a - `shared_ptr` if they want to, but there is no requirement to do so. - - [table - [ - [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp Beast]] - [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L234 websocketpp]] - ][ - [``` - template - class stream - { - NextLayer next_layer_; - ... - } - ```] - [``` - template - class connection - : public config::transport_type::transport_con_type - , public config::connection_base - { - public: - typedef lib::shared_ptr ptr; - ... - } - ```] - ]]]] - - [[3. Client and Server Role][ - - websocketpp provides multi-role support through a hierarchy of - different classes. A `beast::websocket::stream` is role-agnostic, it - offers member functions to perform both client and server handshakes - in the same class. The same types are used for client and server - streams. - - [table - [ - [Beast] - [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/roles/server_endpoint.hpp#L39 websocketpp], - [@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/roles/client_endpoint.hpp#L42 also]] - ][ - [ - // - ] - [``` - template - class client : public endpoint,config>; - template - class server : public endpoint,config>; - ```] - ]]]] - - [[4. Thread Safety][ - - websocketpp uses mutexes to protect shared data from concurrent - access. In contrast, Beast does not use mutexes anywhere in its - implementation. Instead, it follows the Asio pattern. Calls to - asynchronous initiation functions use the same method to invoke - intermediate handlers as the method used to invoke the final handler, - through the __asio_handler_invoke__ mechanism. - - The only requirement in Beast is that calls to asynchronous initiation - functions are made from the same implicit or explicit strand. For - example, if the `io_service` associated with a `beast::websocket::stream` - is single threaded, this counts as an implicit strand and no performance - costs associated with mutexes are incurred. - - [table - [ - [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/impl/read_frame_op.ipp#L118 Beast]] - [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/iostream/connection.hpp#L706 websocketpp]] - ][ - [``` - template - friend - void asio_handler_invoke(Function&& f, read_frame_op* op) - { - return boost_asio_handler_invoke_helpers::invoke(f, op->d_->h); - } - ```] - [``` - mutex_type m_read_mutex; - ```] - ]]]] - - [[5. Callback Model][ - - websocketpp requires a one-time call to set the handler for each event - in its interface (for example, upon message receipt). The handler is - represented by a `std::function` equivalent. Its important to recognize - that the websocketpp interface performs type-erasure on this handler. - - In comparison, Beast handlers are specified in a manner identical to - Boost.Asio. They are function objects which can be copied or moved but - most importantly they are not type erased. The compiler can see - through the type directly to the implementation, permitting - optimization. Furthermore, Beast follows the Asio rules for treatment - of handlers. It respects any allocation, continuation, or invocation - customizations associated with the handler through the use of argument - dependent lookup overloads of functions such as `asio_handler_allocate`. - - The Beast completion handler is provided at the call site. For each - call to an asynchronous initiation function, it is guaranteed that - there will be exactly one final call to the handler. This functions - exactly the same way as the asynchronous initiation functions found in - Boost.Asio, allowing the composition of higher level abstractions. - - [table - [ - [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L834 Beast]] - [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L281 websocketpp], - [@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L473 also]] - ][ - [``` - template - typename async_completion::result_type - async_read(opcode& op, DynamicBuffer& dynabuf, ReadHandler&& handler); - ```] - [``` - typedef lib::function message_handler; - void set_message_handler(message_handler h); - ```] - ]]]] - - [[6. Extensible Asynchronous Model][ - - Beast fully supports the - [@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3896.pdf Extensible Asynchronous Model] - developed by Christopher Kohlhoff, author of Boost.Asio (see Section 8). - - Beast websocket asynchronous interfaces may be used seamlessly with - `std::future` stackful/stackless coroutines, or user defined customizations. - - [table - [ - [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/impl/stream.ipp#L378 Beast]] - [websocketpp] - ][ - [``` - beast::async_completion completion(handler); - read_op{ - completion.handler, *this, op, streambuf}; - return completion.result.get(); - ```] - [ - // - ] - ]]]] - - [[7. Message Buffering][ - - websocketpp defines a message buffer, passed in arguments by - `shared_ptr`, and an associated message manager which permits - aggregation and reuse of memory. The implementation of - `websocketpp::message` uses a `std::string` to hold the payload. If an - incoming message is broken up into multiple frames, the string may be - reallocated for each continuation frame. The `std::string` always uses - the standard allocator, it is not possible to customize the choice of - allocator. - - Beast allows callers to specify the object for receiving the message - or frame data, which is of any type meeting the requirements of - __DynamicBuffer__ (modeled after `boost::asio::streambuf`). - - Beast comes with the class __basic_streambuf__, an efficient - implementation of the __DynamicBuffer__ concept which makes use of multiple - allocated octet arrays. If an incoming message is broken up into - multiple pieces, no reallocation occurs. Instead, new allocations are - appended to the sequence when existing allocations are filled. Beast - does not impose any particular memory management model on callers. The - __basic_streambuf__ provided by beast supports standard allocators through - a template argument. Use the __DynamicBuffer__ that comes with beast, - customize the allocator if you desire, or provide your own type that - meets the requirements. - - [table - [ - [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L774 Beast]] - [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/message_buffer/message.hpp#L78 websocketpp]] - ][ - [``` - template - read(opcode& op, DynamicBuffer& dynabuf); - ```] - [``` - template class con_msg_manager> - class message { - public: - typedef lib::shared_ptr ptr; - ... - std::string m_payload; - ... - }; - ```] - ]]]] - - [[8. Sending Messages][ - - When sending a message, websocketpp requires that the payload is - packaged in a `websocketpp::message` object using `std::string` as the - storage, or it requires a copy of the caller provided buffer by - constructing a new message object. Messages are placed onto an - outgoing queue. An asynchronous write operation runs in the background - to clear the queue. No user facing handler can be registered to be - notified when messages or frames have completed sending. - - Beast doesn't allocate or make copies of buffers when sending data. The - caller's buffers are sent in-place. You can use any object meeting the - requirements of - [@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/ConstBufferSequence.html ConstBufferSequence], - permitting efficient scatter-gather I/O. - - The [*ConstBufferSequence] interface allows callers to send data from - memory-mapped regions (not possible in websocketpp). Callers can also - use the same buffers to send data to multiple streams, for example - broadcasting common subscription data to many clients at once. For - each call to `async_write` the completion handler is called once when - the data finishes sending, in a manner identical to `boost::asio::async_write`. - - [table - [ - [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L1048 Beast]] - [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L672 websocketpp]] - ][ - [``` - template - void - write(ConstBufferSequence const& buffers); - ```] - [``` - lib::error_code send(std::string const & payload, - frame::opcode::value op = frame::opcode::text); - ... - lib::error_code send(message_ptr msg); - ```] - ]]]] - - [[9. Streaming Messages][ - - websocketpp requires that the entire message fit into memory, and that - the size is known ahead of time. - - Beast allows callers to compose messages in individual frames. This is - useful when the size of the data is not known ahead of time or if it - is not desired to buffer the entire message in memory at once before - sending it. For example, sending periodic output of a database query - running on a coroutine. Or sending the contents of a file in pieces, - without bringing it all into memory. - - [table - [ - [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L1151 Beast]] - [websocketpp] - ][ - [``` - template - void - write_frame(bool fin, - ConstBufferSequence const& buffers); - ```] - [ - // - ] - ]]]] - - [[10. Flow Control][ - - The websocketpp read implementation continuously reads asynchronously - from the network and buffers message data. To prevent unbounded growth - and leverage TCP/IP's flow control mechanism, callers can periodically - turn this 'read pump' off and back on. - - In contrast a `beast::websocket::stream` does not independently begin - background activity, nor does it buffer messages. It receives data only - when there is a call to an asynchronous initiation function (for - example `beast::websocket::stream::async_read`) with an associated handler. - Applications do not need to implement explicit logic to regulate the - flow of data. Instead, they follow the traditional model of issuing a - read, receiving a read completion, processing the message, then - issuing a new read and repeating the process. - - [table - [ - [Beast] - [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L728 websocketpp]] - ][ - [ - // - ] - [``` - lib::error_code pause_reading(); - lib::error_code resume_reading(); - ```] - ]]]] - - [[11. Connection Establishment][ - - websocketpp offers the `endpoint` class which can handle binding and - listening to a port, and spawning connection objects. - - Beast does not reinvent the wheel here, callers use the interfaces - already in `boost::asio` for receiving incoming connections resolving - host names, or establishing outgoing connections. After the socket (or - `boost::asio::ssl::stream`) is connected, the `beast::websocket::stream` - is constructed around it and the WebSocket handshake can be performed. - - Beast users are free to implement their own "connection manager", but - there is no requirement to do so. - - [table - [ - [[@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/async_connect.html Beast], - [@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/basic_socket_acceptor/async_accept.html also]] - [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/asio/endpoint.hpp#L52 websocketpp]] - ][ - [``` - #include - ```] - [``` - template - class endpoint : public config::socket_type; - ```] - ]]]] - - [[12. WebSocket Handshaking][ - - Callers invoke `beast::websocket::accept` to perform the WebSocket - handshake, but there is no requirement to use this function. Advanced - users can perform the WebSocket handshake themselves. Beast WebSocket - provides the tools for composing the request or response, and the - Beast HTTP interface provides the container and algorithms for sending - and receiving HTTP/1 messages including the necessary HTTP Upgrade - request for establishing the WebSocket session. - - Beast allows the caller to pass the incoming HTTP Upgrade request for - the cases where the caller has already received an HTTP message. - This flexibility permits novel and robust implementations. For example, - a listening socket that can handshake in multiple protocols on the - same port. - - Sometimes callers want to read some bytes on the socket before reading - the WebSocket HTTP Upgrade request. Beast allows these already-received - bytes to be supplied to an overload of the accepting function to permit - sophisticated features. For example, a listening socket that can - accept both regular WebSocket and Secure WebSocket (SSL) connections. - - [table - [ - [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L501 Beast], - [@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L401 also]] - [websocketpp] - ][ - [``` - template - void - accept(ConstBufferSequence const& buffers); - - template - void - accept(http::request_v1 const& request); - ```] - [ - // - ] - ]]]] - - ] -]] - -] - -[endsect] - -[endsect] diff --git a/src/beast/doc/docca/README.md b/src/beast/doc/docca/README.md new file mode 100644 index 0000000000..ab7aa9ef0a --- /dev/null +++ b/src/beast/doc/docca/README.md @@ -0,0 +1,4 @@ +# docca +Boost.Book XSLT C++ documentation system + +[Example Documentation](http://vinniefalco.github.io/docca/) diff --git a/src/beast/doc/docca/example/.gitignore b/src/beast/doc/docca/example/.gitignore new file mode 100644 index 0000000000..fc40be018b --- /dev/null +++ b/src/beast/doc/docca/example/.gitignore @@ -0,0 +1,5 @@ +bin +html +temp +reference.qbk +out.txt diff --git a/src/beast/doc/docca/example/Jamfile b/src/beast/doc/docca/example/Jamfile new file mode 100644 index 0000000000..61d564f076 --- /dev/null +++ b/src/beast/doc/docca/example/Jamfile @@ -0,0 +1,65 @@ +# +# Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +# + +import os ; + +local broot = [ os.environ BOOST_ROOT ] ; + +project docca/doc ; + +using boostbook ; +using quickbook ; +using doxygen ; + +xml docca_bb : main.qbk ; + +path-constant out : . ; + +install stylesheets + : + $(broot)/doc/src/boostbook.css + : + $(out)/html + ; + +explicit stylesheets ; + +install images + : + [ glob $(broot)/doc/src/images/*.png ] + : + $(out)/html/images + ; + +explicit images ; + +install callouts + : + [ glob $(broot)/doc/src/images/callouts/*.png ] + : + $(out)/html/images/callouts + ; + +explicit callout ; + +boostbook doc + : + docca_bb + : + chapter.autolabel=0 + boost.root=$(broot) + chapter.autolabel=0 + chunk.first.sections=1 # Chunk the first top-level section? + chunk.section.depth=8 # Depth to which sections should be chunked + generate.section.toc.level=1 # Control depth of TOC generation in sections + toc.max.depth=2 # How many levels should be created for each TOC? + toc.section.depth=2 # How deep should recursive sections appear in the TOC? + : + temp + stylesheets + images + ; diff --git a/src/beast/doc/docca/example/boostbook.dtd b/src/beast/doc/docca/example/boostbook.dtd new file mode 100644 index 0000000000..bd4c3f871e --- /dev/null +++ b/src/beast/doc/docca/example/boostbook.dtd @@ -0,0 +1,439 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +%DocBook; diff --git a/src/beast/doc/docca/example/include/docca/example.hpp b/src/beast/doc/docca/example/include/docca/example.hpp new file mode 100644 index 0000000000..daf1a47565 --- /dev/null +++ b/src/beast/doc/docca/example/include/docca/example.hpp @@ -0,0 +1,851 @@ +// +// Copyright (c) 2015-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef EXAMPLE_HPP +#define EXAMPLE_HPP + +#include +#include + +// This is a sample header file to show docca XLST results +// +// namespace, enum, type alias, global, static global, +// function, static function, struct/class + +namespace example { + +/** Enum + + Description +*/ +enum enum_t +{ + /// 0 + zero, + + /// 1 + one, + + /// 2 + two +}; + +/** Enum class + + Description +*/ +enum class enum_c +{ + /// aaa + aaa, + + /// bbb + bbb, + + /// ccc + ccc +}; + +/** Type alias + + Description +*/ +using type = std::string; + +/** Template type alias + + Description +*/ +template +using t_type = std::vector; + +/** Void or deduced + + Description +*/ +using vod = void_or_deduced; + +/** Implementation-defined + + Description +*/ +using impdef = implementation_defined; + +/** Variable + + Description +*/ +extern std::size_t var; + +/** Static variable + + Description +*/ +static std::size_t s_var = 0; + +/** Brief with @b bold text. + + Function returning @ref type. + + @return The type + + @see t_func. + + @throw std::exception on error + @throw std::domain_error on bad parameters + + @par Thread Safety + + Cannot be called concurrently. + + @note Additional notes. + + @param arg1 Function parameter 1 + @param arg2 Function parameter 2 +*/ +type +func(int arg1, std::string arg2); + +/** Brief for function starting with _ + + @return @ref type + + @see func +*/ +type +_func(float arg1, std::size arg2); + +/** Brief. + + Function description. + + See @ref func. + + @tparam T Template parameter 1 + @tparam U Template parameter 2 + @tparam V Template parameter 3 + + @param t Function parameter 1 + @param u Function parameter 2 + @param v Function parameter 3 + + @return nothing +*/ +template +void +t_func(T t, U const& u, V&& v); + +/** Overloaded function 1 + + Description + + @param arg1 Parameter 1 +*/ +void +overload(int arg1); + +/** Overloaded function 2 + + Description + + @param arg1 Parameter 1 + @param arg2 Parameter 2 +*/ +void +overload(int arg1, int arg2); + +/** Overloaded function 3 + + Description + + @param arg1 Parameter 1 + @param arg2 Parameter 2 + @param arg3 Parameter 3 +*/ +void +overload(int arg1, int arg2, int arg3); + +/** Markdown examples + + @par List + + 1. Lists with extra long lines that can *span* multiple lines + and overflow even the longest of buffers. + 2. With Numbers + + Or not + + Nesting + 1. Deeply + + And returning `here`. + + Another list I enjoy: + + -# 1 + - 1.a + -# 1.a.1 + -# 1.a.2 + - 1.b + -# 2 + - 2.a + - 2.b + -# 2.b.1 + -# 2.b.2 + - 2.b.2.a + - 2.b.2.b + + @par Table + + First Header | Second Header + ------------- | ------------- + Content Cell | Content Cell + Content Cell | Content Cell +*/ +void markdown(); + +//------------------------------------------------------------------------------ + +namespace detail { + +/** Detail class + + Description +*/ +struct detail_type +{ +}; + +/** Detail function + + Description +*/ +void +detail_function(); + +} // detail + +//------------------------------------------------------------------------------ + +/// Nested namespace +namespace nested { + +/** Enum + + Description +*/ +enum enum_t +{ + /// 0 + zero, + + /// 1 + one, + + /// 2 + two +}; + +/** Enum class + + Description +*/ +enum class enum_c +{ + /// aaa + aaa, + + /// bbb + bbb, + + /// ccc + ccc +}; + +/** Type alias + + Description +*/ +using type = std::string; + +/** Template type alias + + Description +*/ +template +using t_type = std::vector; + +/** Variable + + Description +*/ +extern std::size_t var; + +/** Static variable + + Description +*/ +static std::size_t s_var = 0; + +/** Brief with @b bold text. + + Function returning @ref type. + + @return The type + + @see t_func. + + @throw std::exception on error + @throw std::domain_error on bad parameters + + @par Thread Safety + + Cannot be called concurrently. + + @note Additional notes. + + @param arg1 Function parameter 1 + @param arg2 Function parameter 2 +*/ +type +func(int arg1, std::string arg2); + +/** Brief for function starting with _ + +@return @ref type + +@see func +*/ +type +_func(float arg1, std::size arg2); + +/** Brief. + + Function description. + + See @ref func. + + @tparam T Template parameter 1 + @tparam U Template parameter 2 + @tparam V Template parameter 3 + + @param t Function parameter 1 + @param u Function parameter 2 + @param v Function parameter 3 + + @return nothing +*/ +template +void +t_func(T t, U const& u, V&& v); + +/** Overloaded function 1 + + Description + + @param arg1 Parameter 1 +*/ +void +overload(int arg1); + +/** Overloaded function 2 + + Description + + @param arg1 Parameter 1 + @param arg2 Parameter 2 +*/ +void +overload(int arg1, int arg2); + +/** Overloaded function 3 + + Description + + @param arg1 Parameter 1 + @param arg2 Parameter 2 + @param arg3 Parameter 3 +*/ +void +overload(int arg1, int arg2, int arg3); + +} // nested + +/// Overloads operators +struct Num +{ + + /// Addition + friend + Num + operator +(Num, Num); + + /// Subtraction + friend + Num + operator -(Num, Num); + + /// Multiplication + friend + Num + operator *(Num, Num); + + /// Division + friend + Num + operator /(Num, Num); + +}; + +/// @ref Num addition +Num +operator +(Num, Num); + +/// @ref Num subtraction +Num +operator -(Num, Num); + +/// @ref Num multiplication +Num +operator *(Num, Num); + +/// @ref Num division +Num +operator /(Num, Num); + +/** Template class type. + + Description. + + @tparam T Template parameter 1 + @tparam U Template parameter 2 +*/ +template +class class_type +{ +public: + /** Enum + + Description + */ + enum enum_t + { + /// 0 + zero, + + /// 1 + one, + + /// 2 + two, + + /// _3 + _three + }; + + /** Enum class + + Description + */ + enum class enum_c + { + /// aaa + aaa, + + /// bbb + bbb, + + /// ccc + ccc, + + /// _ddd + _ddd + }; + + /** Type alias + + Description + */ + using type = std::string; + + /** Template type alias + + Description + */ + template + using t_type = std::vector; + + /** Variable + + Description + */ + extern std::size_t var; + + /** Static variable + + Description + */ + static std::size_t s_var = 0; + + /** Default Ctor + + Description + */ + class_type(); + + /** Dtor + + Description + */ + ~class_type(); + + /** Brief with @b bold text. + + Function returning @ref type. + + @return The type + + @see t_func. + + @throw std::exception on error + @throw std::domain_error on bad parameters + + @par Thread Safety + + Cannot be called concurrently. + + @note Additional notes. + + @param arg1 Function parameter 1 + @param arg2 Function parameter 2 + */ + type + func(int arg1, std::string arg2); + + /** Brief. + + Function description. + + See @ref func. + + @tparam T Template parameter 1 + @tparam U Template parameter 2 + @tparam V Template parameter 3 + + @param t Function parameter 1 + @param u Function parameter 2 + @param v Function parameter 3 + + @return nothing + */ + template + void + t_func(T t, U const& u, V&& v); + + /** Overloaded function 1 + + Description + + @param arg1 Parameter 1 + */ + void + overload(int arg1); + + /** Overloaded function 2 + + Description + + @param arg1 Parameter 1 + @param arg2 Parameter 2 + */ + void + overload(int arg1, int arg2); + + /** Overloaded function 3 + + Description + + @param arg1 Parameter 1 + @param arg2 Parameter 2 + @param arg3 Parameter 3 + */ + void + overload(int arg1, int arg2, int arg3); + + /** Less-than operator + + Description + */ + bool + operator< (class_type const& rhs) const; + + /** Greater-than operator + + Description + */ + bool + operator> (class_type const& rhs) const; + + /** Less-than-or-equal-to operator + + Description + */ + bool + operator<= (class_type const& rhs) const; + + /** Greater-than-or-equal-to operator + + Description + */ + bool + operator>= (class_type const& rhs) const; + + /** Equality operator + + Description + */ + bool + operator== (class_type const& rhs) const; + + /** Inequality operator + + Description + */ + bool + operator!= (class_type const& rhs) const; + + /** Arrow operator + + Description + */ + std::size_t operator->() const; + + /** Index operator + + Description + */ + enum_c& operator[](std::size_t); + + /** Index operator + + Description + */ + enum_c operator[](std::size_t) const; + + /// Public data + std::size_t pub_data_; + + /// Public static data + static std::size_t pub_sdata_; + +protected: + /** Protected data + + Description + */ + std::size_t prot_data_; + + /** Protected enum + + Description + */ + enum_c _prot_enum; + + /** Static protected data + + Description + */ + static std::size_t prot_sdata_; + + /** Protected type + + Description + */ + struct prot_type + { + }; + + /** Protected function + + Description + */ + void prot_memfn(); + + /** Protected function returning @ref prot_type + + Description + */ + prot_type prot_rvmemfn(); + + /** Protected static member function + + Description + */ + static void static_prot_memfn(); + +private: + /** Private data + + Description + */ + std::size_t priv_data_; + + /** Static private data + + Description + */ + static std::size_t priv_sdata_; + + /** Private type + + Description + */ + struct priv_type + { + }; + + /** Private function + + Description + */ + void priv_memfn(); + + /** Private function returning *ref priv_type + + Description + */ + priv_type priv_rvmemfn(); + + /** Static private member function + + Description + */ + static void static_priv_memfn(); + + /** Friend class + + Description + */ + friend friend_class; +}; + +/// Other base class 1 +class other_base_class1 +{ +}; + +/// Other base class 2 +class other_base_class2 +{ +}; + +/** Derived type + + Description +*/ +template +class derived_type : + public class_type, + protected other_base_class1, + private other_base_class2 +{ +}; + +/** References to all identifiers: + + Description one @ref one + + @par See Also + + @li @ref type + + @li @ref t_type + + @li @ref vod + + @li @ref impdef + + @li @ref var + + @li @ref s_var + + @li @ref func + + @li @ref t_func + + @li @ref overload + + @li @ref nested::enum_t : @ref nested::zero @ref nested::one @ref nested::two + + @li @ref nested::enum_c : nested::enum_c::aaa @ref nested::enum_c::bbb @ref nested::enum_c::ccc + + @li @ref nested::type + + @li @ref nested::t_type + + @li @ref nested::var + + @li @ref nested::s_var + + @li @ref nested::func + + @li @ref nested::t_func + + @li @ref nested::overload + + @li @ref class_type + + @li @ref class_type::enum_t : @ref class_type::zero @ref class_type::one @ref class_type::two @ref class_type::_three + + @li @ref class_type::enum_c : class_type::enum_c::aaa @ref class_type::enum_c::bbb @ref class_type::enum_c::ccc class_type::enum_c::_ddd + + @li @ref class_type::type + + @li @ref class_type::t_type + + @li @ref class_type::var + + @li @ref class_type::s_var + + @li @ref class_type::class_type + + @li @ref class_type::func + + @li @ref class_type::t_func + + @li @ref class_type::overload + + @li @ref class_type::pub_data_ + + @li @ref class_type::pub_sdata_ + + @li @ref class_type::_prot_enum + + @li @ref class_type::prot_type + + @li @ref class_type::priv_type + + @li @ref derived_type + + @li @ref Num + +*/ +void all_ref(); + +} // example + +namespace other { + +/// other function +void func(); + +/// other class +struct class_type +{ +}; + + +} // other + +#endif diff --git a/src/beast/doc/docca/example/index.xml b/src/beast/doc/docca/example/index.xml new file mode 100644 index 0000000000..c364e4ed26 --- /dev/null +++ b/src/beast/doc/docca/example/index.xml @@ -0,0 +1,14 @@ + + + + + +
+ Index + +
diff --git a/src/beast/doc/docca/example/main.qbk b/src/beast/doc/docca/example/main.qbk new file mode 100644 index 0000000000..43ddf6ae18 --- /dev/null +++ b/src/beast/doc/docca/example/main.qbk @@ -0,0 +1,28 @@ +[/ + Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[library docca + [quickbook 1.6] + [copyright 2016 Vinnie Falco] + [purpose Documentation Library] + [license + Distributed under the Boost Software License, Version 1.0. + (See accompanying file LICENSE_1_0.txt or copy at + [@http://www.boost.org/LICENSE_1_0.txt]) + ] + [category template] + [category generic] +] + +[template mdash[] '''— '''] +[template indexterm1[term1] ''''''[term1]''''''] +[template indexterm2[term1 term2] ''''''[term1]''''''[term2]''''''] + +[section:ref Reference] +[include reference.qbk] +[endsect] +[xinclude index.xml] diff --git a/src/beast/doc/docca/example/makeqbk.sh b/src/beast/doc/docca/example/makeqbk.sh new file mode 100644 index 0000000000..e6fa0c30a7 --- /dev/null +++ b/src/beast/doc/docca/example/makeqbk.sh @@ -0,0 +1,13 @@ +#!/usr/bin/bash + +# Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +mkdir -p temp +doxygen source.dox +cd temp +xsltproc combine.xslt index.xml > all.xml +xsltproc ../reference.xsl all.xml > ../reference.qbk + diff --git a/src/beast/doc/docca/example/reference.xsl b/src/beast/doc/docca/example/reference.xsl new file mode 100644 index 0000000000..de56752943 --- /dev/null +++ b/src/beast/doc/docca/example/reference.xsl @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/beast/doc/docca/example/source.dox b/src/beast/doc/docca/example/source.dox new file mode 100644 index 0000000000..c55616ee7b --- /dev/null +++ b/src/beast/doc/docca/example/source.dox @@ -0,0 +1,333 @@ +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +DOXYFILE_ENCODING = UTF-8 +PROJECT_NAME = "docca" +PROJECT_NUMBER = +PROJECT_BRIEF = Documentation Library +PROJECT_LOGO = +OUTPUT_DIRECTORY = +CREATE_SUBDIRS = NO +ALLOW_UNICODE_NAMES = NO +OUTPUT_LANGUAGE = English +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = +ALWAYS_DETAILED_SEC = YES +INLINE_INHERITED_MEMB = YES +FULL_PATH_NAMES = NO +STRIP_FROM_PATH = include/ +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = YES +QT_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = YES +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 4 +ALIASES = +TCL_SUBST = +OPTIMIZE_OUTPUT_FOR_C = NO +OPTIMIZE_OUTPUT_JAVA = NO +OPTIMIZE_FOR_FORTRAN = NO +OPTIMIZE_OUTPUT_VHDL = NO +EXTENSION_MAPPING = +MARKDOWN_SUPPORT = YES +AUTOLINK_SUPPORT = YES +BUILTIN_STL_SUPPORT = NO +CPP_CLI_SUPPORT = NO +SIP_SUPPORT = NO +IDL_PROPERTY_SUPPORT = YES +DISTRIBUTE_GROUP_DOC = NO +GROUP_NESTED_COMPOUNDS = NO +SUBGROUPING = YES +INLINE_GROUPED_CLASSES = NO +INLINE_SIMPLE_STRUCTS = NO +TYPEDEF_HIDES_STRUCT = NO +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = YES +EXTRACT_PRIVATE = YES +EXTRACT_PACKAGE = NO +EXTRACT_STATIC = YES +EXTRACT_LOCAL_CLASSES = NO +EXTRACT_LOCAL_METHODS = NO +EXTRACT_ANON_NSPACES = NO +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = YES +HIDE_SCOPE_NAMES = NO +HIDE_COMPOUND_REFERENCE= NO +SHOW_INCLUDE_FILES = NO +SHOW_GROUPED_MEMB_INC = NO +FORCE_LOCAL_INCLUDES = NO +INLINE_INFO = NO +SORT_MEMBER_DOCS = NO +SORT_BRIEF_DOCS = NO +SORT_MEMBERS_CTORS_1ST = YES +SORT_GROUP_NAMES = NO +SORT_BY_SCOPE_NAME = NO +STRICT_PROTO_MATCHING = NO +GENERATE_TODOLIST = NO +GENERATE_TESTLIST = NO +GENERATE_BUGLIST = NO +GENERATE_DEPRECATEDLIST= NO +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = NO +SHOW_FILES = NO +SHOW_NAMESPACES = NO +FILE_VERSION_FILTER = +LAYOUT_FILE = +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = NO +WARN_AS_ERROR = NO +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = include/docca/example.hpp + +INPUT_ENCODING = UTF-8 +FILE_PATTERNS = +RECURSIVE = NO +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = +EXCLUDE_SYMBOLS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = +EXAMPLE_RECURSIVE = YES +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +FILTER_SOURCE_PATTERNS = +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = NO +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = NO +REFERENCES_RELATION = NO +REFERENCES_LINK_SOURCE = YES +SOURCE_TOOLTIPS = YES +USE_HTAGS = NO +VERBATIM_HEADERS = YES +CLANG_ASSISTED_PARSING = NO +CLANG_OPTIONS = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = YES +COLS_IN_ALPHA_INDEX = 1 +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = NO +HTML_OUTPUT = dhtm +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_EXTRA_STYLESHEET = +HTML_EXTRA_FILES = +HTML_COLORSTYLE_HUE = 220 +HTML_COLORSTYLE_SAT = 100 +HTML_COLORSTYLE_GAMMA = 80 +HTML_TIMESTAMP = NO +HTML_DYNAMIC_SECTIONS = NO +HTML_INDEX_NUM_ENTRIES = 100 +GENERATE_DOCSET = NO +DOCSET_FEEDNAME = "Doxygen generated docs" +DOCSET_BUNDLE_ID = org.doxygen.Project +DOCSET_PUBLISHER_ID = org.doxygen.Publisher +DOCSET_PUBLISHER_NAME = Publisher +GENERATE_HTMLHELP = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +CHM_INDEX_ENCODING = +BINARY_TOC = NO +TOC_EXPAND = NO +GENERATE_QHP = NO +QCH_FILE = +QHP_NAMESPACE = org.doxygen.Project +QHP_VIRTUAL_FOLDER = doc +QHP_CUST_FILTER_NAME = +QHP_CUST_FILTER_ATTRS = +QHP_SECT_FILTER_ATTRS = +QHG_LOCATION = +GENERATE_ECLIPSEHELP = NO +ECLIPSE_DOC_ID = org.doxygen.Project +DISABLE_INDEX = NO +GENERATE_TREEVIEW = NO +ENUM_VALUES_PER_LINE = 4 +TREEVIEW_WIDTH = 250 +EXT_LINKS_IN_WINDOW = NO +FORMULA_FONTSIZE = 10 +FORMULA_TRANSPARENT = YES +USE_MATHJAX = NO +MATHJAX_FORMAT = HTML-CSS +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest +MATHJAX_EXTENSIONS = +MATHJAX_CODEFILE = +SEARCHENGINE = YES +SERVER_BASED_SEARCH = NO +EXTERNAL_SEARCH = NO +SEARCHENGINE_URL = +SEARCHDATA_FILE = searchdata.xml +EXTERNAL_SEARCH_ID = +EXTRA_SEARCH_MAPPINGS = + +#--------------------------------------------------------------------------- +# Configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = NO +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4 +EXTRA_PACKAGES = +LATEX_HEADER = +LATEX_FOOTER = +LATEX_EXTRA_STYLESHEET = +LATEX_EXTRA_FILES = +PDF_HYPERLINKS = YES +USE_PDFLATEX = YES +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +LATEX_SOURCE_CODE = NO +LATEX_BIB_STYLE = plain +LATEX_TIMESTAMP = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +RTF_SOURCE_CODE = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_SUBDIR = +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = YES +XML_OUTPUT = temp/ +XML_PROGRAMLISTING = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the DOCBOOK output +#--------------------------------------------------------------------------- +GENERATE_DOCBOOK = NO +DOCBOOK_OUTPUT = docbook +DOCBOOK_PROGRAMLISTING = NO + +#--------------------------------------------------------------------------- +# Configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = YES +EXPAND_ONLY_PREDEF = YES +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = DOXYGEN \ + GENERATING_DOCS \ + _MSC_VER + +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +EXTERNAL_PAGES = YES +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = NO +MSCGEN_PATH = +DIA_PATH = +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = NO +DOT_NUM_THREADS = 0 +DOT_FONTNAME = Helvetica +DOT_FONTSIZE = 10 +DOT_FONTPATH = +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +GROUP_GRAPHS = YES +UML_LOOK = NO +UML_LIMIT_NUM_FIELDS = 10 +TEMPLATE_RELATIONS = NO +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = NO +CALLER_GRAPH = NO +GRAPHICAL_HIERARCHY = YES +DIRECTORY_GRAPH = YES +DOT_IMAGE_FORMAT = png +INTERACTIVE_SVG = NO +DOT_PATH = +DOTFILE_DIRS = +MSCFILE_DIRS = +DIAFILE_DIRS = +PLANTUML_JAR_PATH = +PLANTUML_INCLUDE_PATH = +DOT_GRAPH_MAX_NODES = 50 +MAX_DOT_GRAPH_DEPTH = 0 +DOT_TRANSPARENT = NO +DOT_MULTI_TARGETS = NO +GENERATE_LEGEND = YES +DOT_CLEANUP = YES diff --git a/src/beast/doc/reference.xsl b/src/beast/doc/docca/include/docca/doxygen.xsl similarity index 74% rename from src/beast/doc/reference.xsl rename to src/beast/doc/docca/include/docca/doxygen.xsl index 7a4420983e..aca6707c57 100644 --- a/src/beast/doc/reference.xsl +++ b/src/beast/doc/docca/include/docca/doxygen.xsl @@ -2,6 +2,8 @@ -[/ - Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + [/ + Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -39,19 +41,12 @@ - + - + @@ -61,17 +56,28 @@ - + + + + + + + + + + + + + + + + + + - - - - - - - - + + @@ -79,12 +85,23 @@ + + + + + + + + + + + + + + - - - @@ -108,6 +125,7 @@ + @@ -162,7 +180,7 @@ ``['implementation-defined]`` - __void_or_deduced__ + ``[@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/asynchronous_operations.html#boost_asio.reference.asynchronous_operations.return_type_of_an_initiating_function ['void-or-deduced]]`` @@ -195,18 +213,6 @@ select="concat(substring-before($name, '::'), '__', substring-after($name, '::'))"/> - - - - - - - - - - + + + + + - + + select="concat(substring-before($name, '/'), '_slash_', substring-after($name, '/'))"/> + + + + + @@ -319,7 +337,7 @@ - + [heading ] @@ -394,41 +412,91 @@ ` + + + + + + + + + - * - - - - + + + + + + + + + # + + + * + + + [*] ['] + - - [heading Parameters] - [heading Exceptions] + [table [[Type][Thrown On]] + + [heading Parameters] + [table [[Name][Description]] + + + [heading Template Parameters] + [table [[Type][Description]] + + + [table [[Name][Description]] + - [variablelist ] + - [[ + [[` - ][ + `][ ]] + + + [table + + ] + + + + [ + + ] + + + + [ + + ] + @@ -586,120 +654,167 @@ - - - - - - - - - - - - - - - - - - - - [link beast.ref. - - ` - - `] - - - [role red |1| - - ] - - - - - - - + + + + - - - - - - + + + + + + + + + + + + + :: + + + + + + + |1a| + [link + + ` + + `] + + [heading Debug] [table [[name][value]] + + + + + + + + + + + + + + - - + + + + + :: + + + + + + - [link beast.ref. - - - - - - - - + |1b| + [link + ` - + `] - [link beast.ref. - + |1c| + [link + ` - + `] [role red - + |1| + + ] - - - [role red - - ] + + [heading Debug] [table [[name][value]] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + |2| + [link + + ` + + `] + + + [role red + |2| + + ] + + + + [heading Debug] [table [[name][value]] + + + + + + + + + + - [role red |3| + [role red + |6| ] @@ -717,28 +832,22 @@ - + + - - - [link beast.ref. + [link @@ -752,7 +861,7 @@ `] - [link beast.ref. + [link ` @@ -760,6 +869,7 @@ [role red + |8| ] @@ -767,6 +877,7 @@ [role red + |9| ] @@ -775,38 +886,29 @@ - + - - [heading Requirements] - ['Header: ][^ - - ] - - - ['Convenience header: ][^beast/core.hpp] - - - ['Convenience header: ][^beast/http.hpp] - - - ['Convenience header: ][^beast/websocket.hpp] - - - - - - - - + + + + + + + + + + + + + - + @@ -817,7 +919,7 @@ - + @@ -827,21 +929,23 @@ ] + [heading Synopsis] + + + ``` - + : - - public - - + + @@ -854,15 +958,16 @@ + [heading Description] - - - + + + [endsect] @@ -874,7 +979,7 @@ sectiondef[@kind='public-type'] | innerclass[@prot='public' and not(contains(., '_handler'))]) > 0"> [heading Types] - [table [[Name][Description]] + [table [[Name][Description]] @@ -882,7 +987,8 @@ [ - [[link beast.ref. + [[link + . @@ -896,25 +1002,20 @@ - - - - - - + - [[link beast.ref. - + [[link + [* - + ]]] [ ] @@ -924,9 +1025,9 @@ ] - + [heading Member Functions] - [table [[Name][Description]] + [table [[Name][Description]] @@ -957,7 +1058,7 @@ [ - [[link beast.ref. + [[link . [* @@ -974,9 +1075,9 @@ ] - + [heading Protected Member Functions] - [table [[Name][Description]] + [table [[Name][Description]] @@ -1002,7 +1103,7 @@ [ - [[link beast.ref. + [[link . [* @@ -1019,9 +1120,9 @@ ] - + [heading Private Member Functions] - [table [[Name][Description]] + [table [[Name][Description]] @@ -1047,7 +1148,7 @@ [ - [[link beast.ref. + [[link . [* @@ -1064,29 +1165,13 @@ ] - - [heading Static Data Members] - [table [[Name][Description]] - - - [ - [[link beast.ref. - . - [* - - ]]] [ - - ] ] - - ] - - + [heading Data Members] - [table [[Name][Description]] - + [table [[Name][Description]] + [ - [[link beast.ref. + [[link . [* @@ -1096,13 +1181,13 @@ ] - + [heading Protected Data Members] - [table [[Name][Description]] + [table [[Name][Description]] [ - [[link beast.ref. + [[link . [* @@ -1112,13 +1197,14 @@ ] - + [heading Private Data Members] - [table [[Name][Description]] + [table [[Name][Description]] [ - [[link beast.ref. + [[link . [* @@ -1130,7 +1216,7 @@ [heading Friends] - [table [[Name][Description]] + [table [[Name][Description]] @@ -1156,7 +1242,7 @@ [ - [[link beast.ref. + [[link . [* @@ -1175,7 +1261,7 @@ [heading Related Functions] - [table [[Name][Description]] + [table [[Name][Description]] @@ -1201,7 +1287,7 @@ [ - [[link beast.ref.. + [[link . [*]]] [ @@ -1227,12 +1313,53 @@ - - - - - - + + + + + + + + + + + + + + + + + + @@ -1315,7 +1442,7 @@ - ``[link beast.ref. + ``[link . @@ -1330,12 +1457,22 @@ const ; + + ``[''''&raquo;''' + [link + + . + + .overload + + more...]]`` + + ``` - [section: @@ -1359,11 +1496,12 @@ - ['Inherited from - + (Inherited from ` + + - .] + `) @@ -1374,6 +1512,12 @@ ] + [heading Synopsis] + + + + + @@ -1399,15 +1543,16 @@ ``` + [heading Description] - - + + - [endsect] [endsect] + [endsect] [endsect] [endsect] @@ -1439,17 +1584,23 @@ + - - - - - + @@ -1459,7 +1610,7 @@ - + ``` @@ -1494,12 +1645,14 @@ [heading Values] - [variablelist + [table [[Name][Description]] - [[ + [[[^ - ] [ + ]][ + + ]] ] @@ -1552,56 +1705,7 @@ - - class ``[link beast.ref.streams.AsyncStream [*AsyncStream]]`` - - - class __AsyncReadStream__ - - - class __AsyncWriteStream__ - - - class ``[link beast.ref.Body [*Body]]`` - - - class ``[link beast.ref.BufferSequence [*BufferSequence]]`` - - - - ``[link beast.ref.BufferSequence [*BufferSequence]]`` - - - class __CompletionHandler__ - - - class __ConstBufferSequence__ - - - class ``[link beast.ref.DynamicBuffer [*DynamicBuffer]]`` - - - class __Handler__ - - - class __MutableBufferSequence__ - - - class ``[link beast.ref.Parser [*Parser]]`` - - - class ``[link beast.ref.streams.Stream [*Stream]]`` - - - class ``[link beast.ref.streams.SyncStream [*SyncStream]]`` - - - class __SyncReadStream__ - - - class __SyncWriteStream__ - - + @@ -1611,7 +1715,7 @@ - + @@ -1621,7 +1725,7 @@ - + = @@ -1644,13 +1748,13 @@ - + - + = @@ -1665,7 +1769,7 @@ - + @@ -1676,7 +1780,7 @@ - + @@ -1692,15 +1796,25 @@ + + + + + [section: - + ] [indexterm1 - - ] + + ] + @@ -1731,7 +1845,7 @@ - ``[link beast.ref. + ``[link .overload @@ -1739,15 +1853,21 @@ ]``( - ); + ); + + ``[''''&raquo;''' + [link + + .overload + + more...]]`` + + ``` - - - [section: @@ -1774,29 +1894,50 @@ ] - - - - - - - - - - - - - - - - ``` - - ``` - - - - - + [heading Synopsis] + + + + + + + + + + + + + + + + + + + + + + + ``` + + ``` + + + [heading Description] + + + [heading Debug] [table [[name][value]] + + + + + + + + + + + + diff --git a/src/beast/doc/examples.qbk b/src/beast/doc/examples.qbk deleted file mode 100644 index 224f4d9fdc..0000000000 --- a/src/beast/doc/examples.qbk +++ /dev/null @@ -1,136 +0,0 @@ -[/ - Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) - - Distributed under the Boost Software License, Version 1.0. (See accompanying - file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -] - -[section:example Examples] - -These usage examples are intended to quickly impress upon readers the -flavor of the library. They are complete programs which may be built -and run. Source code and build scripts for these programs may be found -in the examples directory. - -[heading HTTP GET] - -Use HTTP to request the root page from a website and print the response: - -``` -#include -#include -#include -#include -#include - -int main() -{ - // Normal boost::asio setup - std::string const host = "boost.org"; - boost::asio::io_service ios; - boost::asio::ip::tcp::resolver r{ios}; - boost::asio::ip::tcp::socket sock{ios}; - boost::asio::connect(sock, - r.resolve(boost::asio::ip::tcp::resolver::query{host, "http"})); - - // Send HTTP request using beast - beast::http::request req; - req.method = "GET"; - req.url = "/"; - req.version = 11; - req.fields.replace("Host", host + ":" + - boost::lexical_cast(sock.remote_endpoint().port())); - req.fields.replace("User-Agent", "Beast"); - beast::http::prepare(req); - beast::http::write(sock, req); - - // Receive and print HTTP response using beast - beast::streambuf sb; - beast::http::response resp; - beast::http::read(sock, sb, resp); - std::cout << resp; -} -``` -[heading WebSocket] - -Establish a WebSocket connection, send a message and receive the reply: -``` -#include -#include -#include -#include -#include - -int main() -{ - // Normal boost::asio setup - std::string const host = "echo.websocket.org"; - boost::asio::io_service ios; - boost::asio::ip::tcp::resolver r{ios}; - boost::asio::ip::tcp::socket sock{ios}; - boost::asio::connect(sock, - r.resolve(boost::asio::ip::tcp::resolver::query{host, "80"})); - - // WebSocket connect and send message using beast - beast::websocket::stream ws{sock}; - ws.handshake(host, "/"); - ws.write(boost::asio::buffer(std::string("Hello, world!"))); - - // Receive WebSocket message, print and close using beast - beast::streambuf sb; - beast::websocket::opcode op; - ws.read(op, sb); - ws.close(beast::websocket::close_code::normal); - std::cout << beast::to_string(sb.data()) << "\n"; -} -``` - -[heading WebSocket Echo Server] - -This example demonstrates both synchronous and asynchronous -WebSocket server implementations. - -* [@examples/websocket_async_echo_server.hpp] -* [@examples/websocket_sync_echo_server.hpp] -* [@examples/websocket_echo.cpp] - -[heading Secure WebSocket] - -Establish a WebSocket connection over an encrypted TLS connection, -send a message and receive the reply. Requires OpenSSL to build. - -* [@examples/websocket_ssl_example.cpp] - -[heading HTTPS GET] - -This example demonstrates sending and receiving HTTP messages -over a TLS connection. Requires OpenSSL to build. - -* [@examples/http_ssl_example.cpp] - -[heading HTTP Crawl] - -This example retrieves the page at each of the most popular domains -as measured by Alexa. - -* [@examples/http_crawl.cpp] - -[heading HTTP Server] - -This example demonstrates both synchronous and asynchronous server -implementations. It also provides an example of implementing a [*Body] -type, in `file_body`. - -* [@examples/file_body.hpp] -* [@examples/http_async_server.hpp] -* [@examples/http_sync_server.hpp] -* [@examples/http_server.cpp] - -[heading Listings] - -These are stand-alone listings of the HTTP and WebSocket examples. - -* [@examples/http_example.cpp] -* [@examples/websocket_example.cpp] - -[endsect] diff --git a/src/beast/doc/http.qbk b/src/beast/doc/http.qbk deleted file mode 100644 index 9249fd64fb..0000000000 --- a/src/beast/doc/http.qbk +++ /dev/null @@ -1,381 +0,0 @@ -[/ - Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) - - Distributed under the Boost Software License, Version 1.0. (See accompanying - file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -] - -[/ -ideas: - - complete send request walkthrough (client) - - complete receive response walkthrough (client) - - complete receive request walkthrough (server) - - complete send response walkthrough (server) - - - Introduce concepts from simple to complex - - Smooth progression of new ideas building on the previous ideas - - - do we show a simplified message with collapsed fields? - - do we introduce `header` or `message` first? - - -contents: - Message (and header, fields) - Create request - Create response - Algorithms - Write - Read - Parse - Examples - Send Request - Receive Response - Receive Request - Send Response - Advanced - Responding to HEAD - Expect: 100-continue - Body (user defined) - - -section beast.http.examples Examples - -note - In the example code which follows, `socket` refers to an object of type - `boost::asio::ip::tcp::socket` which is currently connected to a remote peer. -] - - - -[section:http Using HTTP] - -[block ''' - - Message - Fields - Body - Algorithms - -'''] - -Beast offers programmers simple and performant models of HTTP messages and -their associated operations including synchronous and asynchronous reading and -writing of messages and headers in the HTTP/1 wire format using Boost.Asio. - -[note - The following documentation assumes familiarity with both Boost.Asio - and the HTTP protocol specification described in __rfc7230__. Sample code - and identifiers mentioned in this section are written as if the following - declarations are in effect: - ``` - #include - #include - using namespace beast; - using namespace beast::http; - ``` -] - - - - - -[section:message Message] - -The HTTP protocol defines the client and server roles: clients send messages -called requests and servers send back messages called responses. A HTTP message -(referred to hereafter as "message") contains request or response specific -attributes (contained in the "Start Line"), a series of zero or more name/value -pairs (collectively termed "Fields"), and an optional series of octets called -the message body which may be zero in length. The start line for a HTTP request -includes a string called the method, a string called the URL, and a version -number indicating HTTP/1.0 or HTTP/1.1. For a response, the start line contains -an integer status code and a string called the reason phrase. Alternatively, a -HTTP message can be viewed as two parts: a header, followed by a body. - -[note - The Reason-Phrase is obsolete as of rfc7230. -] - -The __header__ class template models the header for HTTP/1 and HTTP/2 messages. -This class template is a family of specializations, one for requests and one -for responses, depending on the [*`isRequest`] template value. -The [*`Fields`] template type determines the type of associative container -used to store the field values. The provided __basic_fields__ class template -and __fields__ type alias are typical choices for the [*`Fields`] type, but -advanced applications may supply user defined types which meet the requirements. -The __message__ class template models the header and optional body for HTTP/1 -and HTTP/2 requests and responses. It is derived from the __header__ class -template with the same shared template parameters, and adds the `body` data -member. The message class template requires an additional template argument -type [*`Body`]. This type controls the container used to represent the body, -if any, as well as the algorithms needed to serialize and parse bodies of -that type. - -This illustration shows the declarations and members of the __header__ and -__message__ class templates, as well as the inheritance relationship: - -[$images/message.png [width 650px] [height 390px]] - -For notational convenience, these template type aliases are provided which -supply typical choices for the [*`Fields`] type: -``` -using request_header = header; -using response_header = header; - -template -using request = message; - -template -using response = message; -``` - -The code examples below show how to create and fill in a request and response -object: - -[table Create Message -[[HTTP Request] [HTTP Response]] -[[ - ``` - request req; - req.version = 11; // HTTP/1.1 - req.method = "GET"; - req.url = "/index.htm" - req.fields.insert("Accept", "text/html"); - req.fields.insert("Connection", "keep-alive"); - req.fields.insert("User-Agent", "Beast"); - ``` -][ - ``` - response res; - res.version = 11; // HTTP/1.1 - res.status = 200; - res.reason = "OK"; - res.fields.insert("Sever", "Beast"); - res.fields.insert("Content-Length", 4); - res.body = "****"; - ``` -]]] - -In the serialized format of a HTTP message, the header is represented as a -series of text lines ending in CRLF (`"\r\n"`). The end of the header is -indicated by a line containing only CRLF. Here are examples of serialized HTTP -request and response objects. The objects created above will produce these -results when serialized. Note that only the response has a body: - -[table Serialized HTTP Request and Response -[[HTTP Request] [HTTP Response]] -[[ - ``` - GET /index.htm HTTP/1.1\r\n - Accept: text/html\r\n - Connection: keep-alive\r\n - User-Agent: Beast\r\n - \r\n - ``` -][ - ``` - 200 OK HTTP/1.1\r\n - Server: Beast\r\n - Content-Length: 4\r\n - \r\n - **** - ``` -]]] - - - - -[endsect] - - - - -[section:fields Fields] - -The [*`Fields`] type represents a container that can set or retrieve the -fields in a message. Beast provides the -[link beast.ref.http__basic_fields `basic_fields`] class which serves -the needs for most users. It supports modification and inspection of values. -The field names are not case-sensitive. - -These statements change the values of the headers in the message passed: -``` - template - void set_fields(request& req) - { - if(! req.exists("User-Agent")) - req.insert("User-Agent", "myWebClient"); - - if(req.exists("Accept-Charset")) - req.erase("Accept-Charset"); - - req.replace("Accept", "text/plain"); - } -``` - -User defined [*`Fields`] types are possible. To support serialization, the -type must meet the requirements of __FieldSequence__. To support parsing using -the provided parser, the type must provide the `insert` member function. - -[endsect] - - - -[section:body Body] - -The message [*`Body`] template parameter controls both the type of the data -member of the resulting message object, and the algorithms used during parsing -and serialization. Beast provides three very common [*`Body`] types: - -* [link beast.ref.http__empty_body [*`empty_body`:]] An empty message body. -Used in GET requests where there is no message body. Example: -``` - request req; - req.version = 11; - req.method = "GET"; - req.url = "/index.html"; -``` - -* [link beast.ref.http__string_body [*`string_body`:]] A body with a -`value_type` as `std::string`. Useful for quickly putting together a request -or response with simple text in the message body (such as an error message). -Has the same insertion complexity of `std::string`. This is the type of body -used in the examples: -``` - response res; - static_assert(std::is_same::value); - res.body = "Here is the data you requested"; -``` - -* [link beast.ref.http__streambuf_body [*`streambuf_body`:]] A body with a -`value_type` of [link beast.ref.streambuf `streambuf`]: an efficient storage -object which uses multiple octet arrays of varying lengths to represent data. - -[heading Advanced] - -User-defined types are possible for the message body, where the type meets the -[link beast.ref.Body [*`Body`]] requirements. This simplified class declaration -shows the customization points available to user-defined body types: - -[$images/body.png [width 510px] [height 210px]] - -* [*`value_type`]: Determines the type of the - [link beast.ref.http__message.body `message::body`] member. If this - type defines default construction, move, copy, or swap, then message objects - declared with this [*`Body`] will have those operations defined. - -* [*`reader`]: An optional nested type meeting the requirements of - [link beast.ref.Reader [*`Reader`]]. If present, this defines the algorithm - used for parsing bodies of this type. - -* [*`writer`]: An optional nested type meeting the requirements of - [link beast.ref.Writer [*`Writer`]]. If present, this defines the algorithm - used for serializing bodies of this type. - -The examples included with this library provide a Body implementation that -serializing message bodies that come from a file. - -[endsect] - - - -[section:algorithms Algorithms] - -Algorithms are provided to serialize and deserialize HTTP/1 messages on -streams. - -* [link beast.ref.http__read [*read]]: Deserialize a HTTP/1 __header__ or __message__ from a stream. -* [link beast.ref.http__write [*write]]: Serialize a HTTP/1 __header__ or __message__ to a stream. - -Asynchronous versions of these algorithms are also available: - -* [link beast.ref.http__async_read [*async_read]]: Deserialize a HTTP/1 __header__ or __message__ asynchronously from a stream. -* [link beast.ref.http__async_write [*async_write]]: Serialize a HTTP/1 __header__ or __message__ asynchronously to a stream. - -[heading Using Sockets] - -The free function algorithms are modeled after Boost.Asio to send and receive -messages on TCP/IP sockets, SSL streams, or any object which meets the -Boost.Asio type requirements (__SyncReadStream__, __SyncWriteStream__, -__AsyncReadStream__, and __AsyncWriteStream__ depending on the types of -operations performed). To send messages synchronously, use one of the -[link beast.ref.http__write `write`] functions: -``` - void send_request(boost::asio::ip::tcp::socket& sock) - { - request req; - req.version = 11; - req.method = "GET"; - req.url = "/index.html"; - ... - write(sock, req); // Throws exception on error - ... - // Alternatively - boost::system::error:code ec; - write(sock, req, ec); - if(ec) - std::cerr << "error writing http message: " << ec.message(); - } -``` - -An asynchronous interface is available: -``` - void handle_write(boost::system::error_code); - ... - request req; - ... - async_write(sock, req, std::bind(&handle_write, std::placeholders::_1)); -``` - -When the implementation reads messages from a socket, it can read bytes lying -after the end of the message if they are present (the alternative is to read -a single byte at a time which is unsuitable for performance reasons). To -store and re-use these extra bytes on subsequent messages, the read interface -requires an additional parameter: a [link beast.ref.DynamicBuffer [*`DynamicBuffer`]] -object. This example reads a message from the socket, with the extra bytes -stored in the streambuf parameter for use in a subsequent call to read: -``` - boost::asio::streambuf sb; - ... - response res; - read(sock, sb, res); // Throws exception on error - ... - // Alternatively - boost::system::error:code ec; - read(sock, sb, res, ec); - if(ec) - std::cerr << "error reading http message: " << ec.message(); -``` - -As with the write function, an asynchronous interface is available. The -stream buffer parameter must remain valid until the completion handler is -called: -``` - void handle_read(boost::system::error_code); - ... - boost::asio::streambuf sb; - response res; - ... - async_read(sock, res, std::bind(&handle_read, std::placeholders::_1)); -``` - -An alternative to using a `boost::asio::streambuf` is to use a -__streambuf__, which meets the requirements of __DynamicBuffer__ and -is optimized for performance: -``` - void handle_read(boost::system::error_code); - ... - beast::streambuf sb; - response res; - read(sock, sb, res); -``` - -The `read` implementation can use any object meeting the requirements of -__DynamicBuffer__, allowing callers to define custom -memory management strategies used by the implementation. - -[endsect] - - - -[endsect] diff --git a/src/beast/doc/images/AlfaSlabOne-Regular.ttf b/src/beast/doc/images/AlfaSlabOne-Regular.ttf new file mode 100644 index 0000000000..ad70ebc75d Binary files /dev/null and b/src/beast/doc/images/AlfaSlabOne-Regular.ttf differ diff --git a/src/beast/doc/images/SIL Open Font License.txt b/src/beast/doc/images/SIL Open Font License.txt new file mode 100644 index 0000000000..1cde0c6a7d --- /dev/null +++ b/src/beast/doc/images/SIL Open Font License.txt @@ -0,0 +1,43 @@ +Copyright (c) 2011 JM Sole (info@jmsole.cl), with Reserved Font Name "Alfa Slab One" + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the copyright statement(s). + +"Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. + +5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. \ No newline at end of file diff --git a/src/beast/doc/images/body.png b/src/beast/doc/images/body.png deleted file mode 100644 index 54d05550c3..0000000000 Binary files a/src/beast/doc/images/body.png and /dev/null differ diff --git a/src/beast/doc/images/body.psd b/src/beast/doc/images/body.psd deleted file mode 100644 index 3d8dca09a5..0000000000 Binary files a/src/beast/doc/images/body.psd and /dev/null differ diff --git a/src/beast/doc/images/btc_qr2.png b/src/beast/doc/images/btc_qr2.png new file mode 100644 index 0000000000..06380d6e99 Binary files /dev/null and b/src/beast/doc/images/btc_qr2.png differ diff --git a/src/beast/doc/images/message.png b/src/beast/doc/images/message.png index 9b88298a16..b1675df8ca 100644 Binary files a/src/beast/doc/images/message.png and b/src/beast/doc/images/message.png differ diff --git a/src/beast/doc/images/message.psd b/src/beast/doc/images/message.psd index 3411c20c82..1f6ceaac53 100644 Binary files a/src/beast/doc/images/message.psd and b/src/beast/doc/images/message.psd differ diff --git a/src/beast/doc/images/server.png b/src/beast/doc/images/server.png new file mode 100644 index 0000000000..fc60980056 Binary files /dev/null and b/src/beast/doc/images/server.png differ diff --git a/src/beast/doc/images/server.psd b/src/beast/doc/images/server.psd new file mode 100644 index 0000000000..cf9d445257 Binary files /dev/null and b/src/beast/doc/images/server.psd differ diff --git a/src/beast/doc/makeqbk.sh b/src/beast/doc/makeqbk.sh index 07b2b69441..b742a28bf3 100755 --- a/src/beast/doc/makeqbk.sh +++ b/src/beast/doc/makeqbk.sh @@ -5,9 +5,12 @@ # Distributed under the Boost Software License, Version 1.0. (See accompanying # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -mkdir -p ../bin/doc/xml +mkdir -p temp doxygen source.dox -cd ../bin/doc/xml +cd temp xsltproc combine.xslt index.xml > all.xml -cd ../../../doc -xsltproc reference.xsl ../bin/doc/xml/all.xml > reference.qbk +cp ../docca/include/docca/doxygen.xsl doxygen.xsl +sed -i -e '//{r ../xsl/class_detail.xsl' -e 'd}' doxygen.xsl +sed -i -e '//{r ../xsl/includes.xsl' -e 'd}' doxygen.xsl +sed -i -e '//{r ../xsl/includes_foot.xsl' -e 'd}' doxygen.xsl +xsltproc ../xsl/reference.xsl all.xml > ../reference.qbk diff --git a/src/beast/doc/master.qbk b/src/beast/doc/master.qbk deleted file mode 100644 index 24494c3886..0000000000 --- a/src/beast/doc/master.qbk +++ /dev/null @@ -1,117 +0,0 @@ -[/ - Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) - - Distributed under the Boost Software License, Version 1.0. (See accompanying - file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -] - -[library Beast - [quickbook 1.6] - [copyright 2013 - 2017 Vinnie Falco] - [purpose Networking Protocol Library] - [license - Distributed under the Boost Software License, Version 1.0. - (See accompanying file LICENSE_1_0.txt or copy at - [@http://www.boost.org/LICENSE_1_0.txt]) - ] - [authors [Falco, Vinnie]] - [category template] - [category generic] -] - -[template mdash[] '''— '''] -[template indexterm1[term1] ''''''[term1]''''''] -[template indexterm2[term1 term2] ''''''[term1]''''''[term2]''''''] - -[def __N4588__ [@http://cplusplus.github.io/networking-ts/draft.pdf [*N4588]]] -[def __rfc6455__ [@https://tools.ietf.org/html/rfc6455 rfc6455]] -[def __rfc7230__ [@https://tools.ietf.org/html/rfc7230 rfc7230]] - -[def __asio_handler_invoke__ [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/asio_handler_invoke.html `asio_handler_invoke`]] -[def __asio_handler_allocate__ [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/asio_handler_allocate.html `asio_handler_allocate`]] -[def __void_or_deduced__ [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/asynchronous_operations.html#boost_asio.reference.asynchronous_operations.return_type_of_an_initiating_function ['void-or-deduced]]] - -[def __AsyncReadStream__ [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/AsyncReadStream.html [*AsyncReadStream]]] -[def __AsyncWriteStream__ [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/AsyncWriteStream.html [*AsyncWriteStream]]] -[def __CompletionHandler__ [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/CompletionHandler.html [*CompletionHandler]]] -[def __ConstBufferSequence__ [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/ConstBufferSequence.html [*ConstBufferSequence]]] -[def __Handler__ [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/Handler.html [*Handler]]] -[def __MutableBufferSequence__ [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/MutableBufferSequence.html [*MutableBufferSequence]]] -[def __SyncReadStream__ [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/SyncReadStream.html [*SyncReadStream]]] -[def __SyncWriteStream__ [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/SyncWriteStream.html [*SyncWriteStream]]] - -[def __Body__ [link beast.ref.Body [*`Body`]]] -[def __DynamicBuffer__ [link beast.ref.DynamicBuffer [*DynamicBuffer]]] -[def __FieldSequence__ [link beast.ref.FieldSequence [*FieldSequence]]] -[def __Parser__ [link beast.ref.Parser [*`Parser`]]] - -[def __basic_fields__ [link beast.ref.http__basic_fields `basic_fields`]] -[def __fields__ [link beast.ref.http__fields `fields`]] -[def __header__ [link beast.ref.http__header `header`]] -[def __message__ [link beast.ref.http__message `message`]] -[def __streambuf__ [link beast.ref.streambuf `streambuf`]] -[def __basic_streambuf__ [link beast.ref.basic_streambuf `basic_streambuf`]] - -Beast is a cross-platform, header-only C++ library built on Boost.Asio that -provides implementations of the HTTP and WebSocket protocols. - -[variablelist - [[ - [link beast.overview Overview] - ][ - An introduction with features, requirements, and credits. - ]] - [[ - [link beast.http Using HTTP] - ][ - How to use Beast's HTTP interfaces in your applications. - ]] - [[ - [link beast.websocket Using WebSocket] - ][ - How to use Beast's WebSocket interfaces in your applications. - ]] - [[ - [link beast.example Examples] - ][ - Examples that illustrate the use of Beast in more complex applications. - ]] - [[ - [link beast.design Design] - ][ - Design rationale, answers to review questions, and - other library comparisons. - ]] - [[ - [link beast.ref Reference] - ][ - Detailed class and function reference. - ]] - [[ - [link beast.index Index] - ][ - Book-style text index of Beast documentation. - ]] -] - -[include overview.qbk] -[include http.qbk] -[include websocket.qbk] -[include examples.qbk] -[include design.qbk] - -[section:ref Reference] -[xinclude quickref.xml] -[include types/Body.qbk] -[include types/BufferSequence.qbk] -[include types/DynamicBuffer.qbk] -[include types/Field.qbk] -[include types/FieldSequence.qbk] -[include types/Parser.qbk] -[include types/Reader.qbk] -[include types/Streams.qbk] -[include types/Writer.qbk] -[include reference.qbk] -[endsect] - -[xinclude index.xml] diff --git a/src/beast/doc/overview.qbk b/src/beast/doc/overview.qbk deleted file mode 100644 index 9f33aa73aa..0000000000 --- a/src/beast/doc/overview.qbk +++ /dev/null @@ -1,114 +0,0 @@ -[/ - Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) - - Distributed under the Boost Software License, Version 1.0. (See accompanying - file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -] - -[section:overview Introduction] - -Beast is a header-only, cross-platform C++ library built on Boost.Asio and -parts of Boost, containing two modules implementing widely used network -protocols. Beast offers a universal HTTP message model, plus algorithms for -parsing and serializing HTTP/1 messages. Beast.WebSocket provides a complete -implementation of the WebSocket protocol. Their design achieves these goals: - -* [*Symmetry.] Interfaces are role-agnostic; the same interfaces can be -used to build clients, servers, or both. - -* [*Ease of Use.] HTTP messages are modeled using simple, readily -accessible objects. Functions and classes used to send and receive HTTP -or WebSocket messages are designed to resemble Boost.Asio as closely as -possible. Users familiar with Boost.Asio will be immediately comfortable -using this library. - -* [*Flexibility.] Interfaces do not mandate specific implementation -strategies; important decisions such as buffer or thread management are -left to users of the library. - -* [*Performance.] The implementation performs competitively, making it a -realistic choice for building high performance network servers. - -* [*Scalability.] Development of network applications that scale to thousands -of concurrent connections is possible with the implementation. - -* [*Basis for further abstraction.] The interfaces facilitate the -development of other libraries that provide higher levels of abstraction. - -The HTTP portion of Beast is designed to be a low-level building block for -creating higher level libraries. It implements only the HTTP protocol, and -does not handle domain specific features (for example: cookies, redirects, or -deflate content encodings). - -[heading Requirements] - -Beast requires: - -* [*C++11.] A minimum of C++11 is needed. -* [*Boost.] Beast is built on Boost, especially Boost.Asio. -* [*OpenSSL.] If using TLS/Secure sockets (optional). - -[note Tested compilers: msvc-14+, gcc 5+, clang 3.6+] - -The library is [*header-only]. It is not necessary to add any .cpp files, -or to add commands to your build script for building Beast. To link your -program successfully, you'll need to add the Boost.System library to link -with. If you use coroutines you'll also need the Boost.Coroutine library. -Please visit the Boost documentation for instructions on how to do this for -your particular build system. - -[heading Motivation] - -Beast is built on Boost.Asio. A proposal to add networking functionality to the -C++ standard library, based on Boost.Asio, is under consideration by the -committee and on track for standardization. Since the final approved networking -interface for the C++ standard library will likely closely resemble the current -interface of Boost.Asio, the choice of Boost.Asio as the network transport -layer is prudent. - -The HTTP protocol is pervasive in network applications. As C++ is a logical -choice for high performance network servers, there is great utility in solid -building blocks for manipulating, sending, and receiving HTTP messages -compliant with the Hypertext Transfer Protocol and the supplements that -follow. Unfortunately reliable implementations or industry standards do not -exist in C++. The development of higher level libraries is stymied by the -lack of a common set of low-level algorithms and types for interacting with -the HTTP protocol. - -Today's web applications increasingly rely on alternatives to standard HTTP -to achieve performance and/or responsiveness. While WebSocket implementations -are widely available in common web development languages such as Javascript, -good implementations in C++ are scarce. A survey of existing C++ WebSocket -solutions reveals interfaces which lack symmetry, impose performance penalties, -and needlessly restrict implementation strategies. - -Beast.WebSocket takes advantage of Boost.Asio's extensible asynchronous -model, handler allocation, and handler invocation hooks. Calls to -Beast.WebSocket asynchronous initiation functions allow callers the choice -of using a completion handler, stackful or stackless coroutines, futures, -or user defined customizations (for example, Boost.Fiber). The -implementation uses handler invocation hooks (__asio_handler_invoke__), -providing execution guarantees on composed operations in a manner identical -to Boost.Asio. The implementation also uses handler allocation hooks -(__asio_handler_allocate__) when allocating memory internally for composed -operations. - -There is no need for inheritance or virtual members in a -[link beast.ref.websocket__stream `websocket::stream`]. -All operations are templated and transparent to the compiler, allowing for -maximum inlining and optimization. - -[heading Credits] - -Boost.Asio is the inspiration behind which all of the interfaces and -implementation strategies are built. Some parts of the documentation are -written to closely resemble the wording and presentation of Boost.Asio -documentation. Credit goes to Christopher Kohlhoff for the wonderful -Asio library and the ideas upon which Beast is built. - -Beast would not be possible without the considerable time and patience -contributed by David Schwartz, Edward Hennis, Howard Hinnant, Miguel Portilla, -Nikolaos Bougalis, Scott Determan, Scott Schurr, and Ripple Labs for -supporting its development. - -[endsect] diff --git a/src/beast/doc/quickref.xml b/src/beast/doc/quickref.xml index 874c5a6936..3a0f5961a8 100644 --- a/src/beast/doc/quickref.xml +++ b/src/beast/doc/quickref.xml @@ -29,115 +29,113 @@ Classes - basic_dynabuf_body - basic_fields - basic_parser_v1 - empty_body - fields - header - header_parser_v1 - message - parser_v1 - request - request_header - response - response_header - streambuf_body - string_body - - rfc7230 - - - ext_list - param_list - token_list + basic_dynamic_body + basic_file_body + basic_fields + basic_parser + basic_string_body + buffer_body + dynamic_body + empty_body + fields + header + message + parser + no_chunk_decorator + request + request_header + request_parser + request_serializer + response + response_header + response_parser + response_serializer + serializer + span_body + string_body + vector_body Functions - async_read - async_parse - async_write - chunk_encode - chunk_encode_final - swap - is_keep_alive - is_upgrade - operator<< - parse - prepare - read - reason_string - with_body - write + async_read + async_read_header + async_read_some + async_write + async_write_header + async_write_some + int_to_status + obsolete_reason + operator<< + read + read_header + read_some + string_to_field + string_to_verb + swap + to_string + to_status_class + write + write_header + write_some - Type Traits + rfc7230 - is_Body - is_Parser - is_Reader - is_Writer - has_reader - has_writer + ext_list + opt_token_list + param_list + token_list - Options - - header_max_size - body_max_size - skip_body - Constants - body_what - connection - no_content_length - parse_error - parse_flag + error + field + status + status_class + verb + + Type Traits + + is_body + is_body_writer + is_body_reader + is_fields Concepts - Body - Field - FieldSequence - Parser - Reader - Writer + Body + BodyReader + BodyWriter + Fields + FieldsReader Classes - close_reason - ping_data - stream - reason_string - teardown_tag + close_reason + ping_data + stream + reason_string Functions - async_teardown - teardown + async_teardown + is_upgrade + teardown Options - auto_fragment - decorate - keep_alive - message_type - permessage_deflate - ping_callback - read_buffer_size - read_message_max - write_buffer_size + permessage_deflate Constants - close_code - error - opcode + close_code + error + frame_type @@ -151,10 +149,10 @@ - + Core - + ZLib @@ -164,78 +162,112 @@ Classes - async_completion - basic_streambuf - buffers_adapter - consuming_buffers - dynabuf_readstream - errc - error_category - error_code - error_condition - handler_alloc - handler_ptr - static_streambuf - static_streambuf_n - static_string - streambuf - system_error + async_completion + async_result + async_return_type + basic_flat_buffer + basic_multi_buffer + buffer_cat_view + buffer_prefix_view + buffered_read_stream + buffers_adapter + consuming_buffers + drain_buffer + error_category + error_code + error_condition + file + file_mode + + + +   + + file_posix + file_stdio + file_win32 + flat_buffer + handler_alloc + handler_ptr + handler_type + iequal + iless + multi_buffer + span + static_buffer + static_buffer_n + static_string + string_param + string_view + system_error Functions - bind_handler - buffer_cat - prepare_buffer - prepare_buffers - system_category - to_string - write + bind_handler + buffer_cat + buffer_prefix + buffers + generic_category + iequals + ostream + read_size + read_size_or_throw + system_category + to_static_string + + Constants + + errc + file_mode Type Traits - is_AsyncReadStream - is_AsyncWriteStream - is_AsyncStream - is_BufferSequence - is_CompletionHandler - is_ConstBufferSequence - is_DynamicBuffer - is_MutableBufferSequence - is_SyncReadStream - is_SyncStream - is_SyncWriteStream + get_lowest_layer + has_get_io_service + is_async_read_stream + is_async_write_stream + is_async_stream + is_completion_handler + is_const_buffer_sequence + is_dynamic_buffer + is_file + is_mutable_buffer_sequence + is_sync_read_stream + is_sync_stream + is_sync_write_stream Concepts - AsyncStream - BufferSequence - DynamicBuffer - Stream - SyncStream + AsyncStream + BufferSequence + DynamicBuffer + File + Stream + SyncStream Classes - deflate_stream - inflate_stream - z_params + deflate_stream + inflate_stream + z_params Functions - deflate_upper_bound + deflate_upper_bound Constants - error - Flush - Strategy + error + Flush + Strategy @@ -254,8 +286,8 @@ - doc_debug - nested_doc_debug + doc_debug + nested_doc_debug diff --git a/src/beast/doc/source.dox b/src/beast/doc/source.dox index 624ab63310..7ead3bb099 100644 --- a/src/beast/doc/source.dox +++ b/src/beast/doc/source.dox @@ -16,7 +16,7 @@ ABBREVIATE_BRIEF = ALWAYS_DETAILED_SEC = NO INLINE_INHERITED_MEMB = YES FULL_PATH_NAMES = NO -STRIP_FROM_PATH = +STRIP_FROM_PATH = ../include/ STRIP_FROM_INC_PATH = SHORT_NAMES = NO JAVADOC_AUTOBRIEF = YES @@ -254,7 +254,7 @@ MAN_LINKS = NO # Configuration options related to the XML output #--------------------------------------------------------------------------- GENERATE_XML = YES -XML_OUTPUT = ../bin/doc/xml +XML_OUTPUT = temp/ XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- @@ -282,8 +282,12 @@ EXPAND_ONLY_PREDEF = YES SEARCH_INCLUDES = YES INCLUDE_PATH = ../ INCLUDE_FILE_PATTERNS = -PREDEFINED = DOXYGEN \ - GENERATING_DOCS + +PREDEFINED = \ + BEAST_DOXYGEN \ + BEAST_USE_POSIX_FILE=1 \ + BEAST_USE_WIN32_FILE=1 + EXPAND_AS_DEFINED = SKIP_FUNCTION_MACROS = YES diff --git a/src/beast/doc/types/Body.qbk b/src/beast/doc/types/Body.qbk deleted file mode 100644 index b539fae655..0000000000 --- a/src/beast/doc/types/Body.qbk +++ /dev/null @@ -1,49 +0,0 @@ -[/ - Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) - - Distributed under the Boost Software License, Version 1.0. (See accompanying - file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -] - -[section:Body Body requirements] - -A [*Body] type is supplied as a template argument to the __message__ class. It -controls both the type of the data member of the resulting message object, and -the algorithms used during parsing and serialization. - -In this table: - -* `X` is a type meeting the requirements of [*`Body`]. - -[table Body requirements -[[operation] [type] [semantics, pre/post-conditions]] -[ - [`X::value_type`] - [] - [ - The type of the `message::body` member. - If this is not movable or not copyable, the containing message - will be not movable or not copyable. - ] -] -[ - [`X::reader`] - [] - [ - If present, a type meeting the requirements of - [link beast.ref.Reader [*`Reader`]]. - Provides an implementation to parse the body. - ] -] -[ - [`X::writer`] - [] - [ - If present, a type meeting the requirements of - [link beast.ref.Writer [*`Writer`]]. - Provides an implementation to serialize the body. - ] -] -] - -[endsect] diff --git a/src/beast/doc/types/BufferSequence.qbk b/src/beast/doc/types/BufferSequence.qbk deleted file mode 100644 index c008457be9..0000000000 --- a/src/beast/doc/types/BufferSequence.qbk +++ /dev/null @@ -1,15 +0,0 @@ -[/ - Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) - - Distributed under the Boost Software License, Version 1.0. (See accompanying - file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -] - -[section:BufferSequence BufferSequence requirements] - -A `BufferSequence` is a type meeting either of the following requirements: - -* [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/ConstBufferSequence.html [*`ConstBufferSequence`]] -* [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/MutableBufferSequence.html [*`MutableBufferSequence`]] - -[endsect] diff --git a/src/beast/doc/types/Field.qbk b/src/beast/doc/types/Field.qbk deleted file mode 100644 index e628b980b8..0000000000 --- a/src/beast/doc/types/Field.qbk +++ /dev/null @@ -1,41 +0,0 @@ -[/ - Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) - - Distributed under the Boost Software License, Version 1.0. (See accompanying - file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -] - -[section:Field Field requirements] - -A [*`Field`] represents a single HTTP header field/value pair. - -In this table: - -* `X` denotes a type meeting the requirements of [*`Field`]. -* `a` denotes a value of type `X`. - -[table Field requirements - -[[operation][type][semantics, pre/post-conditions]] -[ - [`a.name()`] - [`boost::string_ref`] - [ - This function returns a value implicitly convertible to - `boost::string_ref` containing the case-insensitive field - name, without leading or trailing white space. - ] -] -[ - [`a.value()`] - [`boost::string_ref`] - [ - This function returns a value implicitly convertible to - `boost::string_ref` containing the value for the field. The - value is considered canonical if there is no leading or - trailing whitespace. - ] -] -] - -[endsect] diff --git a/src/beast/doc/types/FieldSequence.qbk b/src/beast/doc/types/FieldSequence.qbk deleted file mode 100644 index 2c3aa7f1ad..0000000000 --- a/src/beast/doc/types/FieldSequence.qbk +++ /dev/null @@ -1,60 +0,0 @@ -[/ - Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) - - Distributed under the Boost Software License, Version 1.0. (See accompanying - file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -] - -[section:FieldSequence FieldSequence requirements] - -A [*FieldSequence] is an iterable container whose value type meets -the requirements of [link beast.ref.Field [*Field]]. Objects that meet -these requirements become serializable by the implementation. - -In this table: - -* `X` denotes a type that meets the requirements of [*FieldSequence]. - -* `c` is a value of type `X const`. - -[table FieldSequence requirements -[[operation][type][semantics, pre/post-conditions]] -[ - [`X::value_type`] - [] - [ - A type that meets the requirements of [link beast.ref.Field [*Field]]. - ] -] -[ - [`X::const_iterator`] - [] - [ - An iterator type whose `reference` type meets the - requirements of [link beast.ref.Field [*Field]], and which - satisfies all the requirements of [*ForwardIterator], - except that: - - [ordered_list - [there is no requirement that `operator->` is provided, and] - [there is no requirement that `reference` be a reference type.] - ] - ] -] -[ - [`c.begin()`] - [`X::const_iterator`] - [ - Returns an iterator to the beginning of the field sequence. - ] -] -[ - [`c.end()`] - [`X::const_iterator`] - [ - Returns an iterator to the end of the field sequence. - ] -] -] - -[endsect] diff --git a/src/beast/doc/types/Parser.qbk b/src/beast/doc/types/Parser.qbk deleted file mode 100644 index 12cd7b749b..0000000000 --- a/src/beast/doc/types/Parser.qbk +++ /dev/null @@ -1,61 +0,0 @@ -[/ - Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) - - Distributed under the Boost Software License, Version 1.0. (See accompanying - file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -] - -[section:Parser Parser requirements] - -A [*Parser] is used to deserialize objects from -[link beast.ref.streams streams]. Objects of this type are used with -[link beast.ref.http__parse http::parse] and -[link beast.ref.http__async_parse http::async_parse]. The definition of -an object, and the predicate defining when the parse is complete, are -determined by the implementation. - -In this table: - -* `X` denotes a type meeting the requirements of [*Parser]. - -* `a` denotes a value of type `X`. - -* `b` is a value meeting the requirements of __ConstBufferSequence__. - -* `ec` is a value of type [link beast.ref.error_code `error_code&`]. - -[table Parser requirements -[[operation] [type] [semantics, pre/post-conditions]] -[ - [`a.complete()`] - [`bool`] - [ - Returns `true` when parsing is complete. - ] -] -[ - [`a.write(b, ec)`] - [`std::size_t`] - [ - Sequentially parses the octets in the specified input buffer sequence - until an error occurs, the end of the buffer is reached, or parsing is - complete. Upon success, this function returns the number of bytes used - from the input. If an error occurs, `ec` is set to the error code and - parsing stops. - ] -] -[ - [`a.write_eof(ec)`] - [`void`] - [ - Indicates to the parser that no more octets will be available. - Typically this function is called when the end of stream is reached. - For example, if a call to `boost::asio::ip::tcp::socket::read_some` - generates a `boost::asio::error::eof` error. Some objects, such as - certain HTTP/1 messages, determine the end of the message body by - an end of file marker or closing of the connection. - ] -] -] - -[endsect] diff --git a/src/beast/doc/types/Reader.qbk b/src/beast/doc/types/Reader.qbk deleted file mode 100644 index 0fd852f1c9..0000000000 --- a/src/beast/doc/types/Reader.qbk +++ /dev/null @@ -1,69 +0,0 @@ -[/ - Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) - - Distributed under the Boost Software License, Version 1.0. (See accompanying - file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -] - -[section:Reader Reader requirements] - -Parsers provided by the implementation will construct the corresponding -`reader` object during parsing. This customization point allows the -Body to determine the strategy for storing incoming message body data. - -In this table: - -* `X` denotes a type meeting the requirements of [*`Reader`]. - -* `a` denotes a value of type `X`. - -* `n` is a value convertible to `std::size_t`. - -* `p` is a `void const*` to valid memory of at least `n` bytes. - -* `ec` is a value of type [link beast.ref.error_code `error_code&`]. - -* `m` denotes a value of type `message&` where - `std::is_same::value == true`. - -[table Reader requirements -[[operation] [type] [semantics, pre/post-conditions]] -[ - [`X a(m);`] - [] - [ - `a` is constructible from `m`. The lifetime of `m` is guaranteed - to end no earlier than after `a` is destroyed. The constructor - will be called after all headers have been stored in `m`, and - before any body data is deserialized. This function must be - `noexcept`. - ] -] -[ - [`a.init(ec)`] - [`void`] - [ - Called immediately after construction. If the function sets - an error code in `ec`, the parse is aborted and the error is - propagated to the caller. This function must be `noexcept`. - ] -] -[ - [`a.write(p, n, ec)`] - [`void`] - [ - Deserializes the input sequence into the body. If `ec` is set, - the deserialization is aborted and the error is propagated to - the caller. If the message headers specify a chunked transfer - encoding, the reader will receive the decoded version of the - body. This function must be `noexcept`. - ] -] -] - -[note - Definitions for required `Reader` member functions should be declared - inline so the generated code can become part of the implementation. -] - -[endsect] diff --git a/src/beast/doc/types/Streams.qbk b/src/beast/doc/types/Streams.qbk deleted file mode 100644 index 67d6037550..0000000000 --- a/src/beast/doc/types/Streams.qbk +++ /dev/null @@ -1,34 +0,0 @@ -[/ - Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) - - Distributed under the Boost Software License, Version 1.0. (See accompanying - file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -] - -[section:streams Streams requirements] - -Stream types represent objects capable of performing synchronous or -asynchronous I/O. They are based on concepts from `boost::asio`. - -[heading:Stream Stream] - -A type modeling [*`Stream`] meets either or both of the following requirements: - -* [*`AsyncStream`] -* [*`SyncStream`] - -[heading:AsyncStream AsyncStream] - -A type modeling [*`AsyncStream`] meets the following requirements: - -* [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/AsyncReadStream.html [*`AsyncReadStream`]] -* [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/AsyncWriteStream.html [*`AsyncWriteStream`]] - -[heading:SyncStream SyncStream] - -A type modeling [*`SyncStream`] meets the following requirements: - -* [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/SyncReadStream.html [*`SyncReadStream`]] -* [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/SyncWriteStream.html [*`SyncWriteStream`]] - -[endsect] diff --git a/src/beast/doc/types/Writer.qbk b/src/beast/doc/types/Writer.qbk deleted file mode 100644 index aef4601ce4..0000000000 --- a/src/beast/doc/types/Writer.qbk +++ /dev/null @@ -1,161 +0,0 @@ -[/ - Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) - - Distributed under the Boost Software License, Version 1.0. (See accompanying - file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -] - -[section:Writer Writer requirements] - -A `Writer` serializes the message body. The implementation creates an instance -of this type when serializing a message, and calls into it zero or more times -to provide buffers containing the data. The interface of `Writer` is intended -to allow serialization in these scenarios: - -* A body that does not entirely fit in memory. -* A body produced incrementally from coroutine output. -* A body represented by zero or more buffers already in memory. -* A body as a series of buffers when the content size is not known ahead of time. -* Body data generated on demand from other threads. -* Body data computed algorithmically. - -In this table: - -* `X` denotes a type meeting the requirements of `Writer`. - -* `a` denotes a value of type `X`. - -* `m` denotes a value of type `message const&` where - `std::is_same:value == true`. - -* `ec` is a value of type [link beast.ref.error_code `error_code&`] - -* `wf` is a [*write function]: a function object of unspecified type provided - by the implementation which accepts any value meeting the requirements - of __ConstBufferSequence__ as its single parameter. - -[table Writer requirements -[[operation] [type] [semantics, pre/post-conditions]] -[ - [`X a(m);`] - [] - [ - `a` is constructible from `m`. The lifetime of `m` is guaranteed - to end no earlier than after `a` is destroyed. This function must - be `noexcept`. - ] -] -[ - [`a.init(ec)`] - [`void`] - [ - Called immediately after construction. If the function sets an - error code in `ec`, the serialization is aborted and the error - is propagated to the caller. This function must be `noexcept`. - ] -] -[ - [`a.content_length()`] - [`std::uint64_t`] - [ - If this member is present, it is called after initialization - and before calls to provide buffers. The serialized message will - have the Content-Length field set to the value returned from - this function. If this member is absent, the serialized message - body will be chunk-encoded for HTTP versions 1.1 and later, else - the serialized message body will be sent unmodified, with the - error `boost::asio::error::eof` returned to the caller, to notify - they should close the connection to indicate the end of the message. - This function must be `noexcept`. - ] -] -[ - [`a.write(ec, wf)`] - [`bool`] - [ - Called repeatedly after `init` succeeds. `wf` is a function object - which takes as its single parameter any value meeting the requirements - of __ConstBufferSequence__. Buffers provided to this write function - must remain valid until the next member function of `writer` is - invoked (which may be the destructor). This function returns `true` - to indicate all message body data has been written, or `false` if - there is more body data. - ] -] -] - -[note - Definitions for required `Writer` member functions should be declared - inline so the generated code can become part of the implementation. -] - -Exemplar: -``` -struct writer -{ -public: - /** Construct the writer. - - The msg object is guaranteed to exist for the lifetime of the writer. - - Exceptions: - No-throw guarantee. - - @param msg The message whose body is to be written. - */ - template - explicit - writer(message const& msg) noexcept; - - /** Initialize the writer. - - Called once immediately after construction. - The writer can perform initialization which may fail. - - @param ec Contains the error code if any errors occur. - */ - void - init(error_code& ec) noexcept; - - /** Returns the content length. - - If this member is present, the implementation will set the - Content-Length field accordingly. If absent, the implementation will - use chunk-encoding or terminate the connection to indicate the end - of the message. - */ - std::uint64_t - content_length() noexcept; - - /** Write zero or one buffer representing the message body. - - Postconditions: - - If return value is `true`: - * Callee made zero or one calls to `write`. - * There is no more data remaining to write. - - If return value is `false`: - * Callee does not take ownership of resume. - * Callee made one call to `write`. - - @param ec Set to indicate an error. This will cause an - asynchronous write operation to complete with the error. - - @param write A functor the writer will call to provide the next - set of buffers. Ownership of the buffers is not transferred, - the writer must guarantee that the buffers remain valid until the - next member function is invoked, which may be the destructor. - - @return `true` if there is no more data to send, - `false` when there may be more data. - */ - template - bool - write( - error_code&, - WriteFunction&& wf) noexcept; -}; -``` - -[endsect] diff --git a/src/beast/doc/websocket.qbk b/src/beast/doc/websocket.qbk deleted file mode 100644 index 77bcc2bde0..0000000000 --- a/src/beast/doc/websocket.qbk +++ /dev/null @@ -1,483 +0,0 @@ -[/ - Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) - - Distributed under the Boost Software License, Version 1.0. (See accompanying - file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -] - -[section:websocket WebSocket] - -[block ''' - - Creation - Making connections - Handshaking - Messages - Frames - Control Frames - Buffers - Asynchronous interface - The io_service - Thread Safety - -'''] - -The WebSocket Protocol enables two-way communication between a client -running untrusted code in a controlled environment to a remote host that has -opted-in to communications from that code. The protocol consists of an opening -handshake followed by basic message framing, layered over TCP. The goal of -this technology is to provide a mechanism for browser-based applications that -need two-way communication with servers that does not rely on opening multiple -HTTP connections. - -Beast.WebSocket provides developers with a robust WebSocket implementation -built on Boost.Asio with a consistent asynchronous model using a modern -C++ approach. - -The WebSocket protocol is described fully in -[@https://tools.ietf.org/html/rfc6455 rfc6455] - -[note - The following documentation assumes familiarity with both - Boost.Asio and the WebSocket protocol specification described in __rfc6455__. -] - - - - -[section:creation Creation] - -The interface to Beast's WebSocket implementation is a single template -class [link beast.ref.websocket__stream `beast::websocket::stream`] which -wraps a "next layer" object. The next layer object must meet the requirements -of [link beast.ref.streams.SyncStream [*`SyncReadStream`]] if synchronous -operations are performed, or -[link beast.ref.streams.AsyncStream [*`AsyncStream`]] if asynchronous -operations are performed, or both. Arguments supplied during construction are -passed to next layer's constructor. Here we declare a websocket stream over -a TCP/IP socket with ownership of the socket: -``` -boost::asio::io_service ios; -beast::websocket::stream ws{ios}; -``` - -[heading Using SSL] - -To use WebSockets over SSL, choose an SSL stream for the next layer template -argument when constructing the stream. -``` -#include -#include -#include - -boost::asio::io_service ios; -boost::asio::ssl::context ctx{boost::asio::ssl::context::sslv23}; -beast::websocket::stream ws{ios, ctx}; -``` - -[note - When creating websocket stream objects using SSL, it is necessary - to include the file ``. -] - -[heading Non-owning references] - -For servers that can handshake in multiple protocols, it may be desired -to wrap an object that already exists. This socket can be moved in: -``` - boost::asio::ip::tcp::socket&& sock; - ... - beast::websocket::stream ws{std::move(sock)}; -``` - -Or, the wrapper can be constructed with a non-owning reference. In -this case, the caller is responsible for managing the lifetime of the -underlying socket being wrapped: -``` - boost::asio::ip::tcp::socket sock; - ... - beast::websocket::stream ws{sock}; -``` - -The layer being wrapped can be accessed through the websocket's "next layer", -permitting callers to interact directly with its interface. -``` - boost::asio::ssl::context ctx{boost::asio::ssl::context::sslv23}; - beast::websocket::stream> ws{ios, ctx}; - ... - ws.next_layer().shutdown(); // ssl::stream shutdown -``` - -[warning - Initiating read and write operations on the next layer while - stream operations are being performed can break invariants, and - result in undefined behavior. -] - -[endsect] - - - -[section:connections Making connections] - -Connections are established by using the interfaces which already exist -for the next layer. For example, making an outgoing connection: -``` - std::string const host = "mywebapp.com"; - boost::asio::io_service ios; - boost::asio::ip::tcp::resolver r{ios}; - beast::websocket::stream ws{ios}; - boost::asio::connect(ws.next_layer(), - r.resolve(boost::asio::ip::tcp::resolver::query{host, "ws"})); -``` - -Accepting an incoming connection: -``` -void do_accept(boost::asio::ip::tcp::acceptor& acceptor) -{ - beast::websocket::stream ws{acceptor.get_io_service()}; - acceptor.accept(ws.next_layer()); -} -``` - -[note - Examples use synchronous interfaces for clarity of exposition. -] - -[endsect] - - - -[section:handshaking Handshaking] - -A WebSocket session begins when one side sends the HTTP Upgrade request -for websocket, and the other side sends an appropriate HTTP response -indicating that the request was accepted and that the connection has -been upgraded. The HTTP Upgrade request must include the Host HTTP field, -and the URI of the resource to request. -[link beast.ref.websocket__stream.handshake `handshake`] is used to send the -request with the required host and resource strings. -``` - beast::websocket::stream ws{ios}; - ... - ws.set_option(beast::websocket::keep_alive(true)); - ws.handshake("ws.example.com:80", "/cgi-bin/bitcoin-prices"); -``` - -The [link beast.ref.websocket__stream `stream`] automatically -handles receiving and processing the HTTP response to the handshake request. -The call to handshake is successful if a HTTP response is received with the -101 "Switching Protocols" status code. On failure, an error is returned or an -exception is thrown. Depending on the keep alive setting, the socket may remain -open for a subsequent handshake attempt - -Performing a handshake for an incoming websocket upgrade request operates -similarly. If the handshake fails, an error is returned or exception thrown: -``` - beast::websocket::stream ws{ios}; - ... - ws.accept(); -``` - -Servers that can handshake in multiple protocols may have already read data -on the connection, or might have already received an entire HTTP request -containing the upgrade request. Overloads of `accept` allow callers to -pass in this additional buffered handshake data. -``` -void do_accept(boost::asio::ip::tcp::socket& sock) -{ - boost::asio::streambuf sb; - boost::asio::read_until(sock, sb, "\r\n\r\n"); - ... - beast::websocket::stream ws{sock}; - ws.accept(sb.data()); - ... -} -``` - -Alternatively, the caller can pass an entire HTTP request if it was -obtained elsewhere: -``` -void do_accept(boost::asio::ip::tcp::socket& sock) -{ - boost::asio::streambuf sb; - beast::http::request request; - beast::http::read(sock, sb, request); - if(beast::http::is_upgrade(request)) - { - websocket::stream ws{sock}; - ws.accept(request); - ... - } -} -``` - -[endsect] - - - -[section:messages Messages] - -After the WebSocket handshake is accomplished, callers may send and receive -messages using the message oriented interface. This interface requires that -all of the buffers representing the message are known ahead of time: -``` -void echo(beast::websocket::stream& ws) -{ - beast::streambuf sb; - beast::websocket::opcode::value op; - ws.read(op, sb); - - ws.set_option(beast::websocket::message_type{op}); - ws.write(sb.data()); - sb.consume(sb.size()); -} -``` - -[important - Calls to [link beast.ref.websocket__stream.set_option `set_option`] - must be made from the same implicit or explicit strand as that used - to perform other operations. -] - -[endsect] - - - -[section:frames Frames] - -Some use-cases make it impractical or impossible to buffer the entire -message ahead of time: - -* Streaming multimedia to an endpoint. -* Sending a message that does not fit in memory at once. -* Providing incremental results as they become available. - -For these cases, the frame oriented interface may be used. This -example reads and echoes a complete message using this interface: -``` -void echo(beast::websocket::stream& ws) -{ - beast::streambuf sb; - beast::websocket::frame_info fi; - for(;;) - { - ws.read_frame(fi, sb); - if(fi.fin) - break; - } - ws.set_option(beast::websocket::message_type{fi.op}); - beast::consuming_buffers< - beast::streambuf::const_buffers_type> cb{sb.data()}; - for(;;) - { - using boost::asio::buffer_size; - std::size_t size = std::min(buffer_size(cb)); - if(size > 512) - { - ws.write_frame(false, beast::prepare_buffers(512, cb)); - cb.consume(512); - } - else - { - ws.write_frame(true, cb); - break; - } - } -} -``` - -[endsect] - - - -[section:control Control Frames] - -Control frames are small (less than 128 bytes) messages entirely contained -in an individual WebSocket frame. They may be sent at any time by either -peer on an established connection, and can appear in between continuation -frames for a message. There are three types of control frames: ping, pong, -and close. - -A sent ping indicates a request that the sender wants to receive a pong. A -pong is a response to a ping. Pongs may be sent unsolicited, at any time. -One use for an unsolicited pong is to inform the remote peer that the -session is still active after a long period of inactivity. A close frame -indicates that the remote peer wishes to close the WebSocket connection. -The connection is considered gracefully closed when each side has sent -and received a close frame. - -During read operations, Beast automatically reads and processes control -frames. Pings are replied to as soon as possible with a pong, received -ping and pongs are delivered to the ping callback. The receipt of a close -frame initiates the WebSocket close procedure, eventually resulting in the -error code [link beast.ref.websocket__error `error::closed`] being delivered -to the caller in a subsequent read operation, assuming no other error -takes place. - -A consequence of this automatic behavior is that caller-initiated read -operations can cause socket writes. However, these writes will not -compete with caller-initiated write operations. For the purposes of -correctness with respect to the stream invariants, caller-initiated -read operations still only count as a read. This means that callers can -have a simultaneously active read, write, and ping operation in progress, -while the implementation also automatically handles control frames. - -[heading Ping and Pong Frames] - -Ping and pong messages are control frames which may be sent at any time -by either peer on an established WebSocket connection. They are sent -using the functions - [link beast.ref.websocket__stream.ping `ping`] and - [link beast.ref.websocket__stream.pong `pong`]. - -To be notified of ping and pong control frames, callers may register a -"ping callback" using [link beast.ref.websocket__stream.set_option `set_option`]. -The object provided with this option should be callable with the following -signature: -``` - void on_ping(bool is_pong, ping_data const& payload); - ... - ws.set_option(ping_callback{&on_ping}); -``` - -When a ping callback is registered, all pings and pongs received through -either synchronous read functions or asynchronous read functions will -invoke the ping callback, with the value of `is_pong` set to `true` if a -pong was received else `false` if a ping was received. The payload of -the ping or pong control frame is passed in the payload argument. - -Unlike regular completion handlers used in calls to asynchronous initiation -functions, the ping callback only needs to be set once. The callback is not -reset when a ping or pong is received. The same callback is used for both -synchronous and asynchronous reads. The ping callback is passive; in order -to receive pings and pongs, a synchronous or asynchronous stream read -function must be active. - -[note - When an asynchronous read function receives a ping or pong, the - ping callback is invoked in the same manner as that used to invoke - the final completion handler of the corresponding read function. -] - -[heading Close Frames] - -The WebSocket protocol defines a procedure and control message for initiating -a close of the session. Handling of close initiated by the remote end of the -connection is performed automatically. To manually initiate a close, use -the [link beast.ref.websocket__stream.close `close`] function: -``` - ws.close(); -``` - -When the remote peer initiates a close by sending a close frame, Beast -will handle it for you by causing the next read to return `error::closed`. -When this error code is delivered, it indicates to the application that -the WebSocket connection has been closed cleanly, and that the TCP/IP -connection has been closed. After initiating a close, it is necessary to -continue reading messages until receiving the error `error::closed`. This -is because the remote peer may still be sending message and control frames -before it receives and responds to the close frame. - -[important - To receive the [link beast.ref.websocket__error `error::closed`] - error, a read operation is required. -] - -[heading Auto-fragment] - -To ensure timely delivery of control frames, large messages can be broken up -into smaller sized frames. The automatic fragment option turns on this -feature, and the write buffer size option determines the maximum size of -the fragments: -``` - ... - ws.set_option(beast::websocket::auto_fragment{true}); - ws.set_option(beast::websocket::write_buffer_size{16384}); -``` - -[endsect] - - - -[section:buffers Buffers] - -Because calls to read data may return a variable amount of bytes, the -interface to calls that read data require an object that meets the requirements -of [link beast.ref.DynamicBuffer [*`DynamicBuffer`]]. This concept is modeled on -[@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/basic_streambuf.html `boost::asio::basic_streambuf`]. - -The implementation does not perform queueing or buffering of messages. If -desired, these features should be provided by callers. The impact of this -design is that library users are in full control of the allocation strategy -used to store data and the back-pressure applied on the read and write side -of the underlying TCP/IP connection. - -[endsect] - - - -[section:async Asynchronous interface] - -Asynchronous versions are available for all functions: -``` -websocket::opcode op; -ws.async_read(op, sb, - [](boost::system::error_code const& ec) - { - ... - }); -``` - -Calls to asynchronous initiation functions support the extensible asynchronous -model developed by the Boost.Asio author, allowing for traditional completion -handlers, stackful or stackless coroutines, and even futures: -``` -void echo(websocket::stream& ws, - boost::asio::yield_context yield) -{ - ws.async_read(sb, yield); - std::future fut = - ws.async_write, sb.data(), boost::use_future); - ... -} -``` - -[endsect] - - - -[section:io_service The io_service] - -The creation and operation of the -[@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/io_service.html `boost::asio::io_service`] -associated with the underlying stream is left to the callers, permitting any -implementation strategy including one that does not require threads for -environments where threads are unavailable. Beast.WebSocket itself does not -use or require threads. - -[endsect] - - - -[section:threads Thread Safety] - -Like a regular asio socket, a [link beast.ref.websocket__stream `stream`] is -not thread safe. Callers are responsible for synchronizing operations on the -socket using an implicit or explicit strand, as per the Asio documentation. -The asynchronous interface supports one active read and one active write -simultaneously. Undefined behavior results if two or more reads or two or -more writes are attempted concurrently. Caller initiated WebSocket ping, pong, -and close operations each count as an active write. - -The implementation uses composed asynchronous operations internally; a high -level read can cause both reads and writes to take place on the underlying -stream. This behavior is transparent to callers. - -[endsect] - - - -[endsect] - -[include quickref.xml] diff --git a/src/beast/doc/xsl/class_detail.xsl b/src/beast/doc/xsl/class_detail.xsl new file mode 100644 index 0000000000..1120891e7f --- /dev/null +++ b/src/beast/doc/xsl/class_detail.xsl @@ -0,0 +1,51 @@ + + + class ``[link beast.concept.streams.AsyncStream [*AsyncStream]]`` + + + class __AsyncReadStream__ + + + class __AsyncWriteStream__ + + + class ``[link beast.concept.Body [*Body]]`` + + + class ``[link beast.concept.BufferSequence [*BufferSequence]]`` + + + + ``[link beast.concept.BufferSequence [*BufferSequence]]`` + + + class __CompletionHandler__ + + + class __ConstBufferSequence__ + + + class ``[link beast.concept.DynamicBuffer [*DynamicBuffer]]`` + + + class ``[link beast.concept.Fields [*Fields]]`` + + + class __Handler__ + + + class __MutableBufferSequence__ + + + class ``[link beast.concept.streams.Stream [*Stream]]`` + + + class ``[link beast.concept.streams.SyncStream [*SyncStream]]`` + + + class __SyncReadStream__ + + + class __SyncWriteStream__ + + diff --git a/src/beast/doc/xsl/includes.xsl b/src/beast/doc/xsl/includes.xsl new file mode 100644 index 0000000000..6f48e5fe5b --- /dev/null +++ b/src/beast/doc/xsl/includes.xsl @@ -0,0 +1,5 @@ + + Defined in header [include_file + + ] + diff --git a/src/beast/doc/xsl/includes_foot.xsl b/src/beast/doc/xsl/includes_foot.xsl new file mode 100644 index 0000000000..ba723c06d8 --- /dev/null +++ b/src/beast/doc/xsl/includes_foot.xsl @@ -0,0 +1,16 @@ + + + + Convenience header [include_file beast/core.hpp] + + + Convenience header [include_file beast/http.hpp] + + + Convenience header [include_file beast/websocket.hpp] + + + Convenience header [include_file beast/zlib.hpp] + + + diff --git a/src/beast/doc/xsl/reference.xsl b/src/beast/doc/xsl/reference.xsl new file mode 100644 index 0000000000..90a7c0e870 --- /dev/null +++ b/src/beast/doc/xsl/reference.xsl @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/beast/example/CMakeLists.txt b/src/beast/example/CMakeLists.txt new file mode 100644 index 0000000000..cfaad504ba --- /dev/null +++ b/src/beast/example/CMakeLists.txt @@ -0,0 +1,16 @@ +# Part of Beast + +add_subdirectory (echo-op) +add_subdirectory (http-client) +add_subdirectory (http-crawl) +add_subdirectory (http-server-fast) +add_subdirectory (http-server-small) +add_subdirectory (http-server-threaded) +add_subdirectory (server-framework) +add_subdirectory (websocket-client) +add_subdirectory (websocket-server-async) + +if (OPENSSL_FOUND) + add_subdirectory (http-client-ssl) + add_subdirectory (websocket-client-ssl) +endif() diff --git a/src/beast/example/Jamfile b/src/beast/example/Jamfile new file mode 100644 index 0000000000..61c87b8f46 --- /dev/null +++ b/src/beast/example/Jamfile @@ -0,0 +1,20 @@ +# +# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +# + +build-project echo-op ; +build-project http-client ; +build-project http-crawl ; +build-project http-server-fast ; +build-project http-server-small ; +build-project http-server-threaded ; +build-project server-framework ; +build-project websocket-client ; +build-project websocket-server-async ; + +# VFALCO How do I make this work on Windows and if OpenSSL is not available? +#build-project ssl-http-client ; +#build-project ssl-websocket-client ; diff --git a/src/beast/example/common/detect_ssl.hpp b/src/beast/example/common/detect_ssl.hpp new file mode 100644 index 0000000000..298a644baa --- /dev/null +++ b/src/beast/example/common/detect_ssl.hpp @@ -0,0 +1,481 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_EXAMPLE_COMMON_DETECT_SSL_HPP +#define BEAST_EXAMPLE_COMMON_DETECT_SSL_HPP + +#include +#include + +//------------------------------------------------------------------------------ +// +// Example: Detect TLS/SSL +// +//------------------------------------------------------------------------------ + +//[example_core_detect_ssl_1 + +#include +#include + +/** Return `true` if a buffer contains a TLS/SSL client handshake. + + This function returns `true` if the beginning of the buffer + indicates that a TLS handshake is being negotiated, and that + there are at least four octets in the buffer. + + If the content of the buffer cannot possibly be a TLS handshake + request, the function returns `false`. Otherwise, if additional + octets are required, `boost::indeterminate` is returned. + + @param buffer The input buffer to inspect. This type must meet + the requirements of @b ConstBufferSequence. + + @return `boost::tribool` indicating whether the buffer contains + a TLS client handshake, does not contain a handshake, or needs + additional octets. + + @see + + http://www.ietf.org/rfc/rfc2246.txt + 7.4. Handshake protocol +*/ +template +boost::tribool +is_ssl_handshake(ConstBufferSequence const& buffers); + +//] + +using namespace beast; + +//[example_core_detect_ssl_2 + +template< + class ConstBufferSequence> +boost::tribool +is_ssl_handshake( + ConstBufferSequence const& buffers) +{ + // Make sure buffers meets the requirements + static_assert(is_const_buffer_sequence::value, + "ConstBufferSequence requirements not met"); + + // We need at least one byte to really do anything + if(boost::asio::buffer_size(buffers) < 1) + return boost::indeterminate; + + // Extract the first byte, which holds the + // "message" type for the Handshake protocol. + unsigned char v; + boost::asio::buffer_copy(boost::asio::buffer(&v, 1), buffers); + + // Check that the message type is "SSL Handshake" (rfc2246) + if(v != 0x16) + { + // This is definitely not a handshake + return false; + } + + // At least four bytes are needed for the handshake + // so make sure that we get them before returning `true` + if(boost::asio::buffer_size(buffers) < 4) + return boost::indeterminate; + + // This can only be a TLS/SSL handshake + return true; +} + +//] + +//[example_core_detect_ssl_3 + +/** Detect a TLS/SSL handshake on a stream. + + This function reads from a stream to determine if a TLS/SSL + handshake is being received. The function call will block + until one of the following conditions is true: + + @li The disposition of the handshake is determined + + @li An error occurs + + Octets read from the stream will be stored in the passed dynamic + buffer, which may be used to perform the TLS handshake if the + detector returns true, or otherwise consumed by the caller based + on the expected protocol. + + @param stream The stream to read from. This type must meet the + requirements of @b SyncReadStream. + + @param buffer The dynamic buffer to use. This type must meet the + requirements of @b DynamicBuffer. + + @param ec Set to the error if any occurred. + + @return `boost::tribool` indicating whether the buffer contains + a TLS client handshake, does not contain a handshake, or needs + additional octets. If an error occurs, the return value is + undefined. +*/ +template< + class SyncReadStream, + class DynamicBuffer> +boost::tribool +detect_ssl( + SyncReadStream& stream, + DynamicBuffer& buffer, + error_code& ec) +{ + // Make sure arguments meet the requirements + static_assert(is_sync_read_stream::value, + "SyncReadStream requirements not met"); + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + + // Loop until an error occurs or we get a definitive answer + for(;;) + { + // There could already be data in the buffer + // so we do this first, before reading from the stream. + auto const result = is_ssl_handshake(buffer.data()); + + // If we got an answer, return it + if(! boost::indeterminate(result)) + { + ec = {}; // indicate no error + return result; + } + + // The algorithm should never need more than 4 bytes + BOOST_ASSERT(buffer.size() < 4); + + // Create up to 4 bytes of space in the buffer's output area. + auto const mutable_buffer = buffer.prepare(4 - buffer.size()); + + // Try to fill our buffer by reading from the stream + std::size_t const bytes_transferred = stream.read_some(mutable_buffer, ec); + + // Check for an error + if(ec) + break; + + // Commit what we read into the buffer's input area. + buffer.commit(bytes_transferred); + } + + // error + return false; +} + +//] + +//[example_core_detect_ssl_4 + +/** Detect a TLS/SSL handshake asynchronously on a stream. + + This function is used to asynchronously determine if a TLS/SSL + handshake is being received. + The function call always returns immediately. The asynchronous + operation will continue until one of the following conditions + is true: + + @li The disposition of the handshake is determined + + @li An error occurs + + This operation is implemented in terms of zero or more calls to + the next layer's `async_read_some` function, and is known as a + composed operation. The program must ensure that the + stream performs no other operations until this operation completes. + + Octets read from the stream will be stored in the passed dynamic + buffer, which may be used to perform the TLS handshake if the + detector returns true, or otherwise consumed by the caller based + on the expected protocol. + + @param stream The stream to read from. This type must meet the + requirements of @b AsyncReadStream. + + @param buffer The dynamic buffer to use. This type must meet the + requirements of @b DynamicBuffer. + + @param handler The handler to be called when the request + completes. Copies will be made of the handler as required. + The equivalent function signature of the handler must be: + @code + void handler( + error_code const& error, // Set to the error, if any + boost::tribool result // The result of the detector + ); + @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using `boost::asio::io_service::post`. +*/ +template< + class AsyncReadStream, + class DynamicBuffer, + class CompletionToken> +async_return_type< /*< The [link beast.ref.beast__async_return_type `async_return_type`] customizes the return value based on the completion token >*/ + CompletionToken, + void(error_code, boost::tribool)> /*< This is the signature for the completion handler >*/ +async_detect_ssl( + AsyncReadStream& stream, + DynamicBuffer& buffer, + CompletionToken&& token); + +//] + +//[example_core_detect_ssl_5 + +// This is the composed operation. +template< + class AsyncReadStream, + class DynamicBuffer, + class Handler> +class detect_ssl_op; + +// Here is the implementation of the asynchronous initation function +template< + class AsyncReadStream, + class DynamicBuffer, + class CompletionToken> +async_return_type< + CompletionToken, + void(error_code, boost::tribool)> +async_detect_ssl( + AsyncReadStream& stream, + DynamicBuffer& buffer, + CompletionToken&& token) +{ + // Make sure arguments meet the requirements + static_assert(is_async_read_stream::value, + "SyncReadStream requirements not met"); + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + + // This helper manages some of the handler's lifetime and + // uses the result and handler specializations associated with + // the completion token to help customize the return value. + // + beast::async_completion< + CompletionToken, void(beast::error_code, boost::tribool)> init{token}; + + // Create the composed operation and launch it. This is a constructor + // call followed by invocation of operator(). We use handler_type + // to convert the completion token into the correct handler type, + // allowing user defined specializations of the async result template + // to take effect. + // + detect_ssl_op>{ + stream, buffer, init.completion_handler}( + beast::error_code{}, 0); + + // This hook lets the caller see a return value when appropriate. + // For example this might return std::future if + // CompletionToken is boost::asio::use_future. + // + // If a coroutine is used for the token, the return value from + // this function will be the `boost::tribool` representing the result. + // + return init.result.get(); +} + +//] + +//[example_core_detect_ssl_6 + +// Read from a stream to invoke is_tls_handshake asynchronously +// +template< + class AsyncReadStream, + class DynamicBuffer, + class Handler> +class detect_ssl_op +{ + // This composed operation has trivial state, + // so it is just kept inside the class and can + // be cheaply copied as needed by the implementation. + + // Indicates what step in the operation's state + // machine to perform next, starting from zero. + int step_ = 0; + + AsyncReadStream& stream_; + DynamicBuffer& buffer_; + Handler handler_; + boost::tribool result_ = false; + +public: + // Boost.Asio requires that handlers are CopyConstructible. + // The state for this operation is cheap to copy. + detect_ssl_op(detect_ssl_op const&) = default; + + // The constructor just keeps references the callers varaibles. + // + template + detect_ssl_op(AsyncReadStream& stream, + DynamicBuffer& buffer, DeducedHandler&& handler) + : stream_(stream) + , buffer_(buffer) + , handler_(std::forward(handler)) + { + } + + // Determines if the next asynchronous operation represents a + // continuation of the asynchronous flow of control associated + // with the final handler. If we are past step two, it means + // we have performed an asynchronous operation therefore any + // subsequent operation would represent a continuation. + // Otherwise, we propagate the handler's associated value of + // is_continuation. Getting this right means the implementation + // may schedule the invokation of the invoked functions more + // efficiently. + // + friend bool asio_handler_is_continuation(detect_ssl_op* op) + { + // This next call is structured to permit argument + // dependent lookup to take effect. + using boost::asio::asio_handler_is_continuation; + + // Always use std::addressof to pass the pointer to the handler, + // otherwise an unwanted overload of operator& may be called instead. + return op->step_ > 2 || + asio_handler_is_continuation(std::addressof(op->handler_)); + } + + // Handler hook forwarding. These free functions invoke the hooks + // associated with the final completion handler. In effect, they + // make the Asio implementation treat our composed operation the + // same way it would treat the final completion handler for the + // purpose of memory allocation and invocation. + // + // Our implementation just passes through the call to the hook + // associated with the final handler. + + friend void* asio_handler_allocate(std::size_t size, detect_ssl_op* op) + { + using boost::asio::asio_handler_allocate; + return asio_handler_allocate(size, std::addressof(op->handler_)); + } + + friend void asio_handler_deallocate(void* p, std::size_t size, detect_ssl_op* op) + { + using boost::asio::asio_handler_deallocate; + return asio_handler_deallocate(p, size, std::addressof(op->handler_)); + } + + template + friend void asio_handler_invoke(Function&& f, detect_ssl_op* op) + { + using boost::asio::asio_handler_invoke; + return asio_handler_invoke(f, std::addressof(op->handler_)); + } + + // Our main entry point. This will get called as our + // intermediate operations complete. Definition below. + // + void operator()(beast::error_code ec, std::size_t bytes_transferred); +}; + +//] + +//[example_core_detect_ssl_7 + +// detect_ssl_op is callable with the signature +// void(error_code, bytes_transferred), +// allowing `*this` to be used as a ReadHandler +// +template< + class AsyncStream, + class DynamicBuffer, + class Handler> +void +detect_ssl_op:: +operator()(beast::error_code ec, std::size_t bytes_transferred) +{ + // Execute the state machine + switch(step_) + { + // Initial state + case 0: + // See if we can detect the handshake + result_ = is_ssl_handshake(buffer_.data()); + + // If there's a result, call the handler + if(! boost::indeterminate(result_)) + { + // We need to invoke the handler, but the guarantee + // is that the handler will not be called before the + // call to async_detect_ssl returns, so we must post + // the operation to the io_service. The helper function + // `bind_handler` lets us bind arguments in a safe way + // that preserves the type customization hooks of the + // original handler. + step_ = 1; + return stream_.get_io_service().post( + bind_handler(std::move(*this), ec, 0)); + } + + // The algorithm should never need more than 4 bytes + BOOST_ASSERT(buffer_.size() < 4); + + step_ = 2; + + do_read: + // We need more bytes, but no more than four total. + return stream_.async_read_some(buffer_.prepare(4 - buffer_.size()), std::move(*this)); + + case 1: + // Call the handler + break; + + case 2: + // Set this so that asio_handler_is_continuation knows that + // the next asynchronous operation represents a continuation + // of the initial asynchronous operation. + step_ = 3; + BOOST_FALLTHROUGH; + + case 3: + if(ec) + { + // Deliver the error to the handler + result_ = false; + + // We don't need bind_handler here because we were invoked + // as a result of an intermediate asynchronous operation. + break; + } + + // Commit the bytes that we read + buffer_.commit(bytes_transferred); + + // See if we can detect the handshake + result_ = is_ssl_handshake(buffer_.data()); + + // If it is detected, call the handler + if(! boost::indeterminate(result_)) + { + // We don't need bind_handler here because we were invoked + // as a result of an intermediate asynchronous operation. + break; + } + + // Read some more + goto do_read; + } + + // Invoke the final handler. + handler_(ec, result_); +} + +//] + +#endif diff --git a/src/beast/example/common/helpers.hpp b/src/beast/example/common/helpers.hpp new file mode 100644 index 0000000000..784adc567e --- /dev/null +++ b/src/beast/example/common/helpers.hpp @@ -0,0 +1,56 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_EXAMPLE_COMMON_HELPERS_HPP +#define BEAST_EXAMPLE_COMMON_HELPERS_HPP + +#include +#include +#include +#include + +/// Block until SIGINT or SIGTERM is received. +inline +void +sig_wait() +{ + boost::asio::io_service ios{1}; + boost::asio::signal_set signals(ios, SIGINT, SIGTERM); + signals.async_wait([&](boost::system::error_code const&, int){}); + ios.run(); +} + +namespace detail { + +inline +void +print_1(std::ostream&) +{ +} + +template +void +print_1(std::ostream& os, T1 const& t1, TN const&... tn) +{ + os << t1; + print_1(os, tn...); +} + +} // detail + +// compose a string to std::cout or std::cerr atomically +// +template +void +print(std::ostream& os, Args const&... args) +{ + std::stringstream ss; + detail::print_1(ss, args...); + os << ss.str() << std::endl; +} + +#endif diff --git a/src/beast/example/common/mime_types.hpp b/src/beast/example/common/mime_types.hpp new file mode 100644 index 0000000000..65f646f983 --- /dev/null +++ b/src/beast/example/common/mime_types.hpp @@ -0,0 +1,46 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_EXAMPLE_COMMON_MIME_TYPES_HPP +#define BEAST_EXAMPLE_COMMON_MIME_TYPES_HPP + +#include +#include + +// Return a reasonable mime type based on the extension of a file. +// +template +beast::string_view +mime_type(boost::filesystem::path const& path) +{ + using beast::iequals; + auto const ext = path.extension().string(); + if(iequals(ext, ".txt")) return "text/plain"; + if(iequals(ext, ".htm")) return "text/html"; + if(iequals(ext, ".html")) return "text/html"; + if(iequals(ext, ".php")) return "text/html"; + if(iequals(ext, ".css")) return "text/css"; + if(iequals(ext, ".js")) return "application/javascript"; + if(iequals(ext, ".json")) return "application/json"; + if(iequals(ext, ".xml")) return "application/xml"; + if(iequals(ext, ".swf")) return "application/x-shockwave-flash"; + if(iequals(ext, ".flv")) return "video/x-flv"; + if(iequals(ext, ".png")) return "image/png"; + if(iequals(ext, ".jpe")) return "image/jpeg"; + if(iequals(ext, ".jpeg")) return "image/jpeg"; + if(iequals(ext, ".jpg")) return "image/jpeg"; + if(iequals(ext, ".gif")) return "image/gif"; + if(iequals(ext, ".bmp")) return "image/bmp"; + if(iequals(ext, ".ico")) return "image/vnd.microsoft.icon"; + if(iequals(ext, ".tiff")) return "image/tiff"; + if(iequals(ext, ".tif")) return "image/tiff"; + if(iequals(ext, ".svg")) return "image/svg+xml"; + if(iequals(ext, ".svgz")) return "image/svg+xml"; + return "application/text"; +} + +#endif diff --git a/src/beast/example/common/rfc7231.hpp b/src/beast/example/common/rfc7231.hpp new file mode 100644 index 0000000000..1ee2044126 --- /dev/null +++ b/src/beast/example/common/rfc7231.hpp @@ -0,0 +1,39 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_EXAMPLE_COMMON_RFC7231_HPP +#define BEAST_EXAMPLE_COMMON_RFC7231_HPP + +#include +#include + +namespace rfc7231 { + +// This aggregates a collection of algorithms +// corresponding to specifications in rfc7231: +// +// https://tools.ietf.org/html/rfc7231 +// + +/** Returns `true` if the message specifies Expect: 100-continue + + @param req The request to check + + @see https://tools.ietf.org/html/rfc7231#section-5.1.1 +*/ +template +bool +is_expect_100_continue(beast::http::request< + Body, beast::http::basic_fields> const& req) +{ + return beast::iequals( + req[beast::http::field::expect], "100-continue"); +} + +} // rfc7231 + +#endif diff --git a/src/beast/example/common/root_certificates.hpp b/src/beast/example/common/root_certificates.hpp new file mode 100644 index 0000000000..7d935a5664 --- /dev/null +++ b/src/beast/example/common/root_certificates.hpp @@ -0,0 +1,118 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_EXAMPLE_COMMON_ROOT_CERTIFICATES_HPP +#define BEAST_EXAMPLE_COMMON_ROOT_CERTIFICATES_HPP + +#include +#include + +namespace ssl = boost::asio::ssl; // from + +namespace detail { + +// The template argument is gratuituous, to +// allow the implementation to be header-only. +// +template +void +load_root_certificates(ssl::context& ctx, boost::system::error_code& ec) +{ + std::string const cert = + /* This is the DigiCert root certificate. + + CN = DigiCert High Assurance EV Root CA + OU = www.digicert.com + O = DigiCert Inc + C = US + + Valid to: Sunday, ?November ?9, ?2031 5:00:00 PM + + Thumbprint(sha1): + 5f b7 ee 06 33 e2 59 db ad 0c 4c 9a e6 d3 8f 1a 61 c7 dc 25 + */ + "-----BEGIN CERTIFICATE-----\n" + "MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs\n" + "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n" + "d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j\n" + "ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL\n" + "MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3\n" + "LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug\n" + "RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm\n" + "+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW\n" + "PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM\n" + "xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB\n" + "Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3\n" + "hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg\n" + "EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF\n" + "MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA\n" + "FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec\n" + "nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z\n" + "eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF\n" + "hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2\n" + "Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe\n" + "vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep\n" + "+OkuE6N36B9K\n" + "-----END CERTIFICATE-----\n" + /* This is the GeoTrust root certificate. + + CN = GeoTrust Global CA + O = GeoTrust Inc. + C = US + Valid to: Friday, ‎May ‎20, ‎2022 9:00:00 PM + + Thumbprint(sha1): + ‎de 28 f4 a4 ff e5 b9 2f a3 c5 03 d1 a3 49 a7 f9 96 2a 82 12 + */ + "-----BEGIN CERTIFICATE-----\n" + "MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs\n" + "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n" + "d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j\n" + "ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL\n" + "MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3\n" + "LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug\n" + "RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm\n" + "+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW\n" + "PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM\n" + "xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB\n" + "Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3\n" + "hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg\n" + "EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF\n" + "MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA\n" + "FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec\n" + "nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z\n" + "eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF\n" + "hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2\n" + "Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe\n" + "vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep\n" + "+OkuE6N36B9K\n" + "-----END CERTIFICATE-----\n" + ; + + ctx.add_certificate_authority( + boost::asio::buffer(cert.data(), cert.size()), ec); + if(ec) + return; +} + +} // detail + +// Load the root certificates into an ssl::context +// +// This function is inline so that its easy to take +// the address and there's nothing weird like a +// gratuituous template argument; thus it appears +// like a "normal" function. +// +inline +void +load_root_certificates(ssl::context& ctx, boost::system::error_code& ec) +{ + detail::load_root_certificates(ctx, ec); +} + +#endif diff --git a/src/beast/example/common/ssl_stream.hpp b/src/beast/example/common/ssl_stream.hpp new file mode 100644 index 0000000000..4238ecdeae --- /dev/null +++ b/src/beast/example/common/ssl_stream.hpp @@ -0,0 +1,334 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_EXAMPLE_COMMON_SSL_STREAM_HPP +#define BEAST_EXAMPLE_COMMON_SSL_STREAM_HPP + +// This include is necessary to work with `ssl::stream` and `beast::websocket::stream` +#include + +#include +#include +#include +#include +#include +#include + +/** C++11 enabled SSL socket wrapper + + This wrapper provides an interface identical to `boost::asio::ssl::stream`, + with the following additional properties: + + @li Satisfies @b MoveConstructible + + @li Satisfies @b MoveAssignable + + @li Constructible from a moved socket. +*/ +template +class ssl_stream + : public boost::asio::ssl::stream_base +{ + // only works for boost::asio::ip::tcp::socket + // for now because of the move limitations + static_assert(std::is_same::value, + "NextLayer requirements not met"); + + using stream_type = boost::asio::ssl::stream; + + std::unique_ptr p_; + boost::asio::ssl::context* ctx_; + +public: + /// The native handle type of the SSL stream. + using native_handle_type = typename stream_type::native_handle_type; + + /// Structure for use with deprecated impl_type. + using impl_struct = typename stream_type::impl_struct; + + /// (Deprecated: Use native_handle_type.) The underlying implementation type. + using impl_type = typename stream_type::impl_type; + + /// The type of the next layer. + using next_layer_type = typename stream_type::next_layer_type; + + /// The type of the lowest layer. + using lowest_layer_type = typename stream_type::lowest_layer_type; + + ssl_stream(boost::asio::ip::tcp::socket&& sock, boost::asio::ssl::context& ctx) + : p_(new stream_type{sock.get_io_service(), ctx}) + , ctx_(&ctx) + { + p_->next_layer() = std::move(sock); + } + + ssl_stream(ssl_stream&& other) + : p_(new stream_type(other.get_io_service(), *other.ctx_)) + , ctx_(other.ctx_) + { + using std::swap; + swap(p_, other.p_); + } + + ssl_stream& operator=(ssl_stream&& other) + { + std::unique_ptr p( + new stream_type{other.get_io_service(), other.ctx_}); + using std::swap; + swap(p_, p); + swap(p_, other.p_); + ctx_ = other.ctx_; + return *this; + } + + boost::asio::io_service& + get_io_service() + { + return p_->get_io_service(); + } + + native_handle_type + native_handle() + { + return p_->native_handle(); + } + + impl_type + impl() + { + return p_->impl(); + } + + next_layer_type const& + next_layer() const + { + return p_->next_layer(); + } + + next_layer_type& + next_layer() + { + return p_->next_layer(); + } + + lowest_layer_type& + lowest_layer() + { + return p_->lowest_layer(); + } + + lowest_layer_type const& + lowest_layer() const + { + return p_->lowest_layer(); + } + + void + set_verify_mode(boost::asio::ssl::verify_mode v) + { + p_->set_verify_mode(v); + } + + boost::system::error_code + set_verify_mode(boost::asio::ssl::verify_mode v, + boost::system::error_code& ec) + { + return p_->set_verify_mode(v, ec); + } + + void + set_verify_depth(int depth) + { + p_->set_verify_depth(depth); + } + + boost::system::error_code + set_verify_depth( + int depth, boost::system::error_code& ec) + { + return p_->set_verify_depth(depth, ec); + } + + template + void + set_verify_callback(VerifyCallback callback) + { + p_->set_verify_callback(callback); + } + + template + boost::system::error_code + set_verify_callback(VerifyCallback callback, + boost::system::error_code& ec) + { + return p_->set_verify_callback(callback, ec); + } + + void + handshake(handshake_type type) + { + p_->handshake(type); + } + + boost::system::error_code + handshake(handshake_type type, + boost::system::error_code& ec) + { + return p_->handshake(type, ec); + } + + template + void + handshake( + handshake_type type, ConstBufferSequence const& buffers) + { + p_->handshake(type, buffers); + } + + template + boost::system::error_code + handshake(handshake_type type, + ConstBufferSequence const& buffers, + boost::system::error_code& ec) + { + return p_->handshake(type, buffers, ec); + } + + template + BOOST_ASIO_INITFN_RESULT_TYPE(HandshakeHandler, + void(boost::system::error_code)) + async_handshake(handshake_type type, + BOOST_ASIO_MOVE_ARG(HandshakeHandler) handler) + { + return p_->async_handshake(type, + BOOST_ASIO_MOVE_CAST(HandshakeHandler)(handler)); + } + + template + BOOST_ASIO_INITFN_RESULT_TYPE(BufferedHandshakeHandler, + void (boost::system::error_code, std::size_t)) + async_handshake(handshake_type type, ConstBufferSequence const& buffers, + BOOST_ASIO_MOVE_ARG(BufferedHandshakeHandler) handler) + { + return p_->async_handshake(type, buffers, + BOOST_ASIO_MOVE_CAST(BufferedHandshakeHandler)(handler)); + } + + void + shutdown() + { + p_->shutdown(); + } + + boost::system::error_code + shutdown(boost::system::error_code& ec) + { + return p_->shutdown(ec); + } + + template + BOOST_ASIO_INITFN_RESULT_TYPE(ShutdownHandler, + void (boost::system::error_code)) + async_shutdown(BOOST_ASIO_MOVE_ARG(ShutdownHandler) handler) + { + return p_->async_shutdown( + BOOST_ASIO_MOVE_CAST(ShutdownHandler)(handler)); + } + + template + std::size_t + write_some(ConstBufferSequence const& buffers) + { + return p_->write_some(buffers); + } + + template + std::size_t + write_some(ConstBufferSequence const& buffers, + boost::system::error_code& ec) + { + return p_->write_some(buffers, ec); + } + + template + BOOST_ASIO_INITFN_RESULT_TYPE(WriteHandler, + void (boost::system::error_code, std::size_t)) + async_write_some(ConstBufferSequence const& buffers, + BOOST_ASIO_MOVE_ARG(WriteHandler) handler) + { + return p_->async_write_some(buffers, + BOOST_ASIO_MOVE_CAST(WriteHandler)(handler)); + } + + template + std::size_t + read_some(MutableBufferSequence const& buffers) + { + return p_->read_some(buffers); + } + + template + std::size_t + read_some(MutableBufferSequence const& buffers, + boost::system::error_code& ec) + { + return p_->read_some(buffers, ec); + } + + template + BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler, + void(boost::system::error_code, std::size_t)) + async_read_some(MutableBufferSequence const& buffers, + BOOST_ASIO_MOVE_ARG(ReadHandler) handler) + { + return p_->async_read_some(buffers, + BOOST_ASIO_MOVE_CAST(ReadHandler)(handler)); + } + + template + friend + void + teardown(beast::websocket::teardown_tag, + ssl_stream& stream, + boost::system::error_code& ec); + + template + friend + void + async_teardown(beast::websocket::teardown_tag, + ssl_stream& stream, TeardownHandler&& handler); +}; + +// These hooks are used to inform beast::websocket::stream on +// how to tear down the connection as part of the WebSocket +// protocol specifications + +template +inline +void +teardown(beast::websocket::teardown_tag, + ssl_stream& stream, + boost::system::error_code& ec) +{ + // Just forward it to the wrapped ssl::stream + using beast::websocket::teardown; + teardown(beast::websocket::teardown_tag{}, *stream.p_, ec); +} + +template +inline +void +async_teardown(beast::websocket::teardown_tag, + ssl_stream& stream, TeardownHandler&& handler) +{ + // Just forward it to the wrapped ssl::stream + using beast::websocket::async_teardown; + async_teardown(beast::websocket::teardown_tag{}, + *stream.p_, std::forward(handler)); +} + +#endif diff --git a/src/beast/example/common/write_msg.hpp b/src/beast/example/common/write_msg.hpp new file mode 100644 index 0000000000..038df599f1 --- /dev/null +++ b/src/beast/example/common/write_msg.hpp @@ -0,0 +1,228 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_EXAMPLE_COMMON_WRITE_MSG_HPP +#define BEAST_EXAMPLE_COMMON_WRITE_MSG_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace detail { + +/** Composed operation to send an HTTP message + + This implements the composed operation needed for the + @ref async_write_msg function. +*/ +template< + class AsyncWriteStream, + class Handler, + bool isRequest, class Body, class Fields> +class write_msg_op +{ + // This composed operation has a state which is not trivial + // to copy (msg) so we need to store the state in an allocated + // object. + // + struct data + { + // The stream we are writing to + AsyncWriteStream& stream; + + // The message we are sending. Note that this composed + // operation takes ownership of the message and destroys + // it when it is done. + // + beast::http::message msg; + + // Serializer for the message + beast::http::serializer sr; + + data( + Handler& handler, + AsyncWriteStream& stream_, + beast::http::message&& msg_) + : stream(stream_) + , msg(std::move(msg_)) + , sr(msg) + { + boost::ignore_unused(handler); + } + }; + + // `handler_ptr` is a utility which helps to manage a composed + // operation's state. It is similar to a shared pointer, but + // it uses handler allocation hooks to allocate and free memory, + // and it also helps to meet Asio's deallocate-before-invocation + // guarantee. + // + beast::handler_ptr d_; + +public: + // Asio can move and copy the handler, we support both + write_msg_op(write_msg_op&&) = default; + write_msg_op(write_msg_op const&) = default; + + // Constructor + // + // We take the handler as a template type to + // support both const and rvalue references. + // + template< + class DeducedHandler, + class... Args> + write_msg_op( + DeducedHandler&& h, + AsyncWriteStream& s, + Args&&... args) + : d_(std::forward(h), + s, std::forward(args)...) + { + } + + // Entry point + // + // The initiation function calls this to start the operation + // + void + operator()() + { + auto& d = *d_; + beast::http::async_write( + d.stream, d.sr, std::move(*this)); + } + + // Completion handler + // + // This gets called when beast::http::async_write completes + // + void + operator()(beast::error_code ec) + { + d_.invoke(ec); + } + + // + // These hooks are necessary for Asio + // + // The meaning is explained in the Beast documentation + // + + friend + void* asio_handler_allocate( + std::size_t size, write_msg_op* op) + { + using boost::asio::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(op->d_.handler())); + } + + friend + void asio_handler_deallocate( + void* p, std::size_t size, write_msg_op* op) + { + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->d_.handler())); + } + + friend + bool asio_handler_is_continuation(write_msg_op* op) + { + using boost::asio::asio_handler_is_continuation; + return asio_handler_is_continuation(std::addressof(op->d_.handler())); + } + + template + friend + void asio_handler_invoke(Function&& f, write_msg_op* op) + { + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->d_.handler())); + } +}; + +} // detail + +/** Write an HTTP message to a stream asynchronously + + This function is used to write a complete message to a stream asynchronously + using HTTP/1. The function call always returns immediately. The asynchronous + operation will continue until one of the following conditions is true: + + @li The entire message is written. + + @li An error occurs. + + This operation is implemented in terms of zero or more calls to the stream's + `async_write_some` function, and is known as a composed operation. + The program must ensure that the stream performs no other write operations + until this operation completes. The algorithm will use a temporary + @ref serializer with an empty chunk decorator to produce buffers. If + the semantics of the message indicate that the connection should be + closed after the message is sent, the error delivered by this function + will be @ref error::end_of_stream + + @param stream The stream to which the data is to be written. + The type must support the @b AsyncWriteStream concept. + + @param msg The message to write. The function will take ownership + of the object as if by move constrction. + + @param handler The handler to be called when the operation + completes. Copies will be made of the handler as required. + The equivalent function signature of the handler must be: + @code void handler( + error_code const& error // result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using `boost::asio::io_service::post`. +*/ +template< + class AsyncWriteStream, + bool isRequest, class Body, class Fields, + class WriteHandler> +beast::async_return_type +async_write_msg( + AsyncWriteStream& stream, + beast::http::message&& msg, + WriteHandler&& handler) +{ + static_assert( + beast::is_async_write_stream::value, + "AsyncWriteStream requirements not met"); + + static_assert(beast::http::is_body::value, + "Body requirements not met"); + + static_assert(beast::http::is_body_reader::value, + "BodyReader requirements not met"); + + beast::async_completion init{handler}; + + ::detail::write_msg_op< + AsyncWriteStream, + beast::handler_type, + isRequest, Body, Fields>{ + init.completion_handler, + stream, + std::move(msg)}(); + + return init.result.get(); +} + +#endif diff --git a/src/beast/example/doc/http_examples.hpp b/src/beast/example/doc/http_examples.hpp new file mode 100644 index 0000000000..7371e15daa --- /dev/null +++ b/src/beast/example/doc/http_examples.hpp @@ -0,0 +1,1060 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include +#include + +/* This file contains the functions and classes found in the documentation + + They are compiled and run as part of the unit tests, so you can copy + the code and use it in your own projects as a starting point for + building a network application. +*/ + +// The documentation assumes the beast::http namespace +namespace beast { +namespace http { + +//------------------------------------------------------------------------------ +// +// Example: Expect 100-continue +// +//------------------------------------------------------------------------------ + +//[example_http_send_expect_100_continue + +/** Send a request with Expect: 100-continue + + This function will send a request with the Expect: 100-continue + field by first sending the header, then waiting for a successful + response from the server before continuing to send the body. If + a non-successful server response is received, the function + returns immediately. + + @param stream The remote HTTP server stream. + + @param buffer The buffer used for reading. + + @param req The request to send. This function modifies the object: + the Expect header field is inserted into the message if it does + not already exist, and set to "100-continue". + + @param ec Set to the error, if any occurred. +*/ +template< + class SyncStream, + class DynamicBuffer, + class Body, class Allocator> +void +send_expect_100_continue( + SyncStream& stream, + DynamicBuffer& buffer, + request>& req, + error_code& ec) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + + // Insert or replace the Expect field + req.set(field::expect, "100-continue"); + + // Create the serializer + request_serializer> sr{req}; + + // Send just the header + write_header(stream, sr, ec); + if(ec) + return; + + // Read the response from the server. + // A robust client could set a timeout here. + { + response res; + read(stream, buffer, res, ec); + if(ec) + return; + if(res.result() != status::continue_) + { + // The server indicated that it will not + // accept the request, so skip sending the body. + return; + } + } + + // Server is OK with the request, send the body + write(stream, sr, ec); +} + +//] + +//[example_http_receive_expect_100_continue + +/** Receive a request, handling Expect: 100-continue if present. + + This function will read a request from the specified stream. + If the request contains the Expect: 100-continue field, a + status response will be delivered. + + @param stream The remote HTTP client stream. + + @param buffer The buffer used for reading. + + @param ec Set to the error, if any occurred. +*/ +template< + class SyncStream, + class DynamicBuffer> +void +receive_expect_100_continue( + SyncStream& stream, + DynamicBuffer& buffer, + error_code& ec) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + + // Declare a parser for a request with a string body + request_parser parser; + + // Read the header + read_header(stream, buffer, parser, ec); + if(ec) + return; + + // Check for the Expect field value + if(parser.get()[field::expect] == "100-continue") + { + // send 100 response + response res; + res.version = 11; + res.result(status::continue_); + res.set(field::server, "test"); + write(stream, res, ec); + if(ec) + return; + } + + // Read the rest of the message. + // + // We use parser.base() to return a basic_parser&, to avoid an + // ambiguous function error (from boost::asio::read). Another + // solution is to qualify the call, e.g. `beast::http::read` + // + read(stream, buffer, parser.base(), ec); +} + +//] + +//------------------------------------------------------------------------------ +// +// Example: Send Child Process Output +// +//------------------------------------------------------------------------------ + +//[example_http_send_cgi_response + +/** Send the output of a child process as an HTTP response. + + The output of the child process comes from a @b SyncReadStream. Data + will be sent continuously as it is produced, without the requirement + that the entire process output is buffered before being sent. The + response will use the chunked transfer encoding. + + @param input A stream to read the child process output from. + + @param output A stream to write the HTTP response to. + + @param ec Set to the error, if any occurred. +*/ +template< + class SyncReadStream, + class SyncWriteStream> +void +send_cgi_response( + SyncReadStream& input, + SyncWriteStream& output, + error_code& ec) +{ + static_assert(is_sync_read_stream::value, + "SyncReadStream requirements not met"); + + static_assert(is_sync_write_stream::value, + "SyncWriteStream requirements not met"); + + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + + // Set up the response. We use the buffer_body type, + // allowing serialization to use manually provided buffers. + response res; + + res.result(status::ok); + res.version = 11; + res.set(field::server, "Beast"); + res.set(field::transfer_encoding, "chunked"); + + // No data yet, but we set more = true to indicate + // that it might be coming later. Otherwise the + // serializer::is_done would return true right after + // sending the header. + res.body.data = nullptr; + res.body.more = true; + + // Create the serializer. + response_serializer sr{res}; + + // Send the header immediately. + write_header(output, sr, ec); + if(ec) + return; + + // Alternate between reading from the child process + // and sending all the process output until there + // is no more output. + do + { + // Read a buffer from the child process + char buffer[2048]; + auto bytes_transferred = input.read_some( + boost::asio::buffer(buffer, sizeof(buffer)), ec); + if(ec == boost::asio::error::eof) + { + ec = {}; + + // `nullptr` indicates there is no buffer + res.body.data = nullptr; + + // `false` means no more data is coming + res.body.more = false; + } + else + { + if(ec) + return; + + // Point to our buffer with the bytes that + // we received, and indicate that there may + // be some more data coming + res.body.data = buffer; + res.body.size = bytes_transferred; + res.body.more = true; + } + + // Write everything in the body buffer + write(output, sr, ec); + + // This error is returned by body_buffer during + // serialization when it is done sending the data + // provided and needs another buffer. + if(ec == error::need_buffer) + { + ec = {}; + continue; + } + if(ec) + return; + } + while(! sr.is_done()); +} + +//] + +//-------------------------------------------------------------------------- +// +// Example: HEAD Request +// +//-------------------------------------------------------------------------- + +//[example_http_do_head_response + +/** Handle a HEAD request for a resource. +*/ +template< + class SyncStream, + class DynamicBuffer +> +void do_server_head( + SyncStream& stream, + DynamicBuffer& buffer, + error_code& ec) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirments not met"); + + // We deliver this payload for all GET requests + static std::string const payload = "Hello, world!"; + + // Read the request + request req; + read(stream, buffer, req, ec); + if(ec) + return; + + // Set up the response, starting with the common fields + response res; + res.version = 11; + res.set(field::server, "test"); + + // Now handle request-specific fields + switch(req.method()) + { + case verb::head: + case verb::get: + { + // A HEAD request is handled by delivering the same + // set of headers that would be sent for a GET request, + // including the Content-Length, except for the body. + res.result(status::ok); + res.set(field::content_length, payload.size()); + + // For GET requests, we include the body + if(req.method() == verb::get) + { + // We deliver the same payload for GET requests + // regardless of the target. A real server might + // deliver a file based on the target. + res.body = payload; + } + break; + } + + default: + { + // We return responses indicating an error if + // we do not recognize the request method. + res.result(status::bad_request); + res.set(field::content_type, "text/plain"); + res.body = "Invalid request-method '" + req.method_string().to_string() + "'"; + res.prepare_payload(); + break; + } + } + + // Send the response + write(stream, res, ec); + if(ec) + return; +} + +//] + +//[example_http_do_head_request + +/** Send a HEAD request for a resource. + + This function submits a HEAD request for the specified resource + and returns the response. + + @param res The response. This is an output parameter. + + @param stream The synchronous stream to use. + + @param buffer The buffer to use. + + @param target The request target. + + @param ec Set to the error, if any occurred. + + @throws std::invalid_argument if target is empty. +*/ +template< + class SyncStream, + class DynamicBuffer +> +response +do_head_request( + SyncStream& stream, + DynamicBuffer& buffer, + string_view target, + error_code& ec) +{ + // Do some type checking to be a good citizen + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirments not met"); + + // The interfaces we are using are low level and do not + // perform any checking of arguments; so we do it here. + if(target.empty()) + throw std::invalid_argument("target may not be empty"); + + // Build the HEAD request for the target + request req; + req.version = 11; + req.method(verb::head); + req.target(target); + req.set(field::user_agent, "test"); + + // A client MUST send a Host header field in all HTTP/1.1 request messages. + // https://tools.ietf.org/html/rfc7230#section-5.4 + req.set(field::host, "localhost"); + + // Now send it + write(stream, req, ec); + if(ec) + return {}; + + // Create a parser to read the response. + // Responses to HEAD requests MUST NOT include + // a body, so we use the `empty_body` type and + // only attempt to read the header. + parser p; + read_header(stream, buffer, p, ec); + if(ec) + return {}; + + // Transfer ownership of the response to the caller. + return p.release(); +} + +//] + +//------------------------------------------------------------------------------ +// +// Example: HTTP Relay +// +//------------------------------------------------------------------------------ + +//[example_http_relay + +/** Relay an HTTP message. + + This function efficiently relays an HTTP message from a downstream + client to an upstream server, or from an upstream server to a + downstream client. After the message header is read from the input, + a user provided transformation function is invoked which may change + the contents of the header before forwarding to the output. This may + be used to adjust fields such as Server, or proxy fields. + + @param output The stream to write to. + + @param input The stream to read from. + + @param buffer The buffer to use for the input. + + @param transform The header transformation to apply. The function will + be called with this signature: + @code + template + void transform(message< + isRequest, Body, Fields>&, // The message to transform + error_code&); // Set to the error, if any + @endcode + + @param ec Set to the error if any occurred. + + @tparam isRequest `true` to relay a request. + + @tparam Fields The type of fields to use for the message. +*/ +template< + bool isRequest, + class SyncWriteStream, + class SyncReadStream, + class DynamicBuffer, + class Transform> +void +relay( + SyncWriteStream& output, + SyncReadStream& input, + DynamicBuffer& buffer, + error_code& ec, + Transform&& transform) +{ + static_assert(is_sync_write_stream::value, + "SyncWriteStream requirements not met"); + + static_assert(is_sync_read_stream::value, + "SyncReadStream requirements not met"); + + // A small buffer for relaying the body piece by piece + char buf[2048]; + + // Create a parser with a buffer body to read from the input. + parser p; + + // Create a serializer from the message contained in the parser. + serializer sr{p.get()}; + + // Read just the header from the input + read_header(input, buffer, p, ec); + if(ec) + return; + + // Apply the caller's header tranformation + transform(p.get(), ec); + if(ec) + return; + + // Send the transformed message to the output + write_header(output, sr, ec); + if(ec) + return; + + // Loop over the input and transfer it to the output + do + { + if(! p.is_done()) + { + // Set up the body for writing into our small buffer + p.get().body.data = buf; + p.get().body.size = sizeof(buf); + + // Read as much as we can + read(input, buffer, p, ec); + + // This error is returned when buffer_body uses up the buffer + if(ec == error::need_buffer) + ec = {}; + if(ec) + return; + + // Set up the body for reading. + // This is how much was parsed: + p.get().body.size = sizeof(buf) - p.get().body.size; + p.get().body.data = buf; + p.get().body.more = ! p.is_done(); + } + else + { + p.get().body.data = nullptr; + p.get().body.size = 0; + } + + // Write everything in the buffer (which might be empty) + write(output, sr, ec); + + // This error is returned when buffer_body uses up the buffer + if(ec == error::need_buffer) + ec = {}; + if(ec) + return; + } + while(! p.is_done() && ! sr.is_done()); +} + +//] + +//------------------------------------------------------------------------------ +// +// Example: Serialize to std::ostream +// +//------------------------------------------------------------------------------ + +//[example_http_write_ostream + +// The detail namespace means "not public" +namespace detail { + +// This helper is needed for C++11. +// When invoked with a buffer sequence, writes the buffers `to the std::ostream`. +template +class write_ostream_helper +{ + Serializer& sr_; + std::ostream& os_; + +public: + write_ostream_helper(Serializer& sr, std::ostream& os) + : sr_(sr) + , os_(os) + { + } + + // This function is called by the serializer + template + void + operator()(error_code& ec, ConstBufferSequence const& buffers) const + { + // These asio functions are needed to access a buffer's contents + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + + // Error codes must be cleared on success + ec = {}; + + // Keep a running total of how much we wrote + std::size_t bytes_transferred = 0; + + // Loop over the buffer sequence + for(auto it = buffers.begin(); it != buffers.end(); ++ it) + { + // This is the next buffer in the sequence + boost::asio::const_buffer const buffer = *it; + + // Write it to the std::ostream + os_.write( + buffer_cast(buffer), + buffer_size(buffer)); + + // If the std::ostream fails, convert it to an error code + if(os_.fail()) + { + ec = make_error_code(errc::io_error); + return; + } + + // Adjust our running total + bytes_transferred += buffer_size(buffer); + } + + // Inform the serializer of the amount we consumed + sr_.consume(bytes_transferred); + } +}; + +} // detail + +/** Write a message to a `std::ostream`. + + This function writes the serialized representation of the + HTTP/1 message to the sream. + + @param os The `std::ostream` to write to. + + @param msg The message to serialize. + + @param ec Set to the error, if any occurred. +*/ +template< + bool isRequest, + class Body, + class Fields> +void +write_ostream( + std::ostream& os, + message& msg, + error_code& ec) +{ + // Create the serializer instance + serializer sr{msg}; + + // This lambda is used as the "visit" function + detail::write_ostream_helper lambda{sr, os}; + do + { + // In C++14 we could use a generic lambda but since we want + // to require only C++11, the lambda is written out by hand. + // This function call retrieves the next serialized buffers. + sr.next(ec, lambda); + if(ec) + return; + } + while(! sr.is_done()); +} + +//] + +//------------------------------------------------------------------------------ +// +// Example: Parse from std::istream +// +//------------------------------------------------------------------------------ + +//[example_http_read_istream + +/** Read a message from a `std::istream`. + + This function attempts to parse a complete HTTP/1 message from the stream. + + @param is The `std::istream` to read from. + + @param buffer The buffer to use. + + @param msg The message to store the result. + + @param ec Set to the error, if any occurred. +*/ +template< + class Allocator, + bool isRequest, + class Body> +void +read_istream( + std::istream& is, + basic_flat_buffer& buffer, + message& msg, + error_code& ec) +{ + // Create the message parser + // + // Arguments passed to the parser's constructor are + // forwarded to the message constructor. Here, we use + // a move construction in case the caller has constructed + // their message in a non-default way. + // + parser p{std::move(msg)}; + + do + { + // Extract whatever characters are presently available in the istream + if(is.rdbuf()->in_avail() > 0) + { + // Get a mutable buffer sequence for writing + auto const mb = buffer.prepare( + static_cast(is.rdbuf()->in_avail())); + + // Now get everything we can from the istream + buffer.commit(static_cast(is.readsome( + boost::asio::buffer_cast(mb), + boost::asio::buffer_size(mb)))); + } + else if(buffer.size() == 0) + { + // Our buffer is empty and we need more characters, + // see if we've reached the end of file on the istream + if(! is.eof()) + { + // Get a mutable buffer sequence for writing + auto const mb = buffer.prepare(1024); + + // Try to get more from the istream. This might block. + is.read( + boost::asio::buffer_cast(mb), + boost::asio::buffer_size(mb)); + + // If an error occurs on the istream then return it to the caller. + if(is.fail() && ! is.eof()) + { + // We'll just re-use io_error since std::istream has no error_code interface. + ec = make_error_code(errc::io_error); + return; + } + + // Commit the characters we got to the buffer. + buffer.commit(static_cast(is.gcount())); + } + else + { + // Inform the parser that we've reached the end of the istream. + p.put_eof(ec); + if(ec) + return; + break; + } + } + + // Write the data to the parser + auto const bytes_used = p.put(buffer.data(), ec); + + // This error means that the parser needs additional octets. + if(ec == error::need_more) + ec = {}; + if(ec) + return; + + // Consume the buffer octets that were actually parsed. + buffer.consume(bytes_used); + } + while(! p.is_done()); + + // Transfer ownership of the message container in the parser to the caller. + msg = p.release(); +} + +//] + +//------------------------------------------------------------------------------ +// +// Example: Deferred Body Type +// +//------------------------------------------------------------------------------ + +//[example_http_defer_body + +/** Handle a form PUT request, choosing a body type depending on the Content-Type. + + This reads a request from the input stream. If the method is POST, and + the Content-Type is "application/x-www-form-urlencoded " or + "multipart/form-data", a `string_body` is used to receive and store + the message body. Otherwise, a `dynamic_body` is used to store the message + body. After the request is received, the handler will be invoked with the + request. + + @param stream The stream to read from. + + @param buffer The buffer to use for reading. + + @param handler The handler to invoke when the request is complete. + The handler must be invokable with this signature: + @code + template + void handler(request&& req); + @endcode + + @throws system_error Thrown on failure. +*/ +template< + class SyncReadStream, + class DynamicBuffer, + class Handler> +void +do_form_request( + SyncReadStream& stream, + DynamicBuffer& buffer, + Handler&& handler) +{ + // Start with an empty_body parser + request_parser req0; + + // Read just the header. Otherwise, the empty_body + // would generate an error if body octets were received. + read_header(stream, buffer, req0); + + // Choose a body depending on the method verb + switch(req0.get().method()) + { + case verb::post: + { + // If this is not a form upload then use a string_body + if( req0.get()[field::content_type] != "application/x-www-form-urlencoded" && + req0.get()[field::content_type] != "multipart/form-data") + goto do_string_body; + + // Commit to string_body as the body type. + // As long as there are no body octets in the parser + // we are constructing from, no exception is thrown. + request_parser req{std::move(req0)}; + + // Finish reading the message + read(stream, buffer, req); + + // Call the handler. It can take ownership + // if desired, since we are calling release() + handler(req.release()); + break; + } + + do_string_body: + default: + { + // Commit to dynamic_body as the body type. + // As long as there are no body octets in the parser + // we are constructing from, no exception is thrown. + request_parser req{std::move(req0)}; + + // Finish reading the message + read(stream, buffer, req); + + // Call the handler. It can take ownership + // if desired, since we are calling release() + handler(req.release()); + break; + } + } +} + +//] + + + +//------------------------------------------------------------------------------ +// +// Example: Custom Parser +// +//------------------------------------------------------------------------------ + +//[example_http_custom_parser + +template +class custom_parser + : public basic_parser> +{ + // The friend declaration is needed, + // otherwise the callbacks must be made public. + friend class basic_parser; + + /// Called after receiving the request-line (isRequest == true). + void + on_request( + verb method, // The method verb, verb::unknown if no match + string_view method_str, // The method as a string + string_view target, // The request-target + int version, // The HTTP-version + error_code& ec); // The error returned to the caller, if any + + /// Called after receiving the start-line (isRequest == false). + void + on_response( + int code, // The status-code + string_view reason, // The obsolete reason-phrase + int version, // The HTTP-version + error_code& ec); // The error returned to the caller, if any + + /// Called after receiving a header field. + void + on_field( + field f, // The known-field enumeration constant + string_view name, // The field name string. + string_view value, // The field value + error_code& ec); // The error returned to the caller, if any + + /// Called after the complete header is received. + void + on_header( + error_code& ec); // The error returned to the caller, if any + + /// Called just before processing the body, if a body exists. + void + on_body(boost::optional< + std::uint64_t> const& + content_length, // Content length if known, else `boost::none` + error_code& ec); // The error returned to the caller, if any + + /** Called for each piece of the body, if a body exists. + + If present, the chunked Transfer-Encoding will be removed + before this callback is invoked. The function returns + the number of bytes consumed from the input buffer. + Any input octets not consumed will be will be presented + on subsequent calls. + */ + std::size_t + on_data( + string_view s, // A portion of the body + error_code& ec); // The error returned to the caller, if any + + /// Called for each chunk header. + void + on_chunk( + std::uint64_t size, // The size of the upcoming chunk + string_view extension, // The chunk-extension (may be empty) + error_code& ec); // The error returned to the caller, if any + + /// Called when the complete message is parsed. + void + on_complete(error_code& ec); + +public: + custom_parser() = default; +}; + +//] + +// Definitions are not part of the docs but necessary to link + +template +void custom_parser:: +on_request(verb method, string_view method_str, + string_view path, int version, error_code& ec) +{ + boost::ignore_unused(method, method_str, path, version); + ec = {}; +} + +template +void custom_parser:: +on_response(int status, string_view reason, + int version, error_code& ec) +{ + boost::ignore_unused(status, reason, version); + ec = {}; +} + +template +void custom_parser:: +on_field(field f, string_view name, + string_view value, error_code& ec) +{ + boost::ignore_unused(f, name, value); + ec = {}; +} + +template +void custom_parser:: +on_header(error_code& ec) +{ + ec = {}; +} + +template +void custom_parser:: +on_body(boost::optional const& content_length, + error_code& ec) +{ + boost::ignore_unused(content_length); + ec = {}; +} + +template +std::size_t custom_parser:: +on_data(string_view s, error_code& ec) +{ + boost::ignore_unused(s); + ec = {}; + return s.size(); +} + +template +void custom_parser:: +on_chunk(std::uint64_t size, + string_view extension, error_code& ec) +{ + boost::ignore_unused(size, extension); + ec = {}; +} + +template +void custom_parser:: +on_complete(error_code& ec) +{ + ec = {}; +} + +//------------------------------------------------------------------------------ +// +// Example: Incremental Read +// +//------------------------------------------------------------------------------ + +//[example_incremental_read + +/* This function reads a message using a fixed size buffer to hold + portions of the body, and prints the body contents to a `std::ostream`. +*/ +template< + bool isRequest, + class SyncReadStream, + class DynamicBuffer> +void +read_and_print_body( + std::ostream& os, + SyncReadStream& stream, + DynamicBuffer& buffer, + error_code& ec) +{ + parser p; + read_header(stream, buffer, p, ec); + if(ec) + return; + while(! p.is_done()) + { + char buf[512]; + p.get().body.data = buf; + p.get().body.size = sizeof(buf); + read(stream, buffer, p, ec); + if(ec == error::need_buffer) + ec.assign(0, ec.category()); + if(ec) + return; + os.write(buf, sizeof(buf) - p.get().body.size); + } +} + +//] + +} // http +} // beast diff --git a/src/beast/example/echo-op/CMakeLists.txt b/src/beast/example/echo-op/CMakeLists.txt new file mode 100644 index 0000000000..d6af84394c --- /dev/null +++ b/src/beast/example/echo-op/CMakeLists.txt @@ -0,0 +1,12 @@ +# Part of Beast + +GroupSources(include/beast beast) + +GroupSources(example/echo-op "/") + +add_executable (echo-op + ${BEAST_INCLUDES} + echo_op.cpp +) + +target_link_libraries(echo-op Beast) diff --git a/src/beast/example/echo-op/Jamfile b/src/beast/example/echo-op/Jamfile new file mode 100644 index 0000000000..cfc32495a8 --- /dev/null +++ b/src/beast/example/echo-op/Jamfile @@ -0,0 +1,13 @@ +# +# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +# + +exe echo-op : + echo_op.cpp + : + coverage:no + ubasan:no + ; diff --git a/src/beast/example/echo-op/echo_op.cpp b/src/beast/example/echo-op/echo_op.cpp new file mode 100644 index 0000000000..150c76fc48 --- /dev/null +++ b/src/beast/example/echo-op/echo_op.cpp @@ -0,0 +1,356 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include +#include +#include +#include +#include +#include + +//[example_core_echo_op_1 + +template< + class AsyncStream, + class CompletionToken> +auto +async_echo(AsyncStream& stream, CompletionToken&& token) + +//] + -> beast::async_return_type; + +//[example_core_echo_op_2 + +/** Asynchronously read a line and echo it back. + + This function is used to asynchronously read a line ending + in a carriage-return ("CR") from the stream, and then write + it back. The function call always returns immediately. The + asynchronous operation will continue until one of the + following conditions is true: + + @li A line was read in and sent back on the stream + + @li An error occurs. + + This operation is implemented in terms of one or more calls to + the stream's `async_read_some` and `async_write_some` functions, + and is known as a composed operation. The program must + ensure that the stream performs no other operations until this + operation completes. The implementation may read additional octets + that lie past the end of the line being read. These octets are + silently discarded. + + @param The stream to operate on. The type must meet the + requirements of @b AsyncReadStream and @AsyncWriteStream + + @param token The completion token to use. If this is a + completion handler, copies will be made as required. + The equivalent signature of the handler must be: + @code + void handler( + error_code ec // result of operation + ); + @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using `boost::asio::io_service::post`. +*/ +template< + class AsyncStream, + class CompletionToken> +beast::async_return_type< /*< The [link beast.ref.beast__async_return_type `async_return_type`] customizes the return value based on the completion token >*/ + CompletionToken, + void(beast::error_code)> /*< This is the signature for the completion handler >*/ +async_echo( + AsyncStream& stream, + CompletionToken&& token); + +//] + +//[example_core_echo_op_4 + +// This composed operation reads a line of input and echoes it back. +// +template +class echo_op +{ + // This holds all of the state information required by the operation. + struct state + { + // The stream to read and write to + AsyncStream& stream; + + // Indicates what step in the operation's state machine + // to perform next, starting from zero. + int step = 0; + + // The buffer used to hold the input and output data. + // + // We use a custom allocator for performance, this allows + // the implementation of the io_service to make efficient + // re-use of memory allocated by composed operations during + // a continuation. + // + boost::asio::basic_streambuf> buffer; + + // handler_ptr requires that the first parameter to the + // contained object constructor is a reference to the + // managed final completion handler. + // + explicit state(Handler& handler, AsyncStream& stream_) + : stream(stream_) + , buffer((std::numeric_limits::max)(), + beast::handler_alloc{handler}) + { + } + }; + + // The operation's data is kept in a cheap-to-copy smart + // pointer container called `handler_ptr`. This efficiently + // satisfies the CopyConstructible requirements of completion + // handlers. + // + // `handler_ptr` uses these memory allocation hooks associated + // with the final completion handler, in order to allocate the + // storage for `state`: + // + // asio_handler_allocate + // asio_handler_deallocate + // + beast::handler_ptr p_; + +public: + // Boost.Asio requires that handlers are CopyConstructible. + // In some cases, it takes advantage of handlers that are + // MoveConstructible. This operation supports both. + // + echo_op(echo_op&&) = default; + echo_op(echo_op const&) = default; + + // The constructor simply creates our state variables in + // the smart pointer container. + // + template + echo_op(AsyncStream& stream, DeducedHandler&& handler) + : p_(std::forward(handler), stream) + { + } + + // The entry point for this handler. This will get called + // as our intermediate operations complete. Definition below. + // + void operator()(beast::error_code ec, std::size_t bytes_transferred); + + // The next four functions are required for our class + // to meet the requirements for composed operations. + // Definitions and exposition will follow. + + template + friend void asio_handler_invoke( + Function&& f, echo_op* op); + + template + friend void* asio_handler_allocate( + std::size_t size, echo_op* op); + + template + friend void asio_handler_deallocate( + void* p, std::size_t size, echo_op* op); + + template + friend bool asio_handler_is_continuation( + echo_op* op); +}; + +//] + +//[example_core_echo_op_5 + +// echo_op is callable with the signature void(error_code, bytes_transferred), +// allowing `*this` to be used as both a ReadHandler and a WriteHandler. +// +template +void echo_op:: +operator()(beast::error_code ec, std::size_t bytes_transferred) +{ + // Store a reference to our state. The address of the state won't + // change, and this solves the problem where dereferencing the + // data member is undefined after a move. + auto& p = *p_; + + // Now perform the next step in the state machine + switch(ec ? 2 : p.step) + { + // initial entry + case 0: + // read up to the first newline + p.step = 1; + return boost::asio::async_read_until(p.stream, p.buffer, "\r", std::move(*this)); + + case 1: + // write everything back + p.step = 2; + // async_read_until could have read past the newline, + // use buffer_prefix to make sure we only send one line + return boost::asio::async_write(p.stream, + beast::buffer_prefix(bytes_transferred, p.buffer.data()), std::move(*this)); + + case 2: + p.buffer.consume(bytes_transferred); + break; + } + + // Invoke the final handler. The implementation of `handler_ptr` + // will deallocate the storage for the state before the handler + // is invoked. This is necessary to provide the + // destroy-before-invocation guarantee on handler memory + // customizations. + // + // If we wanted to pass any arguments to the handler which come + // from the `state`, they would have to be moved to the stack + // first or else undefined behavior results. + // + p_.invoke(ec); + return; +} + +//] + +//[example_core_echo_op_6 + +// Handler hook forwarding. These free functions invoke the hooks +// associated with the final completion handler. In effect, they +// make the Asio implementation treat our composed operation the +// same way it would treat the final completion handler for the +// purpose of memory allocation and invocation. +// +// Our implementation just passes the call through to the hook +// associated with the final handler. The "using" statements are +// structured to permit argument dependent lookup. Always use +// `std::addressof` or its equivalent to pass the pointer to the +// handler, otherwise an unwanted overload of `operator&` may be +// called instead. + +template +void asio_handler_invoke( + Function&& f, echo_op* op) +{ + using boost::asio::asio_handler_invoke; + return asio_handler_invoke(f, std::addressof(op->p_.handler())); +} + +template +void* asio_handler_allocate( + std::size_t size, echo_op* op) +{ + using boost::asio::asio_handler_allocate; + return asio_handler_allocate(size, std::addressof(op->p_.handler())); +} + +template +void asio_handler_deallocate( + void* p, std::size_t size, echo_op* op) +{ + using boost::asio::asio_handler_deallocate; + return asio_handler_deallocate(p, size, + std::addressof(op->p_.handler())); +} + +// Determines if the next asynchronous operation represents a +// continuation of the asynchronous flow of control associated +// with the final handler. If we are past step one, it means +// we have performed an asynchronous operation therefore any +// subsequent operation would represent a continuation. +// Otherwise, we propagate the handler's associated value of +// is_continuation. Getting this right means the implementation +// may schedule the invokation of the invoked functions more +// efficiently. +// +template +bool asio_handler_is_continuation(echo_op* op) +{ + // This next call is structured to permit argument + // dependent lookup to take effect. + using boost::asio::asio_handler_is_continuation; + + // Always use std::addressof to pass the pointer to the handler, + // otherwise an unwanted overload of operator& may be called instead. + return op->p_->step > 1 || + asio_handler_is_continuation(std::addressof(op->p_.handler())); +} + +//] + +//[example_core_echo_op_3 + +template +class echo_op; + +// Read a line and echo it back +// +template +beast::async_return_type +async_echo(AsyncStream& stream, CompletionToken&& token) +{ + // Make sure stream meets the requirements. We use static_assert + // to cause a friendly message instead of an error novel. + // + static_assert(beast::is_async_stream::value, + "AsyncStream requirements not met"); + + // This helper manages some of the handler's lifetime and + // uses the result and handler specializations associated with + // the completion token to help customize the return value. + // + beast::async_completion init{token}; + + // Create the composed operation and launch it. This is a constructor + // call followed by invocation of operator(). We use handler_type + // to convert the completion token into the correct handler type, + // allowing user-defined specializations of the async_result template + // to be used. + // + echo_op>{ + stream, init.completion_handler}(beast::error_code{}, 0); + + // This hook lets the caller see a return value when appropriate. + // For example this might return std::future if + // CompletionToken is boost::asio::use_future, or this might + // return an error code if CompletionToken specifies a coroutine. + // + return init.result.get(); +} + +//] + +int main(int, char** argv) +{ + using address_type = boost::asio::ip::address; + using socket_type = boost::asio::ip::tcp::socket; + using endpoint_type = boost::asio::ip::tcp::endpoint; + + // Create a listening socket, accept a connection, perform + // the echo, and then shut everything down and exit. + boost::asio::io_service ios; + socket_type sock{ios}; + boost::asio::ip::tcp::acceptor acceptor{ios}; + endpoint_type ep{address_type::from_string("0.0.0.0"), 0}; + acceptor.open(ep.protocol()); + acceptor.bind(ep); + acceptor.listen(); + acceptor.accept(sock); + async_echo(sock, + [&](beast::error_code ec) + { + if(ec) + std::cerr << argv[0] << ": " << ec.message() << std::endl; + }); + ios.run(); + return 0; +} diff --git a/src/beast/example/http-client-ssl/CMakeLists.txt b/src/beast/example/http-client-ssl/CMakeLists.txt new file mode 100644 index 0000000000..3abb8b8d5c --- /dev/null +++ b/src/beast/example/http-client-ssl/CMakeLists.txt @@ -0,0 +1,16 @@ +# Part of Beast + +GroupSources(include/beast beast) +GroupSources(example/common common) +GroupSources(example/http-client-ssl "/") + +add_executable (http-client-ssl + ${BEAST_INCLUDES} + ${COMMON_INCLUDES} + http_client_ssl.cpp +) + +target_link_libraries(http-client-ssl + Beast + ${OPENSSL_LIBRARIES} + ) diff --git a/src/beast/examples/ssl/Jamfile.v2 b/src/beast/example/http-client-ssl/Jamfile similarity index 84% rename from src/beast/examples/ssl/Jamfile.v2 rename to src/beast/example/http-client-ssl/Jamfile index 45ab791f90..d322b946a6 100644 --- a/src/beast/examples/ssl/Jamfile.v2 +++ b/src/beast/example/http-client-ssl/Jamfile @@ -43,12 +43,9 @@ project crypto ; -exe http-ssl-example - : - http_ssl_example.cpp - ; - -exe websocket-ssl-example - : - websocket_ssl_example.cpp - ; +exe http-client-ssl : + http_client_ssl.cpp + : + coverage:no + ubasan:no + ; diff --git a/src/beast/example/http-client-ssl/http_client_ssl.cpp b/src/beast/example/http-client-ssl/http_client_ssl.cpp new file mode 100644 index 0000000000..046b21fcf7 --- /dev/null +++ b/src/beast/example/http-client-ssl/http_client_ssl.cpp @@ -0,0 +1,104 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include "../common/root_certificates.hpp" + +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace ssl = boost::asio::ssl; // from +namespace http = beast::http; // from + +int main() +{ + // A helper for reporting errors + auto const fail = + [](std::string what, beast::error_code ec) + { + std::cerr << what << ": " << ec.message() << std::endl; + std::cerr.flush(); + return EXIT_FAILURE; + }; + + boost::system::error_code ec; + + // Normal boost::asio setup + boost::asio::io_service ios; + tcp::resolver r{ios}; + tcp::socket sock{ios}; + + // Look up the domain name + std::string const host = "www.example.com"; + auto const lookup = r.resolve({host, "https"}, ec); + if(ec) + return fail("resolve", ec); + + // Make the connection on the IP address we get from a lookup + boost::asio::connect(sock, lookup, ec); + if(ec) + return fail("connect", ec); + + // Create the required ssl context + ssl::context ctx{ssl::context::sslv23_client}; + + // This holds the root certificate used for verification + load_root_certificates(ctx, ec); + if(ec) + return fail("certificate", ec); + + // Wrap the now-connected socket in an SSL stream + ssl::stream stream{sock, ctx}; + stream.set_verify_mode(ssl::verify_peer | ssl::verify_fail_if_no_peer_cert); + + // Perform SSL handshaking + stream.handshake(ssl::stream_base::client, ec); + if(ec) + return fail("handshake", ec); + + // Set up an HTTP GET request message + http::request req; + req.method(http::verb::get); + req.target("/"); + req.version = 11; + req.set(http::field::host, host + ":" + + std::to_string(sock.remote_endpoint().port())); + req.set(http::field::user_agent, BEAST_VERSION_STRING); + req.prepare_payload(); + + // Write the HTTP request to the remote host + http::write(stream, req, ec); + if(ec) + return fail("write", ec); + + // This buffer is used for reading and must be persisted + beast::flat_buffer b; + + // Declare a container to hold the response + http::response res; + + // Read the response + http::read(stream, b, res, ec); + if(ec) + return fail("read", ec); + + // Write the message to standard out + std::cout << res << std::endl; + + // Shut down SSL on the stream + stream.shutdown(ec); + if(ec && ec != boost::asio::error::eof) + fail("ssl_shutdown ", ec); + + // If we get here then the connection is closed gracefully + return EXIT_SUCCESS; +} diff --git a/src/beast/example/http-client/CMakeLists.txt b/src/beast/example/http-client/CMakeLists.txt new file mode 100644 index 0000000000..59044b4744 --- /dev/null +++ b/src/beast/example/http-client/CMakeLists.txt @@ -0,0 +1,13 @@ +# Part of Beast + +GroupSources(include/beast beast) + +GroupSources(example/http-client "/") + +add_executable (http-client + ${BEAST_INCLUDES} + ${EXTRAS_INCLUDES} + http_client.cpp +) + +target_link_libraries(http-client Beast) diff --git a/src/beast/example/http-client/Jamfile b/src/beast/example/http-client/Jamfile new file mode 100644 index 0000000000..a39360eaed --- /dev/null +++ b/src/beast/example/http-client/Jamfile @@ -0,0 +1,13 @@ +# +# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +# + +exe http-client : + http_client.cpp + : + coverage:no + ubasan:no + ; diff --git a/src/beast/example/http-client/http_client.cpp b/src/beast/example/http-client/http_client.cpp new file mode 100644 index 0000000000..ff4b2039f5 --- /dev/null +++ b/src/beast/example/http-client/http_client.cpp @@ -0,0 +1,88 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +//[example_http_client + +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace http = beast::http; // from + +int main() +{ + // A helper for reporting errors + auto const fail = + [](std::string what, beast::error_code ec) + { + std::cerr << what << ": " << ec.message() << std::endl; + return EXIT_FAILURE; + }; + + beast::error_code ec; + + // Set up an asio socket + boost::asio::io_service ios; + tcp::resolver r{ios}; + tcp::socket sock{ios}; + + // Look up the domain name + std::string const host = "www.example.com"; + auto const lookup = r.resolve({host, "http"}, ec); + if(ec) + return fail("resolve", ec); + + // Make the connection on the IP address we get from a lookup + boost::asio::connect(sock, lookup, ec); + if(ec) + return fail("connect", ec); + + // Set up an HTTP GET request message + http::request req{http::verb::get, "/", 11}; + req.set(http::field::host, host + ":" + + std::to_string(sock.remote_endpoint().port())); + req.set(http::field::user_agent, BEAST_VERSION_STRING); + req.prepare_payload(); + + // Write the HTTP request to the remote host + http::write(sock, req, ec); + if(ec) + return fail("write", ec); + + // This buffer is used for reading and must be persisted + beast::flat_buffer b; + + // Declare a container to hold the response + http::response res; + + // Read the response + http::read(sock, b, res, ec); + if(ec) + return fail("read", ec); + + // Write the message to standard out + std::cout << res << std::endl; + + // Gracefully close the socket + sock.shutdown(tcp::socket::shutdown_both, ec); + + // not_connected happens sometimes + // so don't bother reporting it. + // + if(ec && ec != beast::errc::not_connected) + return fail("shutdown", ec); + + // If we get here then the connection is closed gracefully + return EXIT_SUCCESS; +} + +//] diff --git a/src/beast/example/http-crawl/CMakeLists.txt b/src/beast/example/http-crawl/CMakeLists.txt new file mode 100644 index 0000000000..477f699c87 --- /dev/null +++ b/src/beast/example/http-crawl/CMakeLists.txt @@ -0,0 +1,15 @@ +# Part of Beast + +GroupSources(include/beast beast) + +GroupSources(example/http-crawl "/") + +add_executable (http-crawl + ${BEAST_INCLUDES} + ${EXTRAS_INCLUDES} + urls_large_data.hpp + urls_large_data.cpp + http_crawl.cpp +) + +target_link_libraries(http-crawl Beast) diff --git a/src/beast/examples/Jamfile.v2 b/src/beast/example/http-crawl/Jamfile similarity index 59% rename from src/beast/examples/Jamfile.v2 rename to src/beast/example/http-crawl/Jamfile index c80330b610..180be9ec07 100644 --- a/src/beast/examples/Jamfile.v2 +++ b/src/beast/example/http-crawl/Jamfile @@ -8,20 +8,7 @@ exe http-crawl : http_crawl.cpp urls_large_data.cpp - ; - -exe http-server : - http_server.cpp - ; - -exe http-example : - http_example.cpp - ; - -exe websocket-echo : - websocket_echo.cpp - ; - -exe websocket-example : - websocket_example.cpp + : + coverage:no + ubasan:no ; diff --git a/src/beast/example/http-crawl/http_crawl.cpp b/src/beast/example/http-crawl/http_crawl.cpp new file mode 100644 index 0000000000..03ae517277 --- /dev/null +++ b/src/beast/example/http-crawl/http_crawl.cpp @@ -0,0 +1,137 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include "urls_large_data.hpp" + +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace http = beast::http; // from + +template +void +err(beast::error_code const& ec, String const& what) +{ + std::cerr << what << ": " << ec.message() << std::endl; +} + +/* This simple program just visits a list with a few + thousand domain names and tries to retrieve and print + the home page of each site. +*/ +int +main(int, char const*[]) +{ + // A helper for reporting errors + auto const fail = + [](std::string what, beast::error_code ec) + { + std::cerr << what << ": " << ec.message() << std::endl; + std::cerr.flush(); + return EXIT_FAILURE; + }; + + // Obligatory Asio variable + boost::asio::io_service ios; + + // Loop over all the URLs + for(auto const& host : urls_large_data()) + { + beast::error_code ec; + + // Look up the domain name + tcp::resolver r(ios); + auto lookup = r.resolve({host, "http"}, ec); + if(ec) + { + fail("resolve", ec); + continue; + } + + // Now create a socket and connect + tcp::socket sock(ios); + boost::asio::connect(sock, lookup, ec); + if(ec) + { + fail("connect", ec); + continue; + } + + // Grab the remote endpoint + auto ep = sock.remote_endpoint(ec); + if(ec) + { + fail("remote_endpoint", ec); + continue; + } + + // Set up an HTTP GET request + http::request req{http::verb::get, "/", 11}; + req.set(http::field::host, host + std::string(":") + std::to_string(ep.port())); + req.set(http::field::user_agent, BEAST_VERSION_STRING); + + // Set the Connection: close field, this way the server will close + // the connection. This consumes less resources (no TIME_WAIT) because + // of the graceful close. It also makes things go a little faster. + // + req.set(http::field::connection, "close"); + + // Send the GET request + http::write(sock, req, ec); + if(ec == http::error::end_of_stream) + { + // This special error received on a write indicates that the + // semantics of the sent message are such that the connection + // should be closed after the response is done. We do a TCP/IP + // "half-close" here to shut down our end. + // + sock.shutdown(tcp::socket::shutdown_send, ec); + if(ec && ec != beast::errc::not_connected) + return fail("shutdown", ec); + } + if(ec) + { + fail("write", ec); + continue; + } + + // This buffer is needed for reading + beast::multi_buffer b; + + // The response will go into this object + http::response res; + + // Read the response + http::read(sock, b, res, ec); + if(ec == http::error::end_of_stream) + { + // This special error means that the other end closed the socket, + // which is what we want since we asked for Connection: close. + // However, we are going through a rather large number of servers + // and sometimes they misbehave. + ec = {}; + } + else if(ec) + { + fail("read", ec); + continue; + } + + // Now we do the other half of the close, + // which is to shut down the receiver. + sock.shutdown(tcp::socket::shutdown_receive, ec); + if(ec && ec != beast::errc::not_connected) + return fail("shutdown", ec); + + std::cout << res << std::endl; + } +} diff --git a/src/beast/examples/urls_large_data.cpp b/src/beast/example/http-crawl/urls_large_data.cpp similarity index 100% rename from src/beast/examples/urls_large_data.cpp rename to src/beast/example/http-crawl/urls_large_data.cpp diff --git a/src/beast/examples/urls_large_data.hpp b/src/beast/example/http-crawl/urls_large_data.hpp similarity index 75% rename from src/beast/examples/urls_large_data.hpp rename to src/beast/example/http-crawl/urls_large_data.hpp index 74147a8a28..da4d8eb533 100644 --- a/src/beast/examples/urls_large_data.hpp +++ b/src/beast/example/http-crawl/urls_large_data.hpp @@ -5,8 +5,8 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef URLS_LARGE_DATA_H_INCLUDED -#define URLS_LARGE_DATA_H_INCLUDED +#ifndef BEAST_EXAMPLE_HTTP_CRAWL_URLS_LARGE_DATA_HPP +#define BEAST_EXAMPLE_HTTP_CRAWL_URLS_LARGE_DATA_HPP #include diff --git a/src/beast/example/http-server-fast/CMakeLists.txt b/src/beast/example/http-server-fast/CMakeLists.txt new file mode 100644 index 0000000000..5668ec1bd2 --- /dev/null +++ b/src/beast/example/http-server-fast/CMakeLists.txt @@ -0,0 +1,18 @@ +# Part of Beast + +GroupSources(include/beast beast) +GroupSources(example/common common) +GroupSources(example/http-server-fast "/") + +add_executable (http-server-fast + ${BEAST_INCLUDES} + ${COMMON_INCLUDES} + fields_alloc.hpp + http_server_fast.cpp +) + +target_link_libraries(http-server-fast + Beast + ${Boost_FILESYSTEM_LIBRARY} + ) + diff --git a/src/beast/example/http-server-fast/Jamfile b/src/beast/example/http-server-fast/Jamfile new file mode 100644 index 0000000000..3285b7ac17 --- /dev/null +++ b/src/beast/example/http-server-fast/Jamfile @@ -0,0 +1,13 @@ +# +# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +# + +exe http-server-fast : + http_server_fast.cpp + : + coverage:no + ubasan:no + ; diff --git a/src/beast/example/http-server-fast/fields_alloc.hpp b/src/beast/example/http-server-fast/fields_alloc.hpp new file mode 100644 index 0000000000..7550ccb00d --- /dev/null +++ b/src/beast/example/http-server-fast/fields_alloc.hpp @@ -0,0 +1,195 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_EXAMPLE_FIELDS_ALLOC_HPP +#define BEAST_EXAMPLE_FIELDS_ALLOC_HPP + +#include +#include +#include +#include + +namespace detail { + +struct static_pool +{ + std::size_t size_; + std::size_t refs_ = 1; + std::size_t count_ = 0; + char* p_; + + char* + end() + { + return reinterpret_cast(this+1) + size_; + } + + explicit + static_pool(std::size_t size) + : size_(size) + , p_(reinterpret_cast(this+1)) + { + } + +public: + static + static_pool& + construct(std::size_t size) + { + auto p = new char[sizeof(static_pool) + size]; + return *(new(p) static_pool{size}); + } + + static_pool& + share() + { + ++refs_; + return *this; + } + + void + destroy() + { + if(refs_--) + return; + this->~static_pool(); + delete[] reinterpret_cast(this); + } + + void* + alloc(std::size_t n) + { + auto last = p_ + n; + if(last >= end()) + BOOST_THROW_EXCEPTION(std::bad_alloc{}); + ++count_; + auto p = p_; + p_ = last; + return p; + } + + void + dealloc() + { + if(--count_) + return; + p_ = reinterpret_cast(this+1); + } +}; + +} // detail + +/** A non-thread-safe allocator optimized for @ref basic_fields. + + This allocator obtains memory from a pre-allocated memory block + of a given size. It does nothing in deallocate until all + previously allocated blocks are deallocated, upon which it + resets the internal memory block for re-use. + + To use this allocator declare an instance persistent to the + connection or session, and construct with the block size. + A good rule of thumb is 20% more than the maximum allowed + header size. For example if the application only allows up + to an 8,000 byte header, the block size could be 9,600. + + Then, for every instance of `message` construct the header + with a copy of the previously declared allocator instance. +*/ +template +struct fields_alloc +{ + detail::static_pool& pool_; + +public: + using value_type = T; + using is_always_equal = std::false_type; + using pointer = T*; + using reference = T&; + using const_pointer = T const*; + using const_reference = T const&; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + template + struct rebind + { + using other = fields_alloc; + }; + + explicit + fields_alloc(std::size_t size) + : pool_(detail::static_pool::construct(size)) + { + } + + fields_alloc(fields_alloc const& other) + : pool_(other.pool_.share()) + { + } + + template + fields_alloc(fields_alloc const& other) + : pool_(other.pool_.share()) + { + } + + ~fields_alloc() + { + pool_.destroy(); + } + + value_type* + allocate(size_type n) + { + return static_cast( + pool_.alloc(n * sizeof(T))); + } + + void + deallocate(value_type*, size_type) + { + pool_.dealloc(); + } + +#if defined(BOOST_LIBSTDCXX_VERSION) && BOOST_LIBSTDCXX_VERSION < 60000 + template + void + construct(U* ptr, Args&&... args) + { + ::new((void*)ptr) U(std::forward(args)...); + } + + template + void + destroy(U* ptr) + { + ptr->~U(); + } +#endif + + template + friend + bool + operator==( + fields_alloc const& lhs, + fields_alloc const& rhs) + { + return &lhs.pool_ == &rhs.pool_; + } + + template + friend + bool + operator!=( + fields_alloc const& lhs, + fields_alloc const& rhs) + { + return ! (lhs == rhs); + } +}; + +#endif diff --git a/src/beast/example/http-server-fast/http_server_fast.cpp b/src/beast/example/http-server-fast/http_server_fast.cpp new file mode 100644 index 0000000000..bdcea2843f --- /dev/null +++ b/src/beast/example/http-server-fast/http_server_fast.cpp @@ -0,0 +1,310 @@ +// +// Copyright (c) 2017 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include "fields_alloc.hpp" + +#include "../common/mime_types.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ip = boost::asio::ip; // from +using tcp = boost::asio::ip::tcp; // from +namespace http = beast::http; // from + +class http_worker +{ +public: + http_worker(http_worker const&) = delete; + http_worker& operator=(http_worker const&) = delete; + + http_worker(tcp::acceptor& acceptor, const std::string& doc_root) : + acceptor_(acceptor), + doc_root_(doc_root) + { + } + + void start() + { + accept(); + check_deadline(); + } + +private: + using alloc_t = fields_alloc; + using request_body_t = http::basic_dynamic_body>; + + // The acceptor used to listen for incoming connections. + tcp::acceptor& acceptor_; + + // The path to the root of the document directory. + std::string doc_root_; + + // The socket for the currently connected client. + tcp::socket socket_{acceptor_.get_io_service()}; + + // The buffer for performing reads + beast::static_buffer_n<8192> buffer_; + + // The allocator used for the fields in the request and reply. + alloc_t alloc_{8192}; + + // The parser for reading the requests + boost::optional> parser_; + + // The timer putting a time limit on requests. + boost::asio::basic_waitable_timer request_deadline_{ + acceptor_.get_io_service(), (std::chrono::steady_clock::time_point::max)()}; + + // The string-based response message. + boost::optional>> string_response_; + + // The string-based response serializer. + boost::optional>> string_serializer_; + + // The file-based response message. + boost::optional>> file_response_; + + // The file-based response serializer. + boost::optional>> file_serializer_; + + void accept() + { + // Clean up any previous connection. + beast::error_code ec; + socket_.close(ec); + buffer_.consume(buffer_.size()); + + acceptor_.async_accept( + socket_, + [this](beast::error_code ec) + { + if (ec) + { + accept(); + } + else + { + // Request must be fully processed within 60 seconds. + request_deadline_.expires_from_now( + std::chrono::seconds(60)); + + read_request(); + } + }); + } + + void read_request() + { + // On each read the parser needs to be destroyed and + // recreated. We store it in a boost::optional to + // achieve that. + // + // Arguments passed to the parser constructor are + // forwarded to the message object. A single argument + // is forwarded to the body constructor. + // + // We construct the dynamic body with a 1MB limit + // to prevent vulnerability to buffer attacks. + // + parser_.emplace( + std::piecewise_construct, + std::make_tuple(), + std::make_tuple(alloc_)); + + http::async_read( + socket_, + buffer_, + *parser_, + [this](beast::error_code ec) + { + if (ec) + accept(); + else + process_request(parser_->get()); + }); + } + + void process_request(http::request> const& req) + { + switch (req.method()) + { + case http::verb::get: + send_file(req.target()); + break; + + default: + // We return responses indicating an error if + // we do not recognize the request method. + send_bad_response( + http::status::bad_request, + "Invalid request-method '" + req.method_string().to_string() + "'\r\n"); + break; + } + } + + void send_bad_response( + http::status status, + std::string const& error) + { + string_response_.emplace( + std::piecewise_construct, + std::make_tuple(), + std::make_tuple(alloc_)); + + string_response_->result(status); + string_response_->set(http::field::server, "Beast"); + string_response_->set(http::field::connection, "close"); + string_response_->set(http::field::content_type, "text/plain"); + string_response_->body = error; + string_response_->prepare_payload(); + + string_serializer_.emplace(*string_response_); + + http::async_write( + socket_, + *string_serializer_, + [this](beast::error_code ec) + { + socket_.shutdown(tcp::socket::shutdown_send, ec); + string_serializer_.reset(); + string_response_.reset(); + accept(); + }); + } + + void send_file(beast::string_view target) + { + // Request path must be absolute and not contain "..". + if (target.empty() || target[0] != '/' || target.find("..") != std::string::npos) + { + send_bad_response( + http::status::not_found, + "File not found\r\n"); + return; + } + + std::string full_path = doc_root_; + full_path.append( + target.data(), + target.size()); + + http::file_body::value_type file; + beast::error_code ec; + file.open( + full_path.c_str(), + beast::file_mode::read, + ec); + if(ec) + { + send_bad_response( + http::status::not_found, + "File not found\r\n"); + return; + } + + file_response_.emplace( + std::piecewise_construct, + std::make_tuple(), + std::make_tuple(alloc_)); + + file_response_->result(http::status::ok); + file_response_->set(http::field::server, "Beast"); + file_response_->set(http::field::connection, "close"); + file_response_->set(http::field::content_type, mime_type(target.to_string())); + file_response_->body = std::move(file); + file_response_->prepare_payload(); + + file_serializer_.emplace(*file_response_); + + http::async_write( + socket_, + *file_serializer_, + [this](beast::error_code ec) + { + socket_.shutdown(tcp::socket::shutdown_send, ec); + file_serializer_.reset(); + file_response_.reset(); + accept(); + }); + } + + void check_deadline() + { + // The deadline may have moved, so check it has really passed. + if (request_deadline_.expires_at() <= std::chrono::steady_clock::now()) + { + // Close socket to cancel any outstanding operation. + beast::error_code ec; + socket_.close(); + + // Sleep indefinitely until we're given a new deadline. + request_deadline_.expires_at( + std::chrono::steady_clock::time_point::max()); + } + + request_deadline_.async_wait( + [this](beast::error_code) + { + check_deadline(); + }); + } +}; + +int main(int argc, char* argv[]) +{ + try + { + // Check command line arguments. + if (argc != 6) + { + std::cerr << "Usage: http_server_fast
{spin|block}\n"; + std::cerr << " For IPv4, try:\n"; + std::cerr << " http_server_fast 0.0.0.0 80 . 100 block\n"; + std::cerr << " For IPv6, try:\n"; + std::cerr << " http_server_fast 0::0 80 . 100 block\n"; + return EXIT_FAILURE; + } + + auto address = ip::address::from_string(argv[1]); + unsigned short port = static_cast(std::atoi(argv[2])); + std::string doc_root = argv[3]; + int num_workers = std::atoi(argv[4]); + bool spin = (std::strcmp(argv[5], "spin") == 0); + + boost::asio::io_service ios{1}; + tcp::acceptor acceptor{ios, {address, port}}; + + std::list workers; + for (int i = 0; i < num_workers; ++i) + { + workers.emplace_back(acceptor, doc_root); + workers.back().start(); + } + + if (spin) + for (;;) ios.poll(); + else + ios.run(); + } + catch (const std::exception& e) + { + std::cerr << "Exception: " << e.what() << std::endl; + return EXIT_FAILURE; + } +} diff --git a/src/beast/example/http-server-small/CMakeLists.txt b/src/beast/example/http-server-small/CMakeLists.txt new file mode 100644 index 0000000000..dfb1ade71c --- /dev/null +++ b/src/beast/example/http-server-small/CMakeLists.txt @@ -0,0 +1,15 @@ +# Part of Beast + +GroupSources(include/beast beast) + +GroupSources(example/http-server-small "/") + +add_executable (http-server-small + ${BEAST_INCLUDES} + http_server_small.cpp +) + +target_link_libraries(http-server-small + Beast + ) + diff --git a/src/beast/example/http-server-small/Jamfile b/src/beast/example/http-server-small/Jamfile new file mode 100644 index 0000000000..2acfe2cb11 --- /dev/null +++ b/src/beast/example/http-server-small/Jamfile @@ -0,0 +1,13 @@ +# +# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +# + +exe http-server-small : + http_server_small.cpp + : + coverage:no + ubasan:no + ; diff --git a/src/beast/example/http-server-small/http_server_small.cpp b/src/beast/example/http-server-small/http_server_small.cpp new file mode 100644 index 0000000000..f73c7734b0 --- /dev/null +++ b/src/beast/example/http-server-small/http_server_small.cpp @@ -0,0 +1,240 @@ +// +// Copyright (c) 2017 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ip = boost::asio::ip; // from +using tcp = boost::asio::ip::tcp; // from +namespace http = beast::http; // from + +namespace my_program_state +{ + std::size_t + request_count() + { + static std::size_t count = 0; + return ++count; + } + + std::time_t + now() + { + return std::time(0); + } +} + +class http_connection : public std::enable_shared_from_this +{ +public: + http_connection(tcp::socket socket) + : socket_(std::move(socket)) + { + } + + // Initiate the asynchronous operations associated with the connection. + void + start() + { + read_request(); + check_deadline(); + } + +private: + // The socket for the currently connected client. + tcp::socket socket_; + + // The buffer for performing reads. + beast::flat_buffer buffer_{8192}; + + // The request message. + http::request request_; + + // The response message. + http::response response_; + + // The timer for putting a deadline on connection processing. + boost::asio::basic_waitable_timer deadline_{ + socket_.get_io_service(), std::chrono::seconds(60)}; + + // Asynchronously receive a complete request message. + void + read_request() + { + auto self = shared_from_this(); + + http::async_read( + socket_, + buffer_, + request_, + [self](beast::error_code ec) + { + if(!ec) + self->process_request(); + }); + } + + // Determine what needs to be done with the request message. + void + process_request() + { + response_.version = 11; + response_.set(http::field::connection, "close"); + + switch(request_.method()) + { + case http::verb::get: + response_.result(http::status::ok); + response_.set(http::field::server, "Beast"); + create_response(); + break; + + default: + // We return responses indicating an error if + // we do not recognize the request method. + response_.result(http::status::bad_request); + response_.set(http::field::content_type, "text/plain"); + beast::ostream(response_.body) + << "Invalid request-method '" + << request_.method_string().to_string() + << "'"; + break; + } + + write_response(); + } + + // Construct a response message based on the program state. + void + create_response() + { + if(request_.target() == "/count") + { + response_.set(http::field::content_type, "text/html"); + beast::ostream(response_.body) + << "\n" + << "Request count\n" + << "\n" + << "

Request count

\n" + << "

There have been " + << my_program_state::request_count() + << " requests so far.

\n" + << "\n" + << "\n"; + } + else if(request_.target() == "/time") + { + response_.set(http::field::content_type, "text/html"); + beast::ostream(response_.body) + << "\n" + << "Current time\n" + << "\n" + << "

Current time

\n" + << "

The current time is " + << my_program_state::now() + << " seconds since the epoch.

\n" + << "\n" + << "\n"; + } + else + { + response_.result(http::status::not_found); + response_.set(http::field::content_type, "text/plain"); + beast::ostream(response_.body) << "File not found\r\n"; + } + } + + // Asynchronously transmit the response message. + void + write_response() + { + auto self = shared_from_this(); + + response_.set(http::field::content_length, response_.body.size()); + + http::async_write( + socket_, + response_, + [self](beast::error_code ec) + { + self->socket_.shutdown(tcp::socket::shutdown_send, ec); + self->deadline_.cancel(); + }); + } + + // Check whether we have spent enough time on this connection. + void + check_deadline() + { + auto self = shared_from_this(); + + deadline_.async_wait( + [self](beast::error_code ec) + { + if(!ec) + { + // Close socket to cancel any outstanding operation. + self->socket_.close(ec); + } + }); + } +}; + +// "Loop" forever accepting new connections. +void +http_server(tcp::acceptor& acceptor, tcp::socket& socket) +{ + acceptor.async_accept(socket, + [&](beast::error_code ec) + { + if(!ec) + std::make_shared(std::move(socket))->start(); + http_server(acceptor, socket); + }); +} + +int +main(int argc, char* argv[]) +{ + try + { + // Check command line arguments. + if(argc != 3) + { + std::cerr << "Usage: " << argv[0] << "
\n"; + std::cerr << " For IPv4, try:\n"; + std::cerr << " receiver 0.0.0.0 80\n"; + std::cerr << " For IPv6, try:\n"; + std::cerr << " receiver 0::0 80\n"; + return EXIT_FAILURE; + } + + auto address = ip::address::from_string(argv[1]); + unsigned short port = static_cast(std::atoi(argv[2])); + + boost::asio::io_service ios{1}; + + tcp::acceptor acceptor{ios, {address, port}}; + tcp::socket socket{ios}; + http_server(acceptor, socket); + + ios.run(); + } + catch(std::exception const& e) + { + std::cerr << "Exception: " << e.what() << std::endl; + return EXIT_FAILURE; + } +} diff --git a/src/beast/example/http-server-threaded/CMakeLists.txt b/src/beast/example/http-server-threaded/CMakeLists.txt new file mode 100644 index 0000000000..a11dbfa055 --- /dev/null +++ b/src/beast/example/http-server-threaded/CMakeLists.txt @@ -0,0 +1,17 @@ +# Part of Beast + +GroupSources(include/beast beast) +GroupSources(example/common common) +GroupSources(example/http-server-threaded "/") + +add_executable (http-server-threaded + ${BEAST_INCLUDES} + ${COMMON_INCLUDES} + http_server_threaded.cpp +) + +target_link_libraries(http-server-threaded + Beast + ${Boost_FILESYSTEM_LIBRARY} + ) + diff --git a/src/beast/example/http-server-threaded/Jamfile b/src/beast/example/http-server-threaded/Jamfile new file mode 100644 index 0000000000..f2f5f16e54 --- /dev/null +++ b/src/beast/example/http-server-threaded/Jamfile @@ -0,0 +1,13 @@ +# +# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +# + +exe http-server-threaded : + http_server_threaded.cpp + : + coverage:no + ubasan:no + ; diff --git a/src/beast/example/http-server-threaded/http_server_threaded.cpp b/src/beast/example/http-server-threaded/http_server_threaded.cpp new file mode 100644 index 0000000000..e75b3796b6 --- /dev/null +++ b/src/beast/example/http-server-threaded/http_server_threaded.cpp @@ -0,0 +1,227 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include "../common/mime_types.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//------------------------------------------------------------------------------ +// +// Example: HTTP server, synchronous, one thread per connection +// +//------------------------------------------------------------------------------ + +namespace ip = boost::asio::ip; // from +using tcp = boost::asio::ip::tcp; // from +namespace http = beast::http; // from + +class connection + : public std::enable_shared_from_this +{ + tcp::socket sock_; + beast::string_view root_; + +public: + explicit + connection(tcp::socket&& sock, beast::string_view root) + : sock_(std::move(sock)) + , root_(root) + { + } + + void + run() + { + // Bind a shared_ptr to *this into the thread. + // When the thread exits, the connection object + // will be destroyed. + // + std::thread{&connection::do_run, shared_from_this()}.detach(); + } + +private: + // Send a client error response + http::response> + client_error(http::status result, beast::string_view text) + { + http::response> res{result, 11}; + res.set(http::field::server, BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/plain"); + res.set(http::field::connection, "close"); + res.body = text; + res.prepare_payload(); + return res; + } + + // Return an HTTP Not Found response + // + http::response + not_found() const + { + http::response res{http::status::not_found, 11}; + res.set(http::field::server, BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.set(http::field::connection, "close"); + res.body = "The file was not found"; + res.prepare_payload(); + return res; + } + + // Return an HTTP Server Error + // + http::response + server_error(beast::error_code const& ec) const + { + http::response res{http::status::internal_server_error, 11}; + res.set(http::field::server, BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.set(http::field::connection, "close"); + res.body = "Error: " + ec.message(); + res.prepare_payload(); + return res; + } + + // Return a file response to an HTTP GET request + // + http::response + get(boost::filesystem::path const& full_path, + beast::error_code& ec) const + { + http::response res; + res.set(http::field::server, BEAST_VERSION_STRING); + res.set(http::field::content_type, mime_type(full_path)); + res.set(http::field::connection, "close"); + res.body.open(full_path.string().c_str(), beast::file_mode::scan, ec); + if(ec) + return res; + res.set(http::field::content_length, res.body.size()); + return res; + } + + // Handle a request + template + void + do_request(http::request const& req, beast::error_code& ec) + { + // verb must be get + if(req.method() != http::verb::get) + { + http::write(sock_, client_error(http::status::bad_request, "Unsupported method"), ec); + return; + } + + // Request path must be absolute and not contain "..". + if( req.target().empty() || + req.target()[0] != '/' || + req.target().find("..") != std::string::npos) + { + http::write(sock_, client_error(http::status::not_found, "File not found"), ec); + return; + } + + auto full_path = root_.to_string(); + full_path.append(req.target().data(), req.target().size()); + + beast::error_code file_ec; + auto res = get(full_path, file_ec); + + if(file_ec == beast::errc::no_such_file_or_directory) + { + http::write(sock_, not_found(), ec); + } + else if(ec) + { + http::write(sock_, server_error(file_ec), ec); + } + else + { + http::serializer sr{res}; + http::write(sock_, sr, ec); + } + } + + void + do_run() + { + try + { + beast::error_code ec; + beast::flat_buffer buffer; + for(;;) + { + http::request_parser parser; + parser.header_limit(8192); + parser.body_limit(1024 * 1024); + http::read(sock_, buffer, parser, ec); + if(ec == http::error::end_of_stream) + break; + if(ec) + throw beast::system_error{ec}; + do_request(parser.get(), ec); + if(ec) + { + if(ec != http::error::end_of_stream) + throw beast::system_error{ec}; + break; + } + } + sock_.shutdown(tcp::socket::shutdown_both, ec); + if(ec && ec != boost::asio::error::not_connected) + throw beast::system_error{ec}; + } + catch (const std::exception& e) + { + std::cerr << "Exception: " << e.what() << std::endl; + } + } +}; + +int main(int argc, char* argv[]) +{ + try + { + // Check command line arguments. + if (argc != 4) + { + std::cerr << "Usage: http_server
\n"; + std::cerr << " For IPv4, try:\n"; + std::cerr << " receiver 0.0.0.0 80 .\n"; + std::cerr << " For IPv6, try:\n"; + std::cerr << " receiver 0::0 80 .\n"; + return EXIT_FAILURE; + } + + auto address = ip::address::from_string(argv[1]); + unsigned short port = static_cast(std::atoi(argv[2])); + std::string doc_root = argv[3]; + + boost::asio::io_service ios{1}; + tcp::acceptor acceptor{ios, {address, port}}; + for(;;) + { + tcp::socket sock{ios}; + acceptor.accept(sock); + std::make_shared(std::move(sock), doc_root)->run(); + } + } + catch (const std::exception& e) + { + std::cerr << "Exception: " << e.what() << std::endl; + return EXIT_FAILURE; + } +} diff --git a/src/beast/example/server-framework/CMakeLists.txt b/src/beast/example/server-framework/CMakeLists.txt new file mode 100644 index 0000000000..c111c3529b --- /dev/null +++ b/src/beast/example/server-framework/CMakeLists.txt @@ -0,0 +1,27 @@ +# Part of Beast + +GroupSources(include/beast beast) + +GroupSources(example/server-framework "/") +GroupSources(example/common "common") + +file(GLOB_RECURSE SERVER_INCLUDES + ${PROJECT_SOURCE_DIR}/example/server-framework/*.hpp + ) + +add_executable (server-framework + ${BEAST_INCLUDES} + ${COMMON_INCLUDES} + ${SERVER_INCLUDES} + main.cpp +) + +target_link_libraries( + server-framework + Beast + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY}) + +if (OPENSSL_FOUND) + target_link_libraries(server-framework ${OPENSSL_LIBRARIES}) +endif() diff --git a/src/beast/example/server-framework/Jamfile b/src/beast/example/server-framework/Jamfile new file mode 100644 index 0000000000..80d0406ba9 --- /dev/null +++ b/src/beast/example/server-framework/Jamfile @@ -0,0 +1,13 @@ +# +# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +# + +exe server-framework : + main.cpp + : + coverage:no + ubasan:no + ; diff --git a/src/beast/example/server-framework/README.md b/src/beast/example/server-framework/README.md new file mode 100644 index 0000000000..25bdc41151 --- /dev/null +++ b/src/beast/example/server-framework/README.md @@ -0,0 +1,159 @@ +Beast + +# HTTP and WebSocket built on Boost.Asio in C++11 + +## Server-Framework + +This example is a complete, multi-threaded server built with Beast. +It contains the following components + +* WebSocket ports (synchronous and asynchronous) + - Echoes back any message received + - Plain or SSL (if OpenSSL available) + +* HTTP ports (synchronous and asynchronous) + - Serves files from a configurable directory on GET request + - Responds to HEAD requests with the appropriate result + - Routes WebSocket Upgrade requests to a WebSocket port + - Handles Expect: 100-continue + - Supports pipelined requests + - Plain or SSL (if OpenSSL available) + +* Multi-Port: Plain, OpenSSL, HTTP, WebSocket **All on the same port!** + +The server is designed to use modular components that users may simply copy +into their own project to get started quickly. Two concepts are introduced: + +## PortHandler + +The **PortHandler** concept defines an algorithm for handling incoming +connections received on a listening socket. The example comes with a +total of *nine* port handlers! + +| Type | Plain | SSL | +| ----- | ----------------- | ------------------ | +| Sync | `http_sync_port` | `https_sync_port` | +| | `ws_sync_port` | `wss_sync_port` | +| Async | `http_async_port` | `https_async_port` | +| | `wss_sync_port` | `wss_async_port` | +| | `multi_port` | `multi_port` | + + +A port handler takes the stream object resulting form an incoming connection +request and constructs a handler-specific connection object which provides +the desired behavior. + +The HTTP ports which come with the example have a system built in which allows +installation of framework and user-defined "HTTP services". These services +inform connections using the port on how to handle specific requests. This is +similar in concept to an "HTTP router" which is an element of most modern +servers. + +These HTTP services are represented by the **Service** concept, and managed +in a container holding a type-list, called a `service_list`. Each HTTP port +allows the sevice list to be defined at compile-time and initialized at run +time. The framework provides these services: + +* `file_service` Produces HTTP responses delivering files from a system path + +* `ws_upgrade_service` Transports a connection requesting a WebSocket Upgrade +to a websocket port handler. + +## Relationship + +This diagram shows the relationship of the server object, to the nine +ports created in the example program, and the HTTP services contained by +the HTTP ports: + +ServerFramework + +## PortHandler Requirements +```C++ +/** An synchronous WebSocket @b PortHandler which implements echo. + + This is a port handler which accepts WebSocket upgrade HTTP + requests and implements the echo protocol. All received + WebSocket messages will be echoed back to the remote host. +*/ +struct PortHandler +{ + /** Accept a TCP/IP socket. + + This function is called when the server has accepted an + incoming connection. + + @param sock The connected socket. + + @param ep The endpoint of the remote host. + */ + void + on_accept( + socket_type&& sock, + endpoint_type ep); +}; +``` + +## Service Requirements + +```C++ +struct Service +{ + /** Initialize the service + + @param ec Set to the error, if any occurred + */ + void + init(error_code& ec); + + /** Maybe respond to an HTTP request + + Upon handling the response, the service may optionally + take ownership of either the stream, the request, or both. + + @param stream The stream representing the connection + + @param ep The remote endpoint of the stream + + @param req The HTTP request + + @param send A function object which operates on a single + argument of type beast::http::message. The function object + has this equivalent signature: + @code + template + void send(beast::http::response&& res); + @endcode + + @return `true` if the service handled the response. + */ + template< + class Stream, + class Body, class Fields, + class Send> + bool + respond( + Stream&& stream, + endpoint_type const& ep, + beast::http::request&& req, + Send const& send) const +}; +``` + +## Upgrade Service Requirements + +To work with the `ws_upgrade_service`, a port or handler needs +this signature: +```C++ + +struct UpgradePort +{ + template + void + on_upgrade( + Stream&& stream, + endpoint_type ep, + beast::http::request&& req); + +``` diff --git a/src/beast/example/server-framework/file_service.hpp b/src/beast/example/server-framework/file_service.hpp new file mode 100644 index 0000000000..8c7056d8d3 --- /dev/null +++ b/src/beast/example/server-framework/file_service.hpp @@ -0,0 +1,279 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_EXAMPLE_SERVER_FILE_SERVICE_HPP +#define BEAST_EXAMPLE_SERVER_FILE_SERVICE_HPP + +#include "framework.hpp" +#include "../common/mime_types.hpp" + +#include +#include +#include +#include +#include + +#include + +#include + +namespace framework { + +/** An HTTP service which delivers files from a root directory. + + This service will accept GET and HEAD requests for files, + and deliver them as responses. The service constructs with + the location on the file system to act as the root for the + tree of files to serve. + + Meets the requirements of @b Service +*/ +class file_service +{ + // The path to serve files from + boost::filesystem::path root_; + + // The name to use in the Server HTTP field + std::string server_; + +public: + /** Constructor + + @param root A path with files to serve. A GET request + for "/" will try to deliver the file "/index.html". + + @param The string to use in the Server HTTP field. + */ + explicit + file_service( + boost::filesystem::path const& root, + beast::string_view server) + : root_(root) + , server_(server) + { + } + + /** Initialize the service. + + This provides an opportunity for the service to perform + initialization which may fail, while reporting an error + code instead of throwing an exception from the constructor. + + @note This is needed for to meet the requirements for @b Service + */ + void + init(error_code& ec) + { + // This is required by the error_code specification + // + ec = {}; + } + + /** Try to handle a file request. + + @param stream The stream belonging to the connection. + Ownership is not transferred. + + @param ep The remote endpoint of the connection + corresponding to the stream. + + @param req The request message to attempt handling. + Ownership is not transferred. + + @param send The function to invoke with the response. + The function will have this equivalent signature: + + @code + + template + void + send(response&&); + + @endcode + + In C++14 this can be expressed using a generic lambda. In + C++11 it will require a template member function of an invocable + object. + + @return `true` if the request was handled by the service. + */ + template< + class Stream, + class Body, class Fields, + class Send> + bool + respond( + Stream&&, + endpoint_type const& ep, + beast::http::request&& req, + Send const& send) const + { + boost::ignore_unused(ep); + + // Determine our action based on the method + switch(req.method()) + { + case beast::http::verb::get: + { + // For GET requests we deliver the actual file + boost::filesystem::path rel_path(req.target().to_string()); + + // Give them the root web page if the target is "/" + if(rel_path == "/") + rel_path = "/index.html"; + + // Calculate full path from root + boost::filesystem::path full_path = root_ / rel_path; + + beast::error_code ec; + auto res = get(req, full_path, ec); + + if(ec == beast::errc::no_such_file_or_directory) + { + send(not_found(req, rel_path)); + } + else if(ec) + { + send(server_error(req, rel_path, ec)); + } + else + { + send(std::move(*res)); + } + + // Indicate that we handled the request + return true; + } + + case beast::http::verb::head: + { + // We are just going to give them the headers they + // would otherwise get, but without the body. + boost::filesystem::path rel_path(req.target().to_string()); + if(rel_path == "/") + rel_path = "/index.html"; + + // Calculate full path from root + boost::filesystem::path full_path = root_ / rel_path; + + beast::error_code ec; + auto res = head(req, full_path, ec); + + if(ec == beast::errc::no_such_file_or_directory) + { + send(not_found(req, rel_path)); + } + else if(ec) + { + send(server_error(req, rel_path, ec)); + } + else + { + send(std::move(*res)); + } + + // Indicate that we handled the request + return true; + } + + default: + break; + } + + // We didn't handle this request, so return false to + // inform the service list to try the next service. + // + return false; + } + +private: + // Return an HTTP Not Found response + // + template + beast::http::response + not_found( + beast::http::request const& req, + boost::filesystem::path const& rel_path) const + { + boost::ignore_unused(rel_path); + beast::http::response res; + res.version = req.version; + res.result(beast::http::status::not_found); + res.set(beast::http::field::server, server_); + res.set(beast::http::field::content_type, "text/html"); + res.body = "The file was not found"; // VFALCO append rel_path + res.prepare_payload(); + return res; + } + + // Return an HTTP Server Error + // + template + beast::http::response + server_error( + beast::http::request const& req, + boost::filesystem::path const& rel_path, + error_code const& ec) const + { + boost::ignore_unused(rel_path); + beast::http::response res; + res.version = req.version; + res.result(beast::http::status::internal_server_error); + res.set(beast::http::field::server, server_); + res.set(beast::http::field::content_type, "text/html"); + res.body = "Error: " + ec.message(); + res.prepare_payload(); + return res; + } + + // Return a file response to an HTTP GET request + // + template + boost::optional> + get( + beast::http::request const& req, + boost::filesystem::path const& full_path, + beast::error_code& ec) const + { + beast::http::response res; + res.version = req.version; + res.set(beast::http::field::server, server_); + res.set(beast::http::field::content_type, mime_type(full_path)); + res.body.open(full_path.string().c_str(), beast::file_mode::scan, ec); + if(ec) + return boost::none; + res.set(beast::http::field::content_length, res.body.size()); + return {std::move(res)}; + } + + // Return a response to an HTTP HEAD request + // + template + boost::optional> + head( + beast::http::request const& req, + boost::filesystem::path const& full_path, + beast::error_code& ec) const + { + beast::http::response res; + res.version = req.version; + res.set(beast::http::field::server, server_); + res.set(beast::http::field::content_type, mime_type(full_path)); + + // Use a manual file body here + beast::http::file_body::value_type body; + body.open(full_path.string().c_str(), beast::file_mode::scan, ec); + if(ec) + return boost::none; + res.set(beast::http::field::content_length, body.size()); + return {std::move(res)}; + } +}; + +} // framework + +#endif diff --git a/src/beast/example/server-framework/framework.hpp b/src/beast/example/server-framework/framework.hpp new file mode 100644 index 0000000000..6c288c93ab --- /dev/null +++ b/src/beast/example/server-framework/framework.hpp @@ -0,0 +1,53 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_EXAMPLE_SERVER_FRAMEWORK_HPP +#define BEAST_EXAMPLE_SERVER_FRAMEWORK_HPP + +#include +#include +#include +#include +#include + +/** The framework namespace + + This namespace contains all of the identifiers in the + server-framework system. Here we import some commonly + used types for brevity. +*/ +namespace framework { + +// This is our own base from member idiom written for C++11 +// which is simple and works around a glitch in boost's version. +// +template +class base_from_member +{ +public: + template + explicit + base_from_member(Args&&... args) + : member(std::forward(args)...) + { + } + +protected: + T member; +}; + +using error_code = boost::system::error_code; +using socket_type = boost::asio::ip::tcp::socket; +using strand_type = boost::asio::io_service::strand; +using address_type = boost::asio::ip::address_v4; +using endpoint_type = boost::asio::ip::tcp::endpoint; +using acceptor_type = boost::asio::ip::tcp::acceptor; +using io_service_type = boost::asio::io_service; + +} // framework + +#endif diff --git a/src/beast/example/server-framework/http_async_port.hpp b/src/beast/example/server-framework/http_async_port.hpp new file mode 100644 index 0000000000..7231ca7c41 --- /dev/null +++ b/src/beast/example/server-framework/http_async_port.hpp @@ -0,0 +1,653 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_EXAMPLE_SERVER_HTTP_ASYNC_PORT_HPP +#define BEAST_EXAMPLE_SERVER_HTTP_ASYNC_PORT_HPP + +#include "server.hpp" + +#include "http_base.hpp" +#include "service_list.hpp" + +#include "../common/rfc7231.hpp" +#include "../common/write_msg.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace framework { + +// Base class for a type-erased, queued asynchronous HTTP write operation +// +struct queued_http_write +{ + // Destructor must be virtual since we delete a + // derived class through a pointer to the base! + // + virtual ~queued_http_write() = default; + + // When invoked, performs the write operation. + virtual void invoke() = 0; +}; + +/* This implements an object which, when invoked, writes an HTTP + message asynchronously to the stream. These objects are used + to form a queue of outgoing messages for pipelining. The base + class type-erases the message so the queue can hold messsages + of different types. +*/ +template< + class Stream, + bool isRequest, class Body, class Fields, + class Handler> +class queued_http_write_impl : public queued_http_write +{ + // The stream to write to + Stream& stream_; + + // The message to send, which we acquire by move or copy + beast::http::message msg_; + + // The handler to invoke when the send completes. + Handler handler_; + +public: + // Constructor. + // + // Ownership of the message is transferred into the object + // + template + queued_http_write_impl( + Stream& stream, + beast::http::message&& msg, + DeducedHandler&& handler) + : stream_(stream) + , msg_(std::move(msg)) + , handler_(std::forward(handler)) + { + } + + // Writes the stored message. + // + // The caller must make sure this invocation represents + // a continuation of an asynchronous operation which is + // already in the right context. For example, already + // running on the associated strand. + // + void + invoke() override + { + async_write_msg( + stream_, + std::move(msg_), + std::move(handler_)); + } +}; + +// This helper function creates a queued_http_write +// object and returns it inside a unique_ptr. +// +template< + class Stream, + bool isRequest, class Body, class Fields, + class Handler> +std::unique_ptr +make_queued_http_write( + Stream& stream, + beast::http::message&& msg, + Handler&& handler) +{ + return std::unique_ptr{ + new queued_http_write_impl< + Stream, + isRequest, Body, Fields, + typename std::decay::type>{ + stream, + std::move(msg), + std::forward(handler)}}; +} + +//------------------------------------------------------------------------------ + +/** An asynchronous HTTP connection. + + This base class implements an HTTP connection object using + asynchronous calls. + + It uses the Curiously Recurring Template pattern (CRTP) where + we refer to the derived class in order to access the stream object + to use for reading and writing. This lets the same class be used + for plain and SSL stream objects. + + @tparam Services The list of services this connection will support. +*/ +template +class async_http_con_base : public http_base +{ +protected: + // This function lets us access members of the derived class + Derived& + impl() + { + return static_cast(*this); + } + + // The stream to use for logging + std::ostream& log_; + + // The services configured for the port + service_list const& services_; + + // A small unique integer for logging + std::size_t id_; + + // The remote endpoint. We cache it here because + // calls to remote_endpoint() can fail / throw. + // + endpoint_type ep_; + + // The buffer for performing reads + beast::flat_buffer buffer_; + + // The parser for reading the requests + boost::optional> parser_; + + // This is the queue of outgoing messages + std::vector> queue_; + + // Indicates if we have a write active. + bool writing_ = false; + + // The strand makes sure that our data is + // accessed from only one thread at a time. + // + strand_type strand_; + +public: + // Constructor + async_http_con_base( + beast::string_view server_name, + std::ostream& log, + service_list const& services, + std::size_t id, + endpoint_type const& ep) + : http_base(server_name) + , log_(log) + , services_(services) + , id_(id) + , ep_(ep) + + // The buffer has a limit of 8192, otherwise + // the server is vulnerable to a buffer attack. + // + , buffer_(8192) + + , strand_(impl().stream().get_io_service()) + { + } + + // Called to start the object after the listener accepts + // an incoming connection, when no bytes have been read yet. + // + void + run() + { + // Just call run with an empty buffer + run(boost::asio::null_buffers{}); + } + + // Called to start the object after the + // listener accepts an incoming connection. + // + template + void + run(ConstBufferSequence const& buffers) + { + // Copy the data into the buffer for performing + // HTTP reads, so that the bytes get used. + // + buffer_.commit(boost::asio::buffer_copy( + buffer_.prepare(boost::asio::buffer_size(buffers)), + buffers)); + + // Give the derived class a chance to do stuff + // + impl().do_handshake(); + } + +protected: + void + do_run() + { + do_read_header(); + } + + // Called when a failure occurs + // + void + fail(std::string what, error_code ec) + { + // Don't log operation aborted since those happen normally. + // + if(ec && ec != boost::asio::error::operation_aborted) + { + log_ << + "[#" << id_ << " " << ep_ << "] " << + what << ": " << ec.message() << std::endl; + } + } + + // Perform an asynchronous read for the next request header + // + void + do_read_header() + { + // On each read the parser needs to be destroyed and + // recreated. We store it in a boost::optional to + // achieve that. + // + // Arguments passed to the parser constructor are + // forwarded to the message object. A single argument + // is forwarded to the body constructor. + // + // We construct the dynamic body with a 1MB limit + // to prevent vulnerability to buffer attacks. + // + parser_.emplace(std::piecewise_construct, std::make_tuple(1024 * 1024)); + + // Read just the header + beast::http::async_read_header( + impl().stream(), + buffer_, + *parser_, + strand_.wrap(std::bind( + &async_http_con_base::on_read_header, + impl().shared_from_this(), + std::placeholders::_1))); + } + + // This lambda is passed to the service list to handle + // the case of sending request objects of varying types. + // In C++14 this is more easily accomplished using a generic + // lambda, but we want C+11 compatibility so we manually + // write the lambda out. + // + struct send_lambda + { + // holds "this" + async_http_con_base& self_; + + public: + // capture "this" + explicit + send_lambda(async_http_con_base& self) + : self_(self) + { + } + + // sends a message + template + void + operator()(beast::http::response&& res) const + { + self_.do_write(std::move(res)); + } + }; + + // Called when the header has been read in + void + on_read_header(error_code ec) + { + // This happens when the other end closes gracefully + // + if(ec == beast::http::error::end_of_stream) + { + // VFALCO what about the write queue? + return impl().do_shutdown(); + } + + // On failure we just return, the shared_ptr that is bound + // into the completion will go out of scope and eventually + // this will get destroyed. + // + if(ec) + return fail("on_read", ec); + + // The parser holds the request object, + // at this point it only has the header in it. + auto& req = parser_->get(); + + send_lambda send{*this}; + + // See if they are specifying Expect: 100-continue + // + if(rfc7231::is_expect_100_continue(req)) + { + // They want to know if they should continue, + // so send the appropriate response. + // + send(this->continue_100(req)); + } + + // Read the rest of the message, if any. + // + beast::http::async_read( + impl().stream(), + buffer_, + *parser_, + strand_.wrap(std::bind( + &async_http_con_base::on_read, + impl().shared_from_this(), + std::placeholders::_1))); + } + + // Called when the message is complete + void + on_read(error_code ec) + { + // Shouldn't be getting end_of_stream here; + // that would mean that we got an incomplete + // message, counting as an error. + // + if(ec) + return fail("on_read", ec); + + // Grab a reference to the request again + auto& req = parser_->get(); + + // Create a variable for our send + // lambda since we use it more than once. + // + send_lambda send{*this}; + + // Give each service a chance to handle the request + // + if(! services_.respond( + std::move(impl().stream()), + ep_, + std::move(req), + send)) + { + // No service handled the request, + // send a Bad Request result to the client. + // + send(this->bad_request(req)); + } + else + { + // See if the service that handled the + // response took ownership of the stream. + // + if(! impl().stream().lowest_layer().is_open()) + { + // They took ownership so just return and + // let this async_http_con_base object get destroyed. + // + return; + } + } + + // VFALCO Right now we do unlimited pipelining which + // can lead to unbounded resource consumption. + // A more sophisticated server might only issue + // this read when the queue is below some limit. + // + + // Start reading another header + do_read_header(); + } + + // This function either queues a message or + // starts writing it if no other writes are taking place. + // + template + void + do_write(beast::http::response&& res) + { + // See if a write is in progress + if(! writing_) + { + // An assert or two to keep things sane when + // writing asynchronous code can be very helpful. + BOOST_ASSERT(queue_.empty()); + + // We're going to be writing so set the flag + writing_ = true; + + // And now perform the write + return async_write_msg( + impl().stream(), + std::move(res), + strand_.wrap(std::bind( + &async_http_con_base::on_write, + impl().shared_from_this(), + std::placeholders::_1))); + } + + // Queue is not empty, so append this message to the queue. + // It will be sent late when the queue empties. + // + queue_.emplace_back(make_queued_http_write( + impl().stream(), + std::move(res), + strand_.wrap(std::bind( + &async_http_con_base::on_write, + impl().shared_from_this(), + std::placeholders::_1)))); + } + + // Called when a message finishes writing + void + on_write(error_code ec) + { + // Make sure our state is what we think it is + BOOST_ASSERT(writing_); + + // This happens when we send an HTTP message + // whose semantics indicate that the connection + // should be closed afterwards. For example if + // we send a Connection: close. + // + if(ec == beast::http::error::end_of_stream) + return impl().do_shutdown(); + + // On failure just log and return + if(ec) + return fail("on_write", ec); + + // See if the queue is empty + if(queue_.empty()) + { + // Queue was empty so clear the flag... + writing_ = false; + + // ...and return + return; + } + + // Queue was not empty, so invoke the object + // at the head of the queue. This will start + // another wrte. + queue_.front()->invoke(); + + // Delete the item since we used it + queue_.erase(queue_.begin()); + } +}; + +//------------------------------------------------------------------------------ + +// This class represents an asynchronous HTTP connection which +// uses a plain TCP/IP socket (no encryption) as the stream. +// +template +class async_http_con + + // Give this object the enable_shared_from_this, and have + // the base class call impl().shared_from_this(). The reason + // is so that the shared_ptr has the correct type. This lets + // the derived class (this class) use its members in calls to + // `std::bind`, without an ugly call to `dynamic_downcast` or + // other nonsense. + // + : public std::enable_shared_from_this> + + // The stream should be created before the base class so + // use the "base from member" idiom. + // + , public base_from_member + + // Constructs last, destroys first + // + , public async_http_con_base, Services...> +{ +public: + // Constructor + // + // Additional arguments are forwarded to the base class + // + template + async_http_con( + socket_type&& sock, + Args&&... args) + : base_from_member(std::move(sock)) + , async_http_con_base, Services...>( + std::forward(args)...) + { + } + + // Returns the stream. + // + // The base class calls this to obtain the object to use for + // reading and writing HTTP messages. This allows the same base + // class to work with different return types for `stream()` such + // as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&` + // + socket_type& + stream() + { + return this->member; + } + +private: + // Base class needs to be a friend to call our private members + friend class async_http_con_base, Services...>; + + // This is called by the base before running the main loop. + // + void + do_handshake() + { + // Run the main loop right away + // + this->do_run(); + } + + // This is called when the other end closes the connection gracefully. + // + void + do_shutdown() + { + error_code ec; + stream().shutdown(socket_type::shutdown_both, ec); + + // not_connected happens under normal + // circumstances so don't bother reporting it. + // + if(ec && ec != beast::errc::not_connected) + return this->fail("shutdown", ec); + } +}; + +//------------------------------------------------------------------------------ + +/* An asynchronous HTTP port handler + + This type meets the requirements of @b PortHandler. It supports + variable list of HTTP services in its template parameter list, + and provides a synchronous connection implementation to service +*/ +template +class http_async_port +{ + // Reference to the server instance that made us + server& instance_; + + // The stream to log to + std::ostream& log_; + + // The list of services connections created from this port will support + service_list services_; + +public: + /** Constructor + + @param instance The server instance which owns this port + + @param log The stream to use for logging + */ + http_async_port( + server& instance, + std::ostream& log) + : instance_(instance) + , log_(log) + { + } + + /** Initialize a service + + Every service in the list must be initialized exactly once. + + @param args Optional arguments forwarded to the service + constructor. + + @tparam Index The 0-based index of the service to initialize. + + @return A reference to the service list. This permits + calls to be chained in a single expression. + */ + template + void + init(error_code& ec, Args&&... args) + { + services_.template init( + ec, + std::forward(args)...); + } + + /** Called by the server to provide ownership of the socket for a new connection + + @param sock The socket whose ownership is to be transferred + + @ep The remote endpoint + */ + void + on_accept(socket_type&& sock, endpoint_type ep) + { + // Create a plain http connection object + // and transfer ownership of the socket. + // + std::make_shared>( + std::move(sock), + "http_async_port", + log_, + services_, + instance_.next_id(), + ep)->run(); + } +}; + +} // framework + +#endif diff --git a/src/beast/example/server-framework/http_base.hpp b/src/beast/example/server-framework/http_base.hpp new file mode 100644 index 0000000000..6dddb079a4 --- /dev/null +++ b/src/beast/example/server-framework/http_base.hpp @@ -0,0 +1,77 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_EXAMPLE_SERVER_HTTP_BASE_HPP +#define BEAST_EXAMPLE_SERVER_HTTP_BASE_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace framework { + +/* Base class for HTTP PortHandlers + + This holds the server name and has some shared + routines for building typical HTTP responses. +*/ +class http_base +{ + beast::string_view server_name_; + +public: + explicit + http_base(beast::string_view server_name) + : server_name_(server_name) + { + } + +protected: + // Returns a bad request result response + // + template + beast::http::response + bad_request(beast::http::request const& req) const + { + beast::http::response res; + + // Match the version to the request + res.version = req.version; + + res.result(beast::http::status::bad_request); + res.set(beast::http::field::server, server_name_); + res.set(beast::http::field::content_type, "text/html"); + res.body = "Bad request"; + res.prepare_payload(); + return res; + } + + // Returns a 100 Continue result response + // + template + beast::http::response + continue_100(beast::http::request const& req) const + { + beast::http::response res; + + // Match the version to the request + res.version = req.version; + + res.result(beast::http::status::continue_); + res.set(beast::http::field::server, server_name_); + + return res; + } +}; + +} // framework + +#endif diff --git a/src/beast/example/server-framework/http_sync_port.hpp b/src/beast/example/server-framework/http_sync_port.hpp new file mode 100644 index 0000000000..2cd20477bb --- /dev/null +++ b/src/beast/example/server-framework/http_sync_port.hpp @@ -0,0 +1,477 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_EXAMPLE_SERVER_HTTP_SYNC_PORT_HPP +#define BEAST_EXAMPLE_SERVER_HTTP_SYNC_PORT_HPP + +#include "server.hpp" + +#include "http_base.hpp" +#include "service_list.hpp" + +#include "../common/rfc7231.hpp" +#include "../common/write_msg.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace framework { + +/** A synchronous HTTP connection. + + This base class implements an HTTP connection object using + synchronous calls. + + It uses the Curiously Recurring Template pattern (CRTP) where + we refer to the derived class in order to access the stream object + to use for reading and writing. This lets the same class be used + for plain and SSL stream objects. + + @tparam Services The list of services this connection will support. +*/ +template +class sync_http_con_base + : public http_base +{ + // This function lets us access members of the derived class + Derived& + impl() + { + return static_cast(*this); + } + + // The stream to use for logging + std::ostream& log_; + + // The services configured for the port + service_list const& services_; + + // A small unique integer for logging + std::size_t id_; + + // The remote endpoint. We cache it here because + // calls to remote_endpoint() can fail / throw. + // + endpoint_type ep_; + + // The buffer for performing reads + beast::flat_buffer buffer_; + +public: + /// Constructor + sync_http_con_base( + beast::string_view server_name, + std::ostream& log, + service_list const& services, + std::size_t id, + endpoint_type const& ep) + : http_base(server_name) + , log_(log) + , services_(services) + , id_(id) + , ep_(ep) + + // The buffer has a limit of 8192, otherwise + // the server is vulnerable to a buffer attack. + // + , buffer_(8192) + { + } + + // This is called to start the connection after + // it is accepted. + // + void + run() + { + // Bind a shared pointer into the lambda for the + // thread, so the sync_http_con_base is destroyed after + // the thread function exits. + // + std::thread{ + &sync_http_con_base::do_run, + impl().shared_from_this() + }.detach(); + } + +protected: + // Called when a failure occurs + // + void + fail(std::string what, error_code ec) + { + if(ec) + { + log_ << + "[#" << id_ << " " << ep_ << "] " << + what << ": " << ec.message() << std::endl; + } + } + +private: + // This lambda is passed to the service list to handle + // the case of sending request objects of varying types. + // In C++14 this is more easily accomplished using a generic + // lambda, but we want C+11 compatibility so we manually + // write the lambda out. + // + struct send_lambda + { + // holds "this" + sync_http_con_base& self_; + + // holds the captured error code + error_code& ec_; + + public: + // Constructor + // + // Capture "this" and "ec" + // + send_lambda(sync_http_con_base& self, error_code& ec) + : self_(self) + , ec_(ec) + { + } + + // Sends a message + // + // Since this is a synchronous implementation we + // just call the write function and block. + // + template + void + operator()( + beast::http::response&& res) const + { + beast::http::serializer sr{res}; + beast::http::write(self_.impl().stream(), sr, ec_); + } + }; + + void + do_run() + { + error_code ec; + + // Give the derived class a chance to do stuff before we + // enter the main loop. This is for SSL connections really. + // + impl().do_handshake(ec); + + if(ec) + return fail("handshake", ec); + + // The main connection loop, we alternate between + // reading a request and sending a response. On + // error we log and return, which destroys the thread + // and the stream (thus closing the connection) + // + for(;;) + { + // Arguments passed to the parser constructor are + // forwarded to the message object. A single argument + // is forwarded to the body constructor. + // + // We construct the dynamic body with a 1MB limit + // to prevent vulnerability to buffer attacks. + // + beast::http::request_parser parser( + std::piecewise_construct, std::make_tuple(1024* 1024)); + + // Read the header first + beast::http::read_header(impl().stream(), buffer_, parser, ec); + + // This happens when the other end closes gracefully + // + if(ec == beast::http::error::end_of_stream) + { + // Give the derived class a chance to do stuff + impl().do_shutdown(ec); + if(ec && ec != beast::errc::not_connected) + return fail("shutdown", ec); + return; + } + + // Any other error and we fail the connection + if(ec) + return fail("read_header", ec); + + send_lambda send{*this, ec}; + + auto& req = parser.get(); + + // See if they are specifying Expect: 100-continue + // + if(rfc7231::is_expect_100_continue(req)) + { + // They want to know if they should continue, + // so send the appropriate response synchronously. + // + send(this->continue_100(req)); + + // This happens when we send an HTTP message + // whose semantics indicate that the connection + // should be closed afterwards. For example if + // we send a Connection: close. + // + if(ec == beast::http::error::end_of_stream) + { + // Give the derived class a chance to do stuff + impl().do_shutdown(ec); + if(ec && ec != beast::errc::not_connected) + return fail("shutdown", ec); + return; + } + + // Have to check the error every time we call the lambda + // + if(ec) + return fail("write", ec); + } + + // Read the rest of the message, if any. + // + beast::http::read(impl().stream(), buffer_, parser, ec); + + // Shouldn't be getting end_of_stream here; + // that would mean that we got an incomplete + // message, counting as an error. + // + if(ec) + return fail("read", ec); + + // Give each service a chance to handle the request + // + if(! services_.respond( + std::move(impl().stream()), + ep_, + std::move(req), + send)) + { + // No service handled the request, + // send a Bad Request result to the client. + // + send(this->bad_request(req)); + + // This happens when we send an HTTP message + // whose semantics indicate that the connection + // should be closed afterwards. For example if + // we send a Connection: close. + // + if(ec == beast::http::error::end_of_stream) + { + // Give the derived class a chance to do stuff + impl().do_shutdown(ec); + if(ec && ec != beast::errc::not_connected) + return fail("shutdown", ec); + return; + } + + // Have to check the error every time we call the lambda + // + if(ec) + return fail("write", ec); + } + else + { + // This happens when we send an HTTP message + // whose semantics indicate that the connection + // should be closed afterwards. For example if + // we send a Connection: close. + // + if(ec == beast::http::error::end_of_stream) + { + // Give the derived class a chance to do stuff + if(ec && ec != beast::errc::not_connected) + return fail("shutdown", ec); + return; + } + + // Have to check the error every time we call the lambda + // + if(ec) + return fail("write", ec); + + // See if the service that handled the + // response took ownership of the stream. + if(! impl().stream().lowest_layer().is_open()) + { + // They took ownership so just return and + // let this sync_http_con_base object get destroyed. + return; + } + } + + // Theres no pipelining possible in a synchronous server + // because we can't do reads and writes at the same time. + } + } +}; + +//------------------------------------------------------------------------------ + +// This class represents a synchronous HTTP connection which +// uses a plain TCP/IP socket (no encryption) as the stream. +// +template +class sync_http_con + + // Give this object the enable_shared_from_this, and have + // the base class call impl().shared_from_this(). The reason + // is so that the shared_ptr has the correct type. This lets + // the derived class (this class) use its members in calls to + // `std::bind`, without an ugly call to `dynamic_downcast` or + // other nonsense. + // + : public std::enable_shared_from_this> + + // The stream should be created before the base class so + // use the "base from member" idiom. + // + , public base_from_member + + // Constructs last, destroys first + // + , public sync_http_con_base, Services...> +{ +public: + // Constructor + // + // Additional arguments are forwarded to the base class + // + template + sync_http_con( + socket_type&& sock, + Args&&... args) + : base_from_member(std::move(sock)) + , sync_http_con_base, Services...>( + std::forward(args)...) + { + } + + // Returns the stream. + // + // The base class calls this to obtain the object to use for + // reading and writing HTTP messages. This allows the same base + // class to work with different return types for `stream()` such + // as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&` + // + socket_type& + stream() + { + return this->member; + } + +private: + // Base class needs to be a friend to call our private members + friend class sync_http_con_base, Services...>; + + // This is called by the base before running the main loop. + // There's nothing to do for a plain connection. + // + void + do_handshake(error_code& ec) + { + // This is required by the specifications for error_code + // + ec = {}; + } + + // This is called when the other end closes the connection gracefully. + // + void + do_shutdown(error_code& ec) + { + stream().shutdown(socket_type::shutdown_both, ec); + } +}; + +//------------------------------------------------------------------------------ + +/* A synchronous HTTP port handler + + This type meets the requirements of @b PortHandler. It supports + variable list of HTTP services in its template parameter list, + and provides a synchronous connection implementation to service +*/ +template +class http_sync_port +{ + server& instance_; + std::ostream& log_; + service_list services_; + +public: + /** Constructor + + @param instance The server instance which owns this port + + @param log The stream to use for logging + */ + http_sync_port( + server& instance, + std::ostream& log) + : instance_(instance) + , log_(log) + { + } + + /** Initialize a service + + Every service in the list must be initialized exactly once. + + @param ec Set to the error, if any occurred + + @param args Optional arguments forwarded to the service + constructor. + + @tparam Index The 0-based index of the service to initialize. + */ + template + void + init(error_code& ec, Args&&... args) + { + services_.template init( + ec, + std::forward(args)...); + } + + /** Called by the server to provide ownership of the socket for a new connection + + @param sock The socket whose ownership is to be transferred + + @ep The remote endpoint + */ + void + on_accept(socket_type&& sock, endpoint_type ep) + { + // Create a plain http connection object + // and transfer ownership of the socket. + // + std::make_shared>( + std::move(sock), + "http_sync_port", + log_, + services_, + instance_.next_id(), + ep)->run(); + } +}; + +} // framework + +#endif diff --git a/src/beast/example/server-framework/https_ports.hpp b/src/beast/example/server-framework/https_ports.hpp new file mode 100644 index 0000000000..8a4a5c2450 --- /dev/null +++ b/src/beast/example/server-framework/https_ports.hpp @@ -0,0 +1,426 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_EXAMPLE_SERVER_HTTPS_PORTS_HPP +#define BEAST_EXAMPLE_SERVER_HTTPS_PORTS_HPP + +#include "http_sync_port.hpp" +#include "http_async_port.hpp" + +#include "../common/ssl_stream.hpp" + +#include + +namespace framework { + +//------------------------------------------------------------------------------ + +// This class represents a synchronous HTTP connection which +// uses an OpenSSL socket as the stream. +// +template +class sync_https_con + + // Give this object the enable_shared_from_this, and have + // the base class call impl().shared_from_this(). The reason + // is so that the shared_ptr has the correct type. This lets + // the derived class (this class) use its members in calls to + // `std::bind`, without an ugly call to `dynamic_downcast` or + // other nonsense. + // + : public std::enable_shared_from_this> + + // The stream should be created before the base class so + // use the "base from member" idiom. + // + , public base_from_member> + + // Constructs last, destroys first + // + , public sync_http_con_base, Services...> +{ +public: + // Constructor + // + // Additional arguments are forwarded to the base class + // + template + sync_https_con( + socket_type&& sock, + boost::asio::ssl::context& ctx, + Args&&... args) + : base_from_member>(std::move(sock), ctx) + , sync_http_con_base, Services...>( + std::forward(args)...) + { + } + + // Returns the stream. + // + // The base class calls this to obtain the object to use for + // reading and writing HTTP messages. This allows the same base + // class to work with different return types for `stream()` such + // as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&` + // + ssl_stream& + stream() + { + return this->member; + } + +private: + friend class sync_http_con_base, Services...>; + + // This is called by the base before running the main loop. + // + void + do_handshake(error_code& ec) + { + // Perform the SSL handshake + // + stream().handshake(boost::asio::ssl::stream_base::server, ec); + } + + // This is called when the other end closes the connection gracefully. + // + void + do_shutdown(error_code& ec) + { + // Note that this is an SSL shutdown + // + stream().shutdown(ec); + if(ec) + return this->fail("ssl_shutdown", ec); + } +}; + +//------------------------------------------------------------------------------ + +// This class represents an asynchronous HTTP connection which +// uses an OpenSSL socket as the stream. +// +template +class async_https_con + + // Give this object the enable_shared_from_this, and have + // the base class call impl().shared_from_this(). The reason + // is so that the shared_ptr has the correct type. This lets + // the derived class (this class) use its members in calls to + // `std::bind`, without an ugly call to `dynamic_downcast` or + // other nonsense. + // + : public std::enable_shared_from_this> + + // The stream should be created before the base class so + // use the "base from member" idiom. + // + , public base_from_member> + + // Constructs last, destroys first + // + , public async_http_con_base, Services...> +{ +public: + // Constructor + // + // Additional arguments are forwarded to the base class + // + template + async_https_con( + socket_type&& sock, + boost::asio::ssl::context& ctx, + Args&&... args) + : base_from_member>(std::move(sock), ctx) + , async_http_con_base, Services...>( + std::forward(args)...) + { + } + + // Returns the stream. + // + // The base class calls this to obtain the object to use for + // reading and writing HTTP messages. This allows the same base + // class to work with different return types for `stream()` such + // as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&` + // + ssl_stream& + stream() + { + return this->member; + } + + // Called by the multi-port after reading some + // bytes from the stream and detecting SSL. + // + template + void + handshake(ConstBufferSequence const& buffers) + { + // Copy the caller's bytes into the buffer we + // use for reading HTTP messages, otherwise + // the memory pointed to by buffers will go out + // of scope. + // + this->buffer_.commit( + boost::asio::buffer_copy( + this->buffer_.prepare(boost::asio::buffer_size(buffers)), + buffers)); + + // Perform SSL handshake. We use the "buffered" + // overload which lets us pass those extra bytes. + // + stream().async_handshake( + boost::asio::ssl::stream_base::server, + buffers, + this->strand_.wrap( + std::bind( + &async_https_con::on_buffered_handshake, + this->shared_from_this(), + std::placeholders::_1, + std::placeholders::_2))); + } + +private: + friend class async_http_con_base, Services...>; + + // Called by the base class before starting the main loop. + // + void + do_handshake() + { + // This is SSL so perform the handshake + // + stream().async_handshake( + boost::asio::ssl::stream_base::server, + this->strand_.wrap( + std::bind( + &async_https_con::on_handshake, + this->shared_from_this(), + std::placeholders::_1))); + } + + // Called when the SSL handshake completes + void + on_handshake(error_code ec) + { + if(ec) + return this->fail("on_handshake", ec); + + // No error so run the main loop + this->do_run(); + } + + // Called when the buffered SSL handshake completes + void + on_buffered_handshake(error_code ec, std::size_t bytes_transferred) + { + if(ec) + return this->fail("on_handshake", ec); + + // Consume what was read but leave the rest + this->buffer_.consume(bytes_transferred); + + // No error so run the main loop + this->do_run(); + } + + // Called when the end of stream is reached + void + do_shutdown() + { + // This is an SSL shutdown + // + stream().async_shutdown( + this->strand_.wrap( + std::bind( + &async_https_con::on_shutdown, + this->shared_from_this(), + std::placeholders::_1))); + } + + // Called when the SSL shutdown completes + void + on_shutdown(error_code ec) + { + if(ec) + return this->fail("on_shutdown", ec); + } +}; + +//------------------------------------------------------------------------------ + +/* A synchronous HTTPS port handler + + This type meets the requirements of @b PortHandler. It supports + variable list of HTTP services in its template parameter list, + and provides a synchronous connection implementation to service +*/ +template +class https_sync_port +{ + // Reference to the server instance that made us + server& instance_; + + // The stream to log to + std::ostream& log_; + + // The list of services connections created from this port will support + service_list services_; + + // The SSL context containing the server's credentials + boost::asio::ssl::context& ctx_; + +public: + /** Constructor + + @param instance The server instance which owns this port + + @param log The stream to use for logging + + @param ctx The SSL context holding the SSL certificates to use + */ + https_sync_port( + server& instance, + std::ostream& log, + boost::asio::ssl::context& ctx) + : instance_(instance) + , log_(log) + , ctx_(ctx) + { + } + + /** Initialize a service + + Every service in the list must be initialized exactly once. + + @param args Optional arguments forwarded to the service + constructor. + + @tparam Index The 0-based index of the service to initialize. + + @return A reference to the service list. This permits + calls to be chained in a single expression. + */ + template + void + init(error_code& ec, Args&&... args) + { + services_.template init( + ec, + std::forward(args)...); + } + + /** Called by the server to provide ownership of the socket for a new connection + + @param sock The socket whose ownership is to be transferred + + @ep The remote endpoint + */ + void + on_accept(socket_type&& sock, endpoint_type ep) + { + // Create an HTTPS connection object + // and transfer ownership of the socket. + // + std::make_shared>( + std::move(sock), + ctx_, + "https_sync_port", + log_, + services_, + instance_.next_id(), + ep)->run(); + } +}; + +//------------------------------------------------------------------------------ + +/* An asynchronous HTTPS port handler + + This type meets the requirements of @b PortHandler. It supports + variable list of HTTP services in its template parameter list, + and provides a synchronous connection implementation to service +*/ +template +class https_async_port +{ + // Reference to the server instance that made us + server& instance_; + + // The stream to log to + std::ostream& log_; + + // The list of services connections created from this port will support + service_list services_; + + // The SSL context containing the server's credentials + boost::asio::ssl::context& ctx_; + +public: + /** Constructor + + @param instance The server instance which owns this port + + @param log The stream to use for logging + */ + https_async_port( + server& instance, + std::ostream& log, + boost::asio::ssl::context& ctx) + : instance_(instance) + , log_(log) + , ctx_(ctx) + { + } + + /** Initialize a service + + Every service in the list must be initialized exactly once. + + @param args Optional arguments forwarded to the service + constructor. + + @tparam Index The 0-based index of the service to initialize. + + @return A reference to the service list. This permits + calls to be chained in a single expression. + */ + template + void + init(error_code& ec, Args&&... args) + { + services_.template init( + ec, + std::forward(args)...); + } + + /** Called by the server to provide ownership of the socket for a new connection + + @param sock The socket whose ownership is to be transferred + + @ep The remote endpoint + */ + void + on_accept(socket_type&& sock, endpoint_type ep) + { + // Create an SSL connection object + // and transfer ownership of the socket. + // + std::make_shared>( + std::move(sock), + ctx_, + "https_async_port", + log_, + services_, + instance_.next_id(), + ep)->run(); + } +}; + +} // framework + +#endif diff --git a/src/beast/example/server-framework/main.cpp b/src/beast/example/server-framework/main.cpp new file mode 100644 index 0000000000..fcaffa8ce7 --- /dev/null +++ b/src/beast/example/server-framework/main.cpp @@ -0,0 +1,446 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include "server.hpp" + +#include "http_async_port.hpp" +#include "http_sync_port.hpp" +#include "ws_async_port.hpp" +#include "ws_sync_port.hpp" + +#if BEAST_USE_OPENSSL +#include "https_ports.hpp" +#include "multi_port.hpp" +#include "wss_ports.hpp" +#include "ssl_certificate.hpp" +#endif + +#include "file_service.hpp" +#include "ws_upgrade_service.hpp" + +#include + +#include + +/// Block until SIGINT or SIGTERM is received. +void +sig_wait() +{ + // Create our own io_service for this + boost::asio::io_service ios; + + // Get notified on the signals we want + boost::asio::signal_set signals( + ios, SIGINT, SIGTERM); + + // Now perform the asynchronous call + signals.async_wait( + [&](boost::system::error_code const&, int) + { + }); + + // Block the current thread on run(), when the + // signal is received then this call will return. + ios.run(); +} + +/** Set the options on a WebSocket stream. + + This is used by the websocket server port handlers. + It is called every time a new websocket stream is + created, to provide the opportunity to set settings + for the connection. +*/ +class set_ws_options +{ + beast::websocket::permessage_deflate pmd_; + +public: + set_ws_options(beast::websocket::permessage_deflate const& pmd) + : pmd_(pmd) + { + } + + template + void + operator()(beast::websocket::stream& ws) const + { + ws.auto_fragment(false); + ws.set_option(pmd_); + ws.read_message_max(64 * 1024 * 1024); + } +}; + +int +main( + int ac, + char const* av[]) +{ + using namespace framework; + using namespace beast::http; + + // Helper for reporting failures + // + auto const fail = + [&]( + std::string const& what, + error_code const& ec) + { + std::cerr << + av[0] << ": " << + what << " failed, " << + ec.message() << + std::endl; + return EXIT_FAILURE; + }; + + namespace po = boost::program_options; + po::options_description desc("Options"); + + desc.add_options() + ("root,r", po::value()->default_value("."), + "Set the root directory for serving files") + ("port,p", po::value()->default_value(1000), + "Set the base port number for the server") + ("ip", po::value()->default_value("0.0.0.0"), + "Set the IP address to bind to, \"0.0.0.0\" for all") + ("threads,n", po::value()->default_value(4), + "Set the number of threads to use") + ; + po::variables_map vm; + po::store(po::parse_command_line(ac, av, desc), vm); + + // Get the IP address from the options + std::string const ip = vm["ip"].as(); + + // Get the port number from the options + std::uint16_t const port = vm["port"].as(); + + // Build an endpoint from the address and port + address_type const addr{address_type::from_string(ip)}; + + // Get the number of threads from the command line + std::size_t const threads = vm["threads"].as(); + + // Get the root path from the command line + boost::filesystem::path const root = vm["root"].as(); + + // These settings will be applied to all new websocket connections + beast::websocket::permessage_deflate pmd; + pmd.client_enable = true; + pmd.server_enable = true; + pmd.compLevel = 3; + + error_code ec; + + // Create our server instance with the specified number of threads + server instance{threads}; + + //-------------------------------------------------------------------------- + // + // Synchronous WebSocket HTTP + // + // port + 0 port + 1 + // + //-------------------------------------------------------------------------- + { + // Create a WebSocket port + // + auto wsp = instance.make_port( + ec, + endpoint_type{addr,static_cast(port + 0)}, + instance, + std::cout, + set_ws_options{pmd}); + + if(ec) + return fail("ws_sync_port", ec); + + // Create an HTTP port + // + auto sp = instance.make_port, + file_service + >>( + ec, + endpoint_type{addr,static_cast(port + 1)}, + instance, + std::cout); + + if(ec) + return fail("http_sync_port", ec); + + // Init the ws_upgrade_service to + // forward upgrades to the WebSocket port. + // + sp->template init<0>( + ec, + *wsp // The WebSocket port handler + ); + + if(ec) + return fail("http_sync_port/ws_upgrade_service", ec); + + // Init the file_service to point to the root path. + // + sp->template init<1>( + ec, + root, // The root path + "http_sync_port" // The value for the Server field + ); + + if(ec) + return fail("http_sync_port/file_service", ec); + } + + //-------------------------------------------------------------------------- + // + // Asynchronous WebSocket HTTP + // + // port + 2 port + 3 + // + //-------------------------------------------------------------------------- + { + // Create a WebSocket port + // + auto wsp = instance.make_port( + ec, + endpoint_type{addr, + static_cast(port + 2)}, + instance, + std::cout, + set_ws_options{pmd} + ); + + if(ec) + return fail("ws_async_port", ec); + + // Create an HTTP port + // + auto sp = instance.make_port, + file_service + >>( + ec, + endpoint_type{addr, + static_cast(port + 3)}, + instance, + std::cout); + + if(ec) + return fail("http_async_port", ec); + + // Init the ws_upgrade_service to + // forward upgrades to the WebSocket port. + // + sp->template init<0>( + ec, + *wsp // The websocket port handler + ); + + if(ec) + return fail("http_async_port/ws_upgrade_service", ec); + + // Init the file_service to point to the root path. + // + sp->template init<1>( + ec, + root, // The root path + "http_async_port" // The value for the Server field + ); + + if(ec) + return fail("http_async_port/file_service", ec); + } + + // + // The next section supports encrypted connections and requires + // an installed and configured OpenSSL as part of the build. + // + +#if BEAST_USE_OPENSSL + + ssl_certificate cert; + + //-------------------------------------------------------------------------- + // + // Synchronous Secure WebSocket HTTPS + // + // port + 4 port + 5 + // + //-------------------------------------------------------------------------- + { + // Create a WebSocket port + // + auto wsp = instance.make_port( + ec, + endpoint_type{addr, + static_cast(port + 4)}, + instance, + std::cout, + cert.get(), + set_ws_options{pmd}); + + if(ec) + return fail("wss_sync_port", ec); + + // Create an HTTP port + // + auto sp = instance.make_port, + file_service + >>( + ec, + endpoint_type{addr, + static_cast(port + 5)}, + instance, + std::cout, + cert.get()); + + if(ec) + return fail("https_sync_port", ec); + + // Init the ws_upgrade_service to + // forward upgrades to the WebSocket port. + // + sp->template init<0>( + ec, + *wsp // The websocket port handler + ); + + if(ec) + return fail("http_sync_port/ws_upgrade_service", ec); + + // Init the file_service to point to the root path. + // + sp->template init<1>( + ec, + root, // The root path + "http_sync_port" // The value for the Server field + ); + + if(ec) + return fail("https_sync_port/file_service", ec); + } + + //-------------------------------------------------------------------------- + // + // Asynchronous Secure WebSocket HTTPS + // + // port + 6 port + 7 + // + //-------------------------------------------------------------------------- + { + // Create a WebSocket port + // + auto wsp = instance.make_port( + ec, + endpoint_type{addr, + static_cast(port + 6)}, + instance, + std::cout, + cert.get(), + set_ws_options{pmd} + ); + + if(ec) + return fail("ws_async_port", ec); + + // Create an HTTP port + // + auto sp = instance.make_port, + file_service + >>( + ec, + endpoint_type{addr, + static_cast(port + 7)}, + instance, + std::cout, + cert.get()); + + if(ec) + return fail("https_async_port", ec); + + // Init the ws_upgrade_service to + // forward upgrades to the WebSocket port. + // + sp->template init<0>( + ec, + *wsp // The websocket port handler + ); + + if(ec) + return fail("https_async_port/ws_upgrade_service", ec); + + // Init the file_service to point to the root path. + // + sp->template init<1>( + ec, + root, // The root path + "https_async_port" // The value for the Server field + ); + + if(ec) + return fail("https_async_port/file_service", ec); + } + + //-------------------------------------------------------------------------- + // + // Multi-Port HTTP, WebSockets, + // HTTPS Secure WebSockets + // + // Asynchronous, all on the same port! + // + // port + 8 + // + //-------------------------------------------------------------------------- + { + // Create a multi_port + // + auto sp = instance.make_port, + file_service + >>( + ec, + endpoint_type{addr, + static_cast(port + 8)}, + instance, + std::cout, + cert.get(), + set_ws_options{pmd}); + + if(ec) + return fail("multi_port", ec); + + // Init the ws_upgrade_service to forward requests to the multi_port. + // + sp->template init<0>( + ec, + *sp // The websocket port handler + ); + + if(ec) + return fail("multi_port/ws_upgrade_service", ec); + + // Init the ws_upgrade_service to + // forward upgrades to the Multi port. + // + sp->template init<1>( + ec, + root, // The root path + "multi_port" // The value for the Server field + ); + + if(ec) + return fail("multi_port/file_service", ec); + } + +#endif + + sig_wait(); +} diff --git a/src/beast/example/server-framework/multi_port.hpp b/src/beast/example/server-framework/multi_port.hpp new file mode 100644 index 0000000000..3774133229 --- /dev/null +++ b/src/beast/example/server-framework/multi_port.hpp @@ -0,0 +1,397 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_EXAMPLE_SERVER_MULTI_PORT_HPP +#define BEAST_EXAMPLE_SERVER_MULTI_PORT_HPP + +#include "ws_async_port.hpp" +#include "http_async_port.hpp" +#include "https_ports.hpp" +#include "wss_ports.hpp" + +#include "../common/detect_ssl.hpp" + +#include + +#include + +namespace framework { + +// A connection that detects an opening SSL handshake +// +// If the SSL handshake is detected, then an HTTPS connection object +// is move constructed from this object. Otherwise, this object continues +// as a normal unencrypted HTTP connection. If the underlying port has +// the ws_upgrade_service configured, the connection may be optionally +// be upgraded to WebSocket by the client. +// +template +class multi_con + + // Give this object the enable_shared_from_this, and have + // the base class call impl().shared_from_this(). The reason + // is so that the shared_ptr has the correct type. This lets + // the derived class (this class) use its members in calls to + // `std::bind`, without an ugly call to `dynamic_downcast` or + // other nonsense. + // + : public std::enable_shared_from_this> + + // The stream should be created before the base class so + // use the "base from member" idiom. + // + , public base_from_member + + // Constructs last, destroys first + // + , public async_http_con_base, Services...> +{ + // Context to use if we get an SSL handshake + boost::asio::ssl::context& ctx_; + + // Holds the data we read during ssl detection + beast::static_buffer_n<6> buffer_; + +public: + // Constructor + // + // Additional arguments are simply forwarded to the base class + // + template + multi_con( + socket_type&& sock, + boost::asio::ssl::context& ctx, + Args&&... args) + : base_from_member(std::move(sock)) + , async_http_con_base, Services...>(std::forward(args)...) + , ctx_(ctx) + { + } + + // Returns the stream. + // + // The base class calls this to obtain the object to use for + // reading and writing HTTP messages. This allows the same base + // class to work with different return types for `stream()` such + // as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&` + // + socket_type& + stream() + { + return this->member; + } + + // Called by the port to launch the connection in detect mode + void + detect() + { + // The detect function operates asynchronously by reading + // in some data from the stream to figure out if its an SSL + // handshake. When it completes, it informs us of the result + // and also stores the bytes it read in the buffer. + // + async_detect_ssl( + stream(), + buffer_, + this->strand_.wrap( + std::bind( + &multi_con::on_detect, + this->shared_from_this(), + std::placeholders::_1, + std::placeholders::_2))); + } + +private: + // Base class needs to be a friend to call our private members + friend class async_http_con_base, Services...>; + + // Called when the handshake detection is complete + // + void + on_detect( + error_code ec, + boost::tribool result) + { + // Report failures if any + if(ec) + return this->fail("on_detect", ec); + + // Was an SSL handshake detected? + if(result) + { + // Yes, get the remote endpoint since it is + // needed to construct the new connection. + // + endpoint_type ep = stream().remote_endpoint(ec); + if(ec) + return this->fail("remote_endpoint", ec); + + // Now launch our new connection object + // + std::make_shared>( + std::move(stream()), + ctx_, + "multi_port", + this->log_, + this->services_, + this->id_, + ep)->handshake(buffer_.data()); + + // When we return the last shared pointer to this + // object will go away and `*this` will be destroyed. + // + return; + } + + // No SSL handshake, so start the HTTP connection normally. + // + // Since we read some bytes from the connection that might + // contain an HTTP request, we pass the buffer holding those + // bytes to the base class so it can use them. + // + this->run(buffer_.data()); + } + + // This is called by the base before running the main loop. + // + void + do_handshake() + { + // Just run the main loop right away. + // + this->do_run(); + } + + // This is called when the other end closes the connection gracefully. + // + void + do_shutdown() + { + // Attempt a clean TCP/IP shutdown + // + error_code ec; + stream().shutdown( + socket_type::shutdown_both, + ec); + + // not_connected happens under normal + // circumstances so don't bother reporting it. + // + if(ec && ec != beast::errc::not_connected) + return this->fail("shutdown", ec); + } +}; + +//------------------------------------------------------------------------------ + +/* An asynchronous HTTP and WebSocket port handler, plain or SSL + + This type meets the requirements of @b PortHandler. It supports a + variable list of HTTP services in its template parameter list, + and provides a synchronous connection implementation to service. + + The port will automatically detect OpenSSL handshakes and establish + encrypted connections, otherwise will use a plain unencrypted + connection. This all happens through the same port. + + In addition this port can process WebSocket upgrade requests by + launching them as a new asynchronous WebSocket connection using + either plain or OpenSSL transport. + + This class is split up into two parts, the multi_port_base, + and the multi_port, to avoid a recursive type reference when + we name the type of the ws_upgrade_service. +*/ +class multi_port_base +{ +protected: + // VFALCO We use boost::function to work around a compiler + // crash with gcc and clang using libstdc++ + + // The types of the on_stream callback + using on_new_stream_cb1 = boost::function&)>; + using on_new_stream_cb2 = boost::function>&)>; + + // Reference to the server instance that made us + server& instance_; + + // The stream to log to + std::ostream& log_; + + // The context holds the SSL certificates the server uses + boost::asio::ssl::context& ctx_; + + // Called for each new websocket stream + on_new_stream_cb1 cb1_; + on_new_stream_cb2 cb2_; + +public: + /** Constructor + + @param instance The server instance which owns this port + + @param log The stream to use for logging + + @param ctx The SSL context holding the SSL certificates to use + + @param cb A callback which will be invoked for every new + WebSocket connection. This provides an opportunity to change + the settings on the stream before it is used. The callback + should have this equivalent signature: + @code + template + void callback(beast::websocket::stream&); + @endcode + In C++14 this can be accomplished with a generic lambda. In + C++11 it will be necessary to write out a lambda manually, + with a templated operator(). + */ + template + multi_port_base( + server& instance, + std::ostream& log, + boost::asio::ssl::context& ctx, + Callback const& cb) + : instance_(instance) + , log_(log) + , ctx_(ctx) + , cb1_(cb) + , cb2_(cb) + { + } + + /** Accept a WebSocket upgrade request. + + This is used to accept a connection that has already + delivered the handshake. + + @param stream The stream corresponding to the connection. + + @param ep The remote endpoint. + + @param req The upgrade request. + */ + template + void + on_upgrade( + socket_type&& sock, + endpoint_type ep, + beast::http::request&& req) + { + // Create the connection and call the version of + // run that takes the request since we have it already + // + std::make_shared( + std::move(sock), + "multi_port", + log_, + instance_.next_id(), + ep, + cb1_ + )->run(std::move(req)); + } + + /** Accept a WebSocket upgrade request. + + This is used to accept a connection that has already + delivered the handshake. + + @param stream The stream corresponding to the connection. + + @param ep The remote endpoint. + + @param req The upgrade request. + */ + template + void + on_upgrade( + ssl_stream&& stream, + endpoint_type ep, + beast::http::request&& req) + { + std::make_shared( + std::move(stream), + "multi_port", + log_, + instance_.next_id(), + ep, + cb2_)->run(std::move(req)); + } +}; + +/* An asynchronous HTTP and WebSocket port handler, plain or SSL + + This class is the other half of multi_port_base. It gets the + Services... variadic type list and owns the service list. +*/ +template +class multi_port : public multi_port_base +{ + // The list of services connections created from this port will support + service_list services_; + +public: + /** Constructor + + All arguments are forwarded to the multi_port_base constructor. + */ + template + multi_port(Args&&... args) + : multi_port_base(std::forward(args)...) + { + } + + /** Initialize a service + + Every service in the list must be initialized exactly once. + + @param args Optional arguments forwarded to the service + constructor. + + @tparam Index The 0-based index of the service to initialize. + + @return A reference to the service list. This permits + calls to be chained in a single expression. + */ + template + void + init(error_code& ec, Args&&... args) + { + services_.template init( + ec, + std::forward(args)...); + } + + /** Called by the server to provide ownership of the socket for a new connection + + @param sock The socket whose ownership is to be transferred + + @ep The remote endpoint + */ + void + on_accept( + socket_type&& sock, + endpoint_type ep) + { + // Create a plain http connection object by transferring + // ownership of the socket, then launch it to perform + // the SSL handshake detection. + // + std::make_shared>( + std::move(sock), + ctx_, + "multi_port", + log_, + services_, + instance_.next_id(), + ep)->detect(); + } +}; + +} // framework + +#endif diff --git a/src/beast/example/server-framework/server.hpp b/src/beast/example/server-framework/server.hpp new file mode 100644 index 0000000000..6157714c0f --- /dev/null +++ b/src/beast/example/server-framework/server.hpp @@ -0,0 +1,266 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_EXAMPLE_FRAMEWORK_SERVER_HPP +#define BEAST_EXAMPLE_FRAMEWORK_SERVER_HPP + +#include "framework.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace framework { + +/** A server instance that accepts TCP/IP connections. + + This is a general purpose TCP/IP server which contains + zero or more user defined "ports". Each port represents + a listening socket whose behavior is defined by an + instance of the @b PortHandler concept. + + To use the server, construct the class and then add the + ports that you want using @ref make_port. + + @par Example + + @code + + // Create a server with 4 threads + // + framework::server si(4); + + // Create a port that echoes everything back. + // Bind all available interfaces on port 1000. + // + framework::error_code ec; + si.make_port( + ec, + server::endpoint_type{ + server::address_type::from_string("0.0.0.0"), 1000} + ); + + ... + + // Close all connections, shut down the server + si.stop(); + + @endcode +*/ +class server +{ + io_service_type ios_; + std::vector tv_; + boost::optional work_; + +public: + server(server const&) = delete; + server& operator=(server const&) = delete; + + /** Constructor + + @param n The number of threads to run on the `io_service`, + which must be greater than zero. + */ + explicit + server(std::size_t n = 1) + : work_(ios_) + { + if(n < 1) + throw std::invalid_argument{"threads < 1"}; + tv_.reserve(n); + while(n--) + tv_.emplace_back( + [&] + { + ios_.run(); + }); + } + + /** Destructor + + Upon destruction, the `io_service` will be stopped + and all pending completion handlers destroyed. + */ + ~server() + { + work_ = boost::none; + ios_.stop(); + for(auto& t : tv_) + t.join(); + } + + /// Return the `io_service` associated with the server + boost::asio::io_service& + get_io_service() + { + return ios_; + } + + /** Return a new, small integer unique id + + These ids are used to uniquely identify connections + in log output. + */ + std::size_t + next_id() + { + static std::atomic id_{0}; + return ++id_; + } + + /** Create a listening port. + + @param ec Set to the error, if any occurred. + + @param ep The address and port to bind to. + + @param args Optional arguments, forwarded to the + port handler's constructor. + + @tparam PortHandler The port handler to use for handling + incoming connections on this port. This handler must meet + the requirements of @b PortHandler. A model of PortHandler + is as follows: + + @code + + struct PortHandler + { + void + on_accept( + endpoint_type ep, // address of the remote endpoint + socket_type&& sock, // the connected socket + ); + }; + + @endcode + */ + template + std::shared_ptr + make_port( + error_code& ec, + endpoint_type const& ep, + Args&&... args); +}; + +//------------------------------------------------------------------------------ + +/* This implementation class wraps the PortHandler and + manages the listening socket. Upon an incoming connection + it transfers ownership of the socket to the PortHandler. +*/ +template +class port + : public std::enable_shared_from_this< + port> +{ + server& instance_; + PortHandler handler_; + endpoint_type ep_; + strand_type strand_; + acceptor_type acceptor_; + socket_type sock_; + +public: + // Constructor + // + // args are forwarded to the PortHandler + // + template + explicit + port(server& instance, Args&&... args) + : instance_(instance) + , handler_(std::forward(args)...) + , strand_(instance.get_io_service()) + , acceptor_(instance.get_io_service()) + , sock_(instance.get_io_service()) + { + } + + // Return the PortHandler wrapped in a shared_ptr + // + std::shared_ptr + handler() + { + // This uses a feature of std::shared_ptr invented by + // Peter Dimov where the managed object piggy backs off + // the reference count of another object containing it. + // + return std::shared_ptr( + this->shared_from_this(), &handler_); + } + + // Open the listening socket + // + void + open(endpoint_type const& ep, error_code& ec) + { + acceptor_.open(ep.protocol(), ec); + if(ec) + return; + acceptor_.set_option( + boost::asio::socket_base::reuse_address{true}); + acceptor_.bind(ep, ec); + if(ec) + return; + acceptor_.listen( + boost::asio::socket_base::max_connections, ec); + if(ec) + return; + acceptor_.async_accept(sock_, ep_, + std::bind(&port::on_accept, this->shared_from_this(), + std::placeholders::_1)); + } + +private: + // Called when an incoming connection is accepted + // + void + on_accept(error_code ec) + { + if(! acceptor_.is_open()) + return; + if(ec == boost::asio::error::operation_aborted) + return; + if(! ec) + { + // Transfer ownership of the socket to the PortHandler + // + handler_.on_accept(std::move(sock_), ep_); + } + acceptor_.async_accept(sock_, ep_, + std::bind(&port::on_accept, this->shared_from_this(), + std::placeholders::_1)); + } +}; + +//------------------------------------------------------------------------------ + +template +std::shared_ptr +server:: +make_port( + error_code& ec, + endpoint_type const& ep, + Args&&... args) +{ + auto sp = std::make_shared>( + *this, std::forward(args)...); + sp->open(ep, ec); + if(ec) + return nullptr; + return sp->handler(); +} + +} // framework + +#endif diff --git a/src/beast/example/server-framework/service_list.hpp b/src/beast/example/server-framework/service_list.hpp new file mode 100644 index 0000000000..71d73ae793 --- /dev/null +++ b/src/beast/example/server-framework/service_list.hpp @@ -0,0 +1,192 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_EXAMPLE_SERVER_SERVICE_LIST_HPP +#define BEAST_EXAMPLE_SERVER_SERVICE_LIST_HPP + +#include "framework.hpp" + +#include +#include +#include + +namespace framework { + +/** A list of HTTP services which may process requests. + + When a service is invoked, it is provided with the stream and + endpoint metadata in addtion to an HTTP request. The service + decides whether or not the process the request, returning + `true` if the request is processed or `false` if it does not + process the request. + + @see file_service, ws_upgrade_service +*/ +template +class service_list +{ + // This helper is for tag-dispatching tuple index + template + using C = std::integral_constant; + + // Each service is wrapped in a boost::optional so we + // can construct them one by one later, instead of + // having to construct them all at once. + // + std::tuple...> list_; + +public: + /// Constructor + service_list() = default; + + /// Constructor + service_list(service_list&&) = default; + + /// Constructor + service_list(service_list const&) = default; + + /** Initialize a service. + + Every service in the list must be initialized exactly once + before the service list is invoked. + + @param args Optional arguments forwarded to the service + constructor. + + @tparam Index The 0-based index of the service to initialize. + + @return A reference to the service list. This permits + calls to be chained in a single expression. + */ + template + void + init(error_code& ec, Args&&... args) + { + // First, construct the service inside the optional + std::get(list_).emplace(std::forward(args)...); + + // Now allow the service to finish the initialization + std::get(list_)->init(ec); + } + + /** Handle a request. + + This function attempts to process the given HTTP request by + invoking each service one at a time starting with the first + service in the list. When a service indicates that it handles + the request, by returning `true`, the function stops and + returns the value `true`. Otherwise, if no service handles + the request then the function returns the value `false`. + + @param stream The stream belonging to the connection. A service + which handles the request may optionally take ownership of the + stream. + + @param ep The remote endpoint of the connection corresponding + to the stream. + + @param req The request message to attempt handling. A service + which handles the request may optionally take ownership of the + message. + + @param send The function to invoke with the response. The function + should have this equivalent signature: + + @code + + template + void + send(response&&); + + @endcode + + In C++14 this can be expressed using a generic lambda. In + C++11 it will require a template member function of an invocable + object. + + @return `true` if the request was handled by a service. + */ + template< + class Stream, + class Body, + class Send> + bool + respond( + Stream&& stream, + endpoint_type const& ep, + beast::http::request&& req, + Send const& send) const + { + return try_respond( + std::move(stream), + ep, + std::move(req), + send, C<0>{}); + } + +private: + /* The implementation of `try_respond` is implemented using + tail recursion which can usually be optimized away to + something resembling a switch statement. + */ + template< + class Stream, + class Body, + class Send> + bool + try_respond( + Stream&&, + endpoint_type const&, + beast::http::request&&, + Send const&, + C const&) const + { + // This function breaks the recursion for the case where + // where the Index is one past the last type in the list. + // + return false; + } + + // Invoke the I-th type in the type list + // + template< + class Stream, + class Body, + class Send, + std::size_t I> + bool + try_respond( + Stream&& stream, + endpoint_type const& ep, + beast::http::request&& req, + Send const& send, + C const&) const + { + // If the I-th service handles the request then return + // + if(std::get(list_)->respond( + std::move(stream), + ep, + std::move(req), + send)) + return true; + + // Try the I+1th service. If I==sizeof...(Services) + // then we call the other overload and return false. + // + return try_respond( + std::move(stream), + ep, + std::move(req), + send, + C{}); + } +}; + +} // framework + +#endif diff --git a/src/beast/example/server-framework/ssl_certificate.hpp b/src/beast/example/server-framework/ssl_certificate.hpp new file mode 100644 index 0000000000..aaa2e0b853 --- /dev/null +++ b/src/beast/example/server-framework/ssl_certificate.hpp @@ -0,0 +1,146 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_EXAMPLE_SERVER_SSL_CERTIFICATE_HPP +#define BEAST_EXAMPLE_SERVER_SSL_CERTIFICATE_HPP + +#include +#include +#include +#include + +namespace framework { + +// This sets up the self-signed certificate that the server +// uses for its encrypted connections + +class ssl_certificate +{ + // The template argument is gratuitous, to + // make the definition header-only without + // also making it inline. + // + template + void + construct(); + + boost::asio::ssl::context ctx_; + +public: + ssl_certificate() + : ctx_(boost::asio::ssl::context::sslv23) + { + construct(); + } + + boost::asio::ssl::context& + get() + { + return ctx_; + } +}; + +template +void +ssl_certificate::construct() +{ + /* + The certificate was generated from CMD.EXE on Windows 10 using: + + winpty openssl dhparam -out dh.pem 2048 + winpty openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 10000 -out cert.pem -subj "//C=US\ST=CA\L=Los Angeles\O=Beast\CN=www.example.com" + */ + + std::string const cert = + "-----BEGIN CERTIFICATE-----\n" + "MIIDaDCCAlCgAwIBAgIJAO8vBu8i8exWMA0GCSqGSIb3DQEBCwUAMEkxCzAJBgNV\n" + "BAYTAlVTMQswCQYDVQQIDAJDQTEtMCsGA1UEBwwkTG9zIEFuZ2VsZXNPPUJlYXN0\n" + "Q049d3d3LmV4YW1wbGUuY29tMB4XDTE3MDUwMzE4MzkxMloXDTQ0MDkxODE4Mzkx\n" + "MlowSTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMS0wKwYDVQQHDCRMb3MgQW5n\n" + "ZWxlc089QmVhc3RDTj13d3cuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUA\n" + "A4IBDwAwggEKAoIBAQDJ7BRKFO8fqmsEXw8v9YOVXyrQVsVbjSSGEs4Vzs4cJgcF\n" + "xqGitbnLIrOgiJpRAPLy5MNcAXE1strVGfdEf7xMYSZ/4wOrxUyVw/Ltgsft8m7b\n" + "Fu8TsCzO6XrxpnVtWk506YZ7ToTa5UjHfBi2+pWTxbpN12UhiZNUcrRsqTFW+6fO\n" + "9d7xm5wlaZG8cMdg0cO1bhkz45JSl3wWKIES7t3EfKePZbNlQ5hPy7Pd5JTmdGBp\n" + "yY8anC8u4LPbmgW0/U31PH0rRVfGcBbZsAoQw5Tc5dnb6N2GEIbq3ehSfdDHGnrv\n" + "enu2tOK9Qx6GEzXh3sekZkxcgh+NlIxCNxu//Dk9AgMBAAGjUzBRMB0GA1UdDgQW\n" + "BBTZh0N9Ne1OD7GBGJYz4PNESHuXezAfBgNVHSMEGDAWgBTZh0N9Ne1OD7GBGJYz\n" + "4PNESHuXezAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCmTJVT\n" + "LH5Cru1vXtzb3N9dyolcVH82xFVwPewArchgq+CEkajOU9bnzCqvhM4CryBb4cUs\n" + "gqXWp85hAh55uBOqXb2yyESEleMCJEiVTwm/m26FdONvEGptsiCmF5Gxi0YRtn8N\n" + "V+KhrQaAyLrLdPYI7TrwAOisq2I1cD0mt+xgwuv/654Rl3IhOMx+fKWKJ9qLAiaE\n" + "fQyshjlPP9mYVxWOxqctUdQ8UnsUKKGEUcVrA08i1OAnVKlPFjKBvk+r7jpsTPcr\n" + "9pWXTO9JrYMML7d+XRSZA1n3856OqZDX4403+9FnXCvfcLZLLKTBvwwFgEFGpzjK\n" + "UEVbkhd5qstF6qWK\n" + "-----END CERTIFICATE-----\n"; + + std::string const key = + "-----BEGIN PRIVATE KEY-----\n" + "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDJ7BRKFO8fqmsE\n" + "Xw8v9YOVXyrQVsVbjSSGEs4Vzs4cJgcFxqGitbnLIrOgiJpRAPLy5MNcAXE1strV\n" + "GfdEf7xMYSZ/4wOrxUyVw/Ltgsft8m7bFu8TsCzO6XrxpnVtWk506YZ7ToTa5UjH\n" + "fBi2+pWTxbpN12UhiZNUcrRsqTFW+6fO9d7xm5wlaZG8cMdg0cO1bhkz45JSl3wW\n" + "KIES7t3EfKePZbNlQ5hPy7Pd5JTmdGBpyY8anC8u4LPbmgW0/U31PH0rRVfGcBbZ\n" + "sAoQw5Tc5dnb6N2GEIbq3ehSfdDHGnrvenu2tOK9Qx6GEzXh3sekZkxcgh+NlIxC\n" + "Nxu//Dk9AgMBAAECggEBAK1gV8uETg4SdfE67f9v/5uyK0DYQH1ro4C7hNiUycTB\n" + "oiYDd6YOA4m4MiQVJuuGtRR5+IR3eI1zFRMFSJs4UqYChNwqQGys7CVsKpplQOW+\n" + "1BCqkH2HN/Ix5662Dv3mHJemLCKUON77IJKoq0/xuZ04mc9csykox6grFWB3pjXY\n" + "OEn9U8pt5KNldWfpfAZ7xu9WfyvthGXlhfwKEetOuHfAQv7FF6s25UIEU6Hmnwp9\n" + "VmYp2twfMGdztz/gfFjKOGxf92RG+FMSkyAPq/vhyB7oQWxa+vdBn6BSdsfn27Qs\n" + "bTvXrGe4FYcbuw4WkAKTljZX7TUegkXiwFoSps0jegECgYEA7o5AcRTZVUmmSs8W\n" + "PUHn89UEuDAMFVk7grG1bg8exLQSpugCykcqXt1WNrqB7x6nB+dbVANWNhSmhgCg\n" + "VrV941vbx8ketqZ9YInSbGPWIU/tss3r8Yx2Ct3mQpvpGC6iGHzEc/NHJP8Efvh/\n" + "CcUWmLjLGJYYeP5oNu5cncC3fXUCgYEA2LANATm0A6sFVGe3sSLO9un1brA4zlZE\n" + "Hjd3KOZnMPt73B426qUOcw5B2wIS8GJsUES0P94pKg83oyzmoUV9vJpJLjHA4qmL\n" + "CDAd6CjAmE5ea4dFdZwDDS8F9FntJMdPQJA9vq+JaeS+k7ds3+7oiNe+RUIHR1Sz\n" + "VEAKh3Xw66kCgYB7KO/2Mchesu5qku2tZJhHF4QfP5cNcos511uO3bmJ3ln+16uR\n" + "GRqz7Vu0V6f7dvzPJM/O2QYqV5D9f9dHzN2YgvU9+QSlUeFK9PyxPv3vJt/WP1//\n" + "zf+nbpaRbwLxnCnNsKSQJFpnrE166/pSZfFbmZQpNlyeIuJU8czZGQTifQKBgHXe\n" + "/pQGEZhVNab+bHwdFTxXdDzr+1qyrodJYLaM7uFES9InVXQ6qSuJO+WosSi2QXlA\n" + "hlSfwwCwGnHXAPYFWSp5Owm34tbpp0mi8wHQ+UNgjhgsE2qwnTBUvgZ3zHpPORtD\n" + "23KZBkTmO40bIEyIJ1IZGdWO32q79nkEBTY+v/lRAoGBAI1rbouFYPBrTYQ9kcjt\n" + "1yfu4JF5MvO9JrHQ9tOwkqDmNCWx9xWXbgydsn/eFtuUMULWsG3lNjfst/Esb8ch\n" + "k5cZd6pdJZa4/vhEwrYYSuEjMCnRb0lUsm7TsHxQrUd6Fi/mUuFU/haC0o0chLq7\n" + "pVOUFq5mW8p0zbtfHbjkgxyF\n" + "-----END PRIVATE KEY-----\n"; + + std::string const dh = + "-----BEGIN DH PARAMETERS-----\n" + "MIIBCAKCAQEArzQc5mpm0Fs8yahDeySj31JZlwEphUdZ9StM2D8+Fo7TMduGtSi+\n" + "/HRWVwHcTFAgrxVdm+dl474mOUqqaz4MpzIb6+6OVfWHbQJmXPepZKyu4LgUPvY/\n" + "4q3/iDMjIS0fLOu/bLuObwU5ccZmDgfhmz1GanRlTQOiYRty3FiOATWZBRh6uv4u\n" + "tff4A9Bm3V9tLx9S6djq31w31Gl7OQhryodW28kc16t9TvO1BzcV3HjRPwpe701X\n" + "oEEZdnZWANkkpR/m/pfgdmGPU66S2sXMHgsliViQWpDCYeehrvFRHEdR9NV+XJfC\n" + "QMUk26jPTIVTLfXmmwU0u8vUkpR7LQKkwwIBAg==\n" + "-----END DH PARAMETERS-----\n"; + + ctx_.set_password_callback( + [](std::size_t, + boost::asio::ssl::context_base::password_purpose) + { + return "test"; + }); + + ctx_.set_options( + boost::asio::ssl::context::default_workarounds | + boost::asio::ssl::context::no_sslv2 | + boost::asio::ssl::context::single_dh_use); + + ctx_.use_certificate_chain( + boost::asio::buffer(cert.data(), cert.size())); + + ctx_.use_private_key( + boost::asio::buffer(key.data(), key.size()), + boost::asio::ssl::context::file_format::pem); + + ctx_.use_tmp_dh( + boost::asio::buffer(dh.data(), dh.size())); +} + +} // framework + +#endif diff --git a/src/beast/example/server-framework/ws_async_port.hpp b/src/beast/example/server-framework/ws_async_port.hpp new file mode 100644 index 0000000000..c799ab9f89 --- /dev/null +++ b/src/beast/example/server-framework/ws_async_port.hpp @@ -0,0 +1,374 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_EXAMPLE_SERVER_WS_ASYNC_PORT_HPP +#define BEAST_EXAMPLE_SERVER_WS_ASYNC_PORT_HPP + +#include "server.hpp" + +#include +#include +#include +#include +#include + +namespace framework { + +// This object holds the state of the connection +// including, most importantly, the socket or stream. +// +// +template +class async_ws_con_base +{ + // This function lets us access members of the derived class + Derived& + impl() + { + return static_cast(*this); + } + + // The string used to set the Server http field + std::string server_name_; + + // The stream to use for logging + std::ostream& log_; + + // A small unique integer for logging + std::size_t id_; + + // The remote endpoint. We cache it here because + // calls to remote_endpoint() can fail / throw. + // + endpoint_type ep_; + + // This is used to hold the message data + beast::multi_buffer buffer_; + +protected: + // The strand makes sure that our data is + // accessed from only one thread at a time. + // + strand_type strand_; + +public: + // Constructor + template + async_ws_con_base( + beast::string_view server_name, + std::ostream& log, + std::size_t id, + endpoint_type const& ep, + Callback const& cb) + : server_name_(server_name) + , log_(log) + , id_(id) + , ep_(ep) + + // Limit of 1MB on messages + , buffer_(1024 * 1024) + + , strand_(impl().stream().get_io_service()) + { + cb(impl().stream()); + } + + // Run the connection + // + void + run() + { + impl().do_handshake(); + } + + // Run the connection. + // + // This overload handles the case where we + // already have the WebSocket Upgrade request. + // + template + void + run(beast::http::request const& req) + { + // Call the overload of accept() which takes + // the request by parameter, instead of reading + // it from the network. + // + impl().stream().async_accept_ex(req, + [&](beast::websocket::response_type& res) + { + res.set(beast::http::field::server, server_name_); + }, + strand_.wrap(std::bind( + &async_ws_con_base::on_accept, + impl().shared_from_this(), + std::placeholders::_1))); + } + +protected: + // Performs the WebSocket handshake + void + do_accept() + { + // Read the WebSocket upgrade request and attempt + // to send back the response. + // + impl().stream().async_accept_ex( + [&](beast::websocket::response_type& res) + { + res.set(beast::http::field::server, server_name_); + }, + strand_.wrap(std::bind( + &async_ws_con_base::on_accept, + impl().shared_from_this(), + std::placeholders::_1))); + } + + // This helper reports failures + // + void + fail(std::string what, error_code ec) + { + if(ec != beast::websocket::error::closed) + log_ << + "[#" << id_ << " " << ep_ << "] " << + what << ": " << ec.message() << std::endl; + } + +private: + // Called when accept_ex completes + // + void + on_accept(error_code ec) + { + if(ec) + return fail("async_accept", ec); + do_read(); + } + + // Read the next WebSocket message + // + void + do_read() + { + impl().stream().async_read( + buffer_, + strand_.wrap(std::bind( + &async_ws_con_base::on_read, + impl().shared_from_this(), + std::placeholders::_1))); + } + + // Called when the message read completes + // + void + on_read(error_code ec) + { + if(ec) + return fail("on_read", ec); + + // Set the outgoing message type. We will use + // the same setting as the message we just read. + // + impl().stream().binary(impl().stream().got_binary()); + + // Now echo back the message + // + impl().stream().async_write( + buffer_.data(), + strand_.wrap(std::bind( + &async_ws_con_base::on_write, + impl().shared_from_this(), + std::placeholders::_1))); + } + + // Called when the message write completes + // + void + on_write(error_code ec) + { + if(ec) + return fail("on_write", ec); + + // Empty out the contents of the message buffer + // to prepare it for the next call to read. + // + buffer_.consume(buffer_.size()); + + // Now read another message + // + do_read(); + } +}; + +//------------------------------------------------------------------------------ + +// This class represents an asynchronous WebSocket connection +// which uses a plain TCP/IP socket (no encryption) as the stream. +// +class async_ws_con + + // Give this object the enable_shared_from_this, and have + // the base class call impl().shared_from_this(). The reason + // is so that the shared_ptr has the correct type. This lets + // the derived class (this class) use its members in calls to + // `std::bind`, without an ugly call to `dynamic_downcast` or + // other nonsense. + // + : public std::enable_shared_from_this + + // The stream should be created before the base class so + // use the "base from member" idiom. + // + , public base_from_member> + + // Constructs last, destroys first + // + , public async_ws_con_base +{ +public: + // Constructor + // + // Additional arguments are forwarded to the base class + // + template + explicit + async_ws_con( + socket_type&& sock, + Args&&... args) + : base_from_member>(std::move(sock)) + , async_ws_con_base(std::forward(args)...) + { + } + + // Returns the stream. + // + // The base class calls this to obtain the object to use for + // reading and writing HTTP messages. This allows the same base + // class to work with different return types for `stream()` such + // as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&` + // + beast::websocket::stream& + stream() + { + return this->member; + } + +private: + // Base class needs to be a friend to call our private members + friend async_ws_con_base; + + void + do_handshake() + { + do_accept(); + } +}; + +//------------------------------------------------------------------------------ + +/** An asynchronous WebSocket @b PortHandler which implements echo. + + This is a port handler which accepts WebSocket upgrade HTTP + requests and implements the echo protocol. All received + WebSocket messages will be echoed back to the remote host. +*/ +class ws_async_port +{ + // The type of the on_new_stream callback + // + using on_new_stream_cb = + boost::function&)>; + + server& instance_; + std::ostream& log_; + on_new_stream_cb cb_; + +public: + /** Constructor + + @param instance The server instance which owns this port + + @param log The stream to use for logging + + @param cb A callback which will be invoked for every new + WebSocket connection. This provides an opportunity to change + the settings on the stream before it is used. The callback + should have this equivalent signature: + @code + template + void callback(beast::websocket::stream&); + @endcode + In C++14 this can be accomplished with a generic lambda. In + C++11 it will be necessary to write out a lambda manually, + with a templated operator(). + */ + template + ws_async_port( + server& instance, + std::ostream& log, + Callback const& cb) + : instance_(instance) + , log_(log) + , cb_(cb) + { + } + + /** Accept a TCP/IP connection. + + This function is called when the server has accepted an + incoming connection. + + @param sock The connected socket. + + @param ep The endpoint of the remote host. + */ + void + on_accept( + socket_type&& sock, + endpoint_type ep) + { + std::make_shared( + std::move(sock), + "ws_async_port", + log_, + instance_.next_id(), + ep, + cb_)->run(); + } + + /** Accept a WebSocket upgrade request. + + This is used to accept a connection that has already + delivered the handshake. + + @param stream The stream corresponding to the connection. + + @param ep The remote endpoint. + + @param req The upgrade request. + */ + template + void + on_upgrade( + socket_type&& sock, + endpoint_type ep, + beast::http::request&& req) + { + std::make_shared( + std::move(sock), + "ws_async_port", + log_, + instance_.next_id(), + ep, + cb_)->run(std::move(req)); + } +}; + +} // framework + +#endif diff --git a/src/beast/example/server-framework/ws_sync_port.hpp b/src/beast/example/server-framework/ws_sync_port.hpp new file mode 100644 index 0000000000..4ebea95fa5 --- /dev/null +++ b/src/beast/example/server-framework/ws_sync_port.hpp @@ -0,0 +1,432 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_EXAMPLE_SERVER_WS_SYNC_PORT_HPP +#define BEAST_EXAMPLE_SERVER_WS_SYNC_PORT_HPP + +#include "server.hpp" + +#include +#include +#include +#include +#include +#include + +namespace framework { + +/** A synchronous WebSocket connection. + + This base class implements a WebSocket connection object using + synchronous calls over an unencrypted connection. + + It uses the Curiously Recurring Template pattern (CRTP) where + we refer to the derived class in order to access the stream object + to use for reading and writing. This lets the same class be used + for plain and SSL stream objects. +*/ +template +class sync_ws_con_base +{ + // This function lets us access members of the derived class + Derived& + impl() + { + return static_cast(*this); + } + + // The string used to set the Server http field + std::string server_name_; + + // The stream to use for logging + std::ostream& log_; + + // A small unique integer for logging + std::size_t id_; + + // The remote endpoint. We cache it here because + // calls to remote_endpoint() can fail / throw. + // + endpoint_type ep_; + +public: + // Constructor + // + // Additional arguments are forwarded to the base class + // + template + sync_ws_con_base( + beast::string_view server_name, + std::ostream& log, + std::size_t id, + endpoint_type const& ep, + Callback const& cb) + : server_name_(server_name) + , log_(log) + , id_(id) + , ep_(ep) + { + cb(impl().stream()); + } + + // Run the connection. This is called for the case + // where we have not received the upgrade request yet. + // + void + run() + { + // We run the do_run function in its own thread, + // and bind a shared pointer to the connection object + // into the function. The last reference to the shared + // pointer will go away when the thread exits, thus + // destroying the connection object. + // + std::thread{ + &sync_ws_con_base::do_accept, + impl().shared_from_this() + }.detach(); + } + + // Run the connection from an already-received Upgrade request. + // + template + void + run(beast::http::request&& req) + { + BOOST_ASSERT(beast::websocket::is_upgrade(req)); + + // We need to transfer ownership of the request object into + // the lambda, but there's no C++14 lambda capture + // so we have to write it out by manually specifying the lambda. + // + std::thread{ + lambda{ + impl().shared_from_this(), + std::move(req) + }}.detach(); + } + +protected: + // Called when a failure occurs + // + void + fail(std::string what, error_code ec) + { + // Don't report the "closed" error since that + // happens under normal circumstances. + // + if(ec && ec != beast::websocket::error::closed) + { + log_ << + "[#" << id_ << " " << ep_ << "] " << + what << ": " << ec.message() << std::endl; + log_.flush(); + } + } + +private: + // This function performs the WebSocket handshake + // and runs the main loop upon success. + void + do_accept() + { + error_code ec; + + // Give the derived class a chance to do stuff before we + // enter the main loop. This is for SSL connections really. + // + impl().do_handshake(ec); + + if(ec) + return fail("handshake", ec); + + // Read the WebSocket upgrade request and attempt + // to send back the response. + // + impl().stream().accept_ex( + [&](beast::websocket::response_type& res) + { + res.insert(beast::http::field::server, server_name_); + }, + ec); + + if(ec) + return fail("accept", ec); + + // Run the connection + // + do_run(); + } + + // This is the lambda used when launching a connection from + // an already-received request. In C++14 we could simply use + // a lambda capture but this example requires only C++11 so + // we write out the lambda ourselves. This is similar to what + // the compiler would generate anyway. + // + template + class lambda + { + std::shared_ptr self_; + beast::http::request req_; + + public: + // Constructor + // + // This is the equivalent of the capture section of the lambda. + // + lambda( + std::shared_ptr self, + beast::http::request&& req) + : self_(std::move(self)) + , req_(std::move(req)) + { + BOOST_ASSERT(beast::websocket::is_upgrade(req_)); + } + + // Invoke the lambda + // + void + operator()() + { + BOOST_ASSERT(beast::websocket::is_upgrade(req_)); + error_code ec; + { + // Move the message to the stack so we can get + // rid of resources, otherwise it will linger + // for the lifetime of the connection. + // + auto req = std::move(req_); + + // Call the overload of accept() which takes + // the request by parameter, instead of reading + // it from the network. + // + self_->impl().stream().accept_ex(req, + [&](beast::websocket::response_type& res) + { + res.insert(beast::http::field::server, self_->server_name_); + }, + ec); + } + + if(ec) + return self_->fail("accept", ec); + + self_->do_run(); + } + }; + + void + do_run() + { + error_code ec; + + // Loop, reading messages and echoing them back. + // + for(;;) + { + // This buffer holds the message. We place a one + // megabyte limit on the size to prevent abuse. + // + beast::multi_buffer buffer{1024*1024}; + + // Read the message + // + impl().stream().read(buffer, ec); + + if(ec) + return fail("read", ec); + + // Set the outgoing message type. We will use + // the same setting as the message we just read. + // + impl().stream().binary(impl().stream().got_binary()); + + // Now echo back the message + // + impl().stream().write(buffer.data(), ec); + + if(ec) + return fail("write", ec); + } + } +}; + +//------------------------------------------------------------------------------ + +// This class represents a synchronous WebSocket connection +// which uses a plain TCP/IP socket (no encryption) as the stream. +// +class sync_ws_con + + // Give this object the enable_shared_from_this, and have + // the base class call impl().shared_from_this(). The reason + // is so that the shared_ptr has the correct type. This lets + // the derived class (this class) use its members in calls to + // `std::bind`, without an ugly call to `dynamic_downcast` or + // other nonsense. + // + : public std::enable_shared_from_this + + // The stream should be created before the base class so + // use the "base from member" idiom. + // + , public base_from_member> + + // Constructs last, destroys first + // + , public sync_ws_con_base +{ +public: + // Construct the plain connection. + // + template + explicit + sync_ws_con( + socket_type&& sock, + Args&&... args) + : base_from_member>(std::move(sock)) + , sync_ws_con_base(std::forward(args)...) + { + } + + // Returns the stream. + // + // The base class calls this to obtain the object to use for + // reading and writing HTTP messages. This allows the same base + // class to work with different return types for `stream()` such + // as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&` + // + beast::websocket::stream& + stream() + { + return this->member; + } + +private: + // Base class needs to be a friend to call our private members + friend class sync_ws_con_base; + + // This is called by the base before running the main loop. + // There's nothing to do for a plain connection. + // + void + do_handshake(error_code& ec) + { + // This is required by the specifications for error_code + // + ec = {}; + } +}; + +//------------------------------------------------------------------------------ + +/** A synchronous WebSocket @b PortHandler which implements echo. + + This is a port handler which accepts WebSocket upgrade HTTP + requests and implements the echo protocol. All received + WebSocket messages will be echoed back to the remote host. +*/ +class ws_sync_port +{ + // The type of the on_new_stream callback + // + using on_new_stream_cb = + boost::function&)>; + + server& instance_; + std::ostream& log_; + on_new_stream_cb cb_; + +public: + /** Constructor + + @param instance The server instance which owns this port + + @param log The stream to use for logging + + @param cb A callback which will be invoked for every new + WebSocket connection. This provides an opportunity to change + the settings on the stream before it is used. The callback + should have this equivalent signature: + @code + template + void callback(beast::websocket::stream&); + @endcode + In C++14 this can be accomplished with a generic lambda. In + C++11 it will be necessary to write out a lambda manually, + with a templated operator(). + */ + template + ws_sync_port( + server& instance, + std::ostream& log, + Callback const& cb) + : instance_(instance) + , log_(log) + , cb_(cb) + { + } + + /** Accept a TCP/IP connection. + + This function is called when the server has accepted an + incoming connection. + + @param sock The connected socket. + + @param ep The endpoint of the remote host. + */ + void + on_accept(socket_type&& sock, endpoint_type ep) + { + // Create our connection object and run it + // + std::make_shared( + std::move(sock), + "ws_sync_port", + log_, + instance_.next_id(), + ep, + cb_)->run(); + } + + /** Accept a WebSocket upgrade request. + + This is used to accept a connection that has already + delivered the handshake. + + @param stream The stream corresponding to the connection. + + @param ep The remote endpoint. + + @param req The upgrade request. + */ + template + void + on_upgrade( + socket_type&& sock, + endpoint_type ep, + beast::http::request&& req) + { + // Create the connection object and run it, + // transferring ownership of the ugprade request. + // + std::make_shared( + std::move(sock), + "ws_sync_port", + log_, + instance_.next_id(), + ep, + cb_)->run(std::move(req)); + } +}; + +} // framework + +#endif diff --git a/src/beast/example/server-framework/ws_upgrade_service.hpp b/src/beast/example/server-framework/ws_upgrade_service.hpp new file mode 100644 index 0000000000..d4d8f5d614 --- /dev/null +++ b/src/beast/example/server-framework/ws_upgrade_service.hpp @@ -0,0 +1,101 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_EXAMPLE_SERVER_WS_UPGRADE_SERVICE_HPP +#define BEAST_EXAMPLE_SERVER_WS_UPGRADE_SERVICE_HPP + +#include "framework.hpp" + +#include +#include +#include + +namespace framework { + +/** An HTTP service which transfers WebSocket upgrade request to another port handler. + + @tparam PortHandler The type of port handler. The service will + handle WebSocket Upgrade requests by transferring ownership + of the stream and request to a port handler of this type. +*/ +template +class ws_upgrade_service +{ + PortHandler& handler_; + +public: + /** Constructor + + @param handler A shared pointer to the @b PortHandler to + handle WebSocket upgrade requests. + */ + explicit + ws_upgrade_service(PortHandler& handler) + : handler_(handler) + { + } + + /** Initialize the service. + + This provides an opportunity for the service to perform + initialization which may fail, while reporting an error + code instead of throwing an exception from the constructor. + */ + void + init(error_code& ec) + { + // This is required by the error_code specification + // + ec = {}; + } + + /** Handle a WebSocket Upgrade request. + + If the request is an upgrade request, ownership of the + stream and request will be transferred to the corresponding + WebSocket port handler. + + @param stream The stream corresponding to the connection. + + @param ep The remote endpoint associated with the stream. + + @req The request to check. + */ + template< + class Stream, + class Body, + class Send> + bool + respond( + Stream&& stream, + endpoint_type const& ep, + beast::http::request&& req, + Send const&) const + { + // If its not an upgrade request, return `false` + // to indicate that we are not handling it. + // + if(! beast::websocket::is_upgrade(req)) + return false; + + // Its an ugprade request, so transfer ownership + // of the stream and request to the port handler. + // + handler_.on_upgrade( + std::move(stream), + ep, + std::move(req)); + + // Tell the service list that we handled the request. + // + return true; + } +}; + +} // framework + +#endif diff --git a/src/beast/example/server-framework/wss_ports.hpp b/src/beast/example/server-framework/wss_ports.hpp new file mode 100644 index 0000000000..ff7f87e1ae --- /dev/null +++ b/src/beast/example/server-framework/wss_ports.hpp @@ -0,0 +1,438 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_EXAMPLE_SERVER_WSS_PORTS_HPP +#define BEAST_EXAMPLE_SERVER_WSS_PORTS_HPP + +#include "ws_sync_port.hpp" +#include "ws_async_port.hpp" + +#include "../common/ssl_stream.hpp" + +#include +#include + +namespace framework { + +//------------------------------------------------------------------------------ + +// A synchronous WebSocket connection over an SSL connection +// +class sync_wss_con + + // Give this object the enable_shared_from_this, and have + // the base class call impl().shared_from_this(). The reason + // is so that the shared_ptr has the correct type. This lets + // the derived class (this class) use its members in calls to + // `std::bind`, without an ugly call to `dynamic_downcast` or + // other nonsense. + // + : public std::enable_shared_from_this + + // The stream should be created before the base class so + // use the "base from member" idiom. + // + , public base_from_member>> + + // Constructs last, destroys first + // + , public sync_ws_con_base +{ +public: + // Constructor + // + // Additional arguments are forwarded to the base class + // + template + explicit + sync_wss_con( + socket_type&& sock, + boost::asio::ssl::context& ctx, + Args&&... args) + : base_from_member>>(std::move(sock), ctx) + , sync_ws_con_base(std::forward(args)...) + { + } + + // Construct from an existing, handshaked SSL stream + // + template + sync_wss_con( + ssl_stream&& stream, + Args&&... args) + : base_from_member>>(std::move(stream)) + , sync_ws_con_base(std::forward(args)...) + { + } + + // Returns the stream. + // + // The base class calls this to obtain the object to use for + // reading and writing HTTP messages. This allows the same base + // class to work with different return types for `stream()` such + // as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&` + // + beast::websocket::stream>& + stream() + { + return this->member; + } + +private: + friend class sync_ws_con_base; + + // This is called by the base before running the main loop. + // + void + do_handshake(error_code& ec) + { + // Perform the SSL handshake + // + // We use next_layer() to get at the underlying ssl_stream + // + stream().next_layer().handshake(boost::asio::ssl::stream_base::server, ec); + } +}; + +//------------------------------------------------------------------------------ + +// An asynchronous WebSocket connection over an SSL connection +// +class async_wss_con + + // Give this object the enable_shared_from_this, and have + // the base class call impl().shared_from_this(). The reason + // is so that the shared_ptr has the correct type. This lets + // the derived class (this class) use its members in calls to + // `std::bind`, without an ugly call to `dynamic_downcast` or + // other nonsense. + // + : public std::enable_shared_from_this + + // The stream should be created before the base class so + // use the "base from member" idiom. + // + , public base_from_member>> + + // Constructs last, destroys first + // + , public async_ws_con_base +{ +public: + // Constructor + // + // Additional arguments are forwarded to the base class + // + template + async_wss_con( + socket_type&& sock, + boost::asio::ssl::context& ctx, + Args&&... args) + : base_from_member>>(std::move(sock), ctx) + , async_ws_con_base(std::forward(args)...) + { + } + + // Construct from an existing, handshaked SSL stream + // + template + async_wss_con( + ssl_stream&& stream, + Args&&... args) + : base_from_member>>(std::move(stream)) + , async_ws_con_base(std::forward(args)...) + { + } + + // Returns the stream. + // + // The base class calls this to obtain the object to use for + // reading and writing HTTP messages. This allows the same base + // class to work with different return types for `stream()` such + // as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&` + // + beast::websocket::stream>& + stream() + { + return this->member; + } + +private: + friend class async_ws_con_base; + + // Called by the port to start the connection + // after creating the object + // + void + do_handshake() + { + // This is SSL so perform the handshake first + // + stream().next_layer().async_handshake( + boost::asio::ssl::stream_base::server, + this->strand_.wrap( + std::bind( + &async_wss_con::on_handshake, + this->shared_from_this(), + std::placeholders::_1))); + } + + // Called when the SSL handshake completes + // + void + on_handshake(error_code ec) + { + if(ec) + return this->fail("on_handshake", ec); + + // Move on to accepting the WebSocket handshake + // + this->do_accept(); + } +}; + +//------------------------------------------------------------------------------ + +/** A synchronous Secure WebSocket @b PortHandler which implements echo. + + This is a port handler which accepts Secure WebSocket upgrade + HTTP requests and implements the echo protocol. All received + WebSocket messages will be echoed back to the remote host. +*/ +class wss_sync_port +{ + // VFALCO We use boost::function to work around a compiler + // crash with gcc and clang using libstdc++ + + // The types of the on_new_stream callbacks + // + using on_new_stream_cb1 = + boost::function&)>; + + using on_new_stream_cb2 = + boost::function>&)>; + + server& instance_; + std::ostream& log_; + boost::asio::ssl::context& ctx_; + on_new_stream_cb1 cb1_; + on_new_stream_cb2 cb2_; + +public: + /** Constructor + + @param instance The server instance which owns this port + + @param log The stream to use for logging + + @param ctx The SSL context holding the SSL certificates to use + + @param cb A callback which will be invoked for every new + WebSocket connection. This provides an opportunity to change + the settings on the stream before it is used. The callback + should have this equivalent signature: + @code + template + void callback(beast::websocket::stream&); + @endcode + In C++14 this can be accomplished with a generic lambda. In + C++11 it will be necessary to write out a lambda manually, + with a templated operator(). + */ + template + wss_sync_port( + server& instance, + std::ostream& log, + boost::asio::ssl::context& ctx, + Callback const& cb) + : instance_(instance) + , log_(log) + , ctx_(ctx) + , cb1_(cb) + , cb2_(cb) + { + } + + /** Accept a TCP/IP connection. + + This function is called when the server has accepted an + incoming connection. + + @param sock The connected socket. + + @param ep The endpoint of the remote host. + */ + void + on_accept(socket_type&& sock, endpoint_type ep) + { + // Create our connection object and run it + // + std::make_shared( + std::move(sock), + ctx_, + "wss_sync_port", + log_, + instance_.next_id(), + ep, + cb2_)->run(); + } + + /** Accept a WebSocket upgrade request. + + This is used to accept a connection that has already + delivered the handshake. + + @param stream The stream corresponding to the connection. + + @param ep The remote endpoint. + + @param req The upgrade request. + */ + template + void + on_upgrade( + ssl_stream&& stream, + endpoint_type ep, + beast::http::request&& req) + { + // Create the connection object and run it, + // transferring ownership of the ugprade request. + // + std::make_shared( + std::move(stream), + "wss_sync_port", + log_, + instance_.next_id(), + ep, + cb2_)->run(std::move(req)); + } +}; + +//------------------------------------------------------------------------------ + +/** An asynchronous WebSocket @b PortHandler which implements echo. + + This is a port handler which accepts WebSocket upgrade HTTP + requests and implements the echo protocol. All received + WebSocket messages will be echoed back to the remote host. +*/ +class wss_async_port +{ + // VFALCO We use boost::function to work around a compiler + // crash with gcc and clang using libstdc++ + + // The types of the on_new_stream callbacks + // + using on_new_stream_cb1 = + boost::function&)>; + + using on_new_stream_cb2 = + boost::function>&)>; + + // Reference to the server instance that made us + server& instance_; + + // The stream to log to + std::ostream& log_; + + // The context holds the SSL certificates the server uses + boost::asio::ssl::context& ctx_; + + // Called for each new websocket stream + on_new_stream_cb1 cb1_; + on_new_stream_cb2 cb2_; + +public: + /** Constructor + + @param instance The server instance which owns this port + + @param log The stream to use for logging + + @param ctx The SSL context holding the SSL certificates to use + + @param cb A callback which will be invoked for every new + WebSocket connection. This provides an opportunity to change + the settings on the stream before it is used. The callback + should have this equivalent signature: + @code + template + void callback(beast::websocket::stream&); + @endcode + In C++14 this can be accomplished with a generic lambda. In + C++11 it will be necessary to write out a lambda manually, + with a templated operator(). + */ + template + wss_async_port( + server& instance, + std::ostream& log, + boost::asio::ssl::context& ctx, + Callback const& cb) + : instance_(instance) + , log_(log) + , ctx_(ctx) + , cb1_(cb) + , cb2_(cb) + { + } + + /** Accept a TCP/IP connection. + + This function is called when the server has accepted an + incoming connection. + + @param sock The connected socket. + + @param ep The endpoint of the remote host. + */ + void + on_accept( + socket_type&& sock, + endpoint_type ep) + { + std::make_shared( + std::move(sock), + ctx_, + "wss_async_port", + log_, + instance_.next_id(), + ep, + cb2_)->run(); + } + + /** Accept a WebSocket upgrade request. + + This is used to accept a connection that has already + delivered the handshake. + + @param stream The stream corresponding to the connection. + + @param ep The remote endpoint. + + @param req The upgrade request. + */ + template + void + on_upgrade( + ssl_stream&& stream, + endpoint_type ep, + beast::http::request&& req) + { + std::make_shared( + std::move(stream), + "wss_async_port", + log_, + instance_.next_id(), + ep, + cb2_)->run(std::move(req)); + } +}; + +} // framework + +#endif diff --git a/src/beast/example/websocket-client-ssl/CMakeLists.txt b/src/beast/example/websocket-client-ssl/CMakeLists.txt new file mode 100644 index 0000000000..676c2784c3 --- /dev/null +++ b/src/beast/example/websocket-client-ssl/CMakeLists.txt @@ -0,0 +1,15 @@ +# Part of Beast + +GroupSources(include/beast beast) + +GroupSources(example/websocket-client-ssl "/") + +add_executable (websocket-client-ssl + ${BEAST_INCLUDES} + websocket_client_ssl.cpp +) + +target_link_libraries(websocket-client-ssl + Beast + ${OPENSSL_LIBRARIES} + ) diff --git a/src/beast/example/websocket-client-ssl/Jamfile b/src/beast/example/websocket-client-ssl/Jamfile new file mode 100644 index 0000000000..a0a0a2a75d --- /dev/null +++ b/src/beast/example/websocket-client-ssl/Jamfile @@ -0,0 +1,51 @@ +# +# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +# + +import os ; + +if [ os.name ] = SOLARIS +{ + lib socket ; + lib nsl ; +} +else if [ os.name ] = NT +{ + lib ws2_32 ; + lib mswsock ; +} +else if [ os.name ] = HPUX +{ + lib ipv6 ; +} +else if [ os.name ] = HAIKU +{ + lib network ; +} + +if [ os.name ] = NT +{ + lib ssl : : ssleay32 ; + lib crypto : : libeay32 ; +} +else +{ + lib ssl ; + lib crypto ; +} + +project + : requirements + ssl + crypto + ; + +exe ssl-websocket-client : + ssl_websocket_client.cpp + : + coverage:no + ubasan:no + ; diff --git a/src/beast/example/websocket-client-ssl/websocket_client_ssl.cpp b/src/beast/example/websocket-client-ssl/websocket_client_ssl.cpp new file mode 100644 index 0000000000..eb739f0302 --- /dev/null +++ b/src/beast/example/websocket-client-ssl/websocket_client_ssl.cpp @@ -0,0 +1,119 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include "../common/root_certificates.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace ssl = boost::asio::ssl; // from +namespace websocket = beast::websocket; // from + +int main() +{ + // A helper for reporting errors + auto const fail = + [](std::string what, beast::error_code ec) + { + std::cerr << what << ": " << ec.message() << std::endl; + std::cerr.flush(); + return EXIT_FAILURE; + }; + + boost::system::error_code ec; + + // Set up an asio socket to connect to a remote host + boost::asio::io_service ios; + tcp::resolver r{ios}; + tcp::socket sock{ios}; + + // Look up the domain name + std::string const host = "echo.websocket.org"; + auto const lookup = r.resolve({host, "https"}, ec); + if(ec) + return fail("resolve", ec); + + // Make the connection on the IP address we get from a lookup + boost::asio::connect(sock, lookup, ec); + if(ec) + return fail("connect", ec); + + // Create the required ssl context + ssl::context ctx{ssl::context::sslv23_client}; + + // This holds the root certificate used for verification + load_root_certificates(ctx, ec); + if(ec) + return fail("certificate", ec); + + // Wrap the now-connected socket in an SSL stream + using stream_type = ssl::stream; + stream_type stream{sock, ctx}; + stream.set_verify_mode(ssl::verify_none); + + // Perform SSL handshaking + stream.handshake(ssl::stream_base::client, ec); + if(ec) + return fail("ssl handshake", ec); + + // Now wrap the handshaked SSL stream in a websocket stream + websocket::stream ws{stream}; + + // Perform the websocket handshake + ws.handshake(host, "/", ec); + if(ec) + return fail("handshake", ec); + + // Send a message + ws.write(boost::asio::buffer("Hello, world!"), ec); + if(ec) + return fail("write", ec); + + // This buffer will hold the incoming message + beast::multi_buffer b; + + // Read the message into our buffer + ws.read(b, ec); + if(ec) + return fail("read", ec); + + // Send a "close" frame to the other end, this is a websocket thing + ws.close(websocket::close_code::normal, ec); + if(ec) + return fail("close", ec); + + // The buffers() function helps print a ConstBufferSequence + std::cout << beast::buffers(b.data()) << std::endl; + + // WebSocket says that to close a connection you have + // to keep reading messages until you receive a close frame. + // Beast delivers the close frame as an error from read. + // + beast::drain_buffer drain; // Throws everything away efficiently + for(;;) + { + // Keep reading messages... + ws.read(drain, ec); + + // ...until we get the special error code + if(ec == websocket::error::closed) + break; + + // Some other error occurred, report it and exit. + if(ec) + return fail("close", ec); + } + + return EXIT_SUCCESS; +} diff --git a/src/beast/example/websocket-client/CMakeLists.txt b/src/beast/example/websocket-client/CMakeLists.txt new file mode 100644 index 0000000000..0e97d94750 --- /dev/null +++ b/src/beast/example/websocket-client/CMakeLists.txt @@ -0,0 +1,13 @@ +# Part of Beast + +GroupSources(include/beast beast) + +GroupSources(example/websocket-client "/") + +add_executable (websocket-client + ${BEAST_INCLUDES} + ${EXTRAS_INCLUDES} + websocket_client.cpp +) + +target_link_libraries(websocket-client Beast) diff --git a/src/beast/example/websocket-client/Jamfile b/src/beast/example/websocket-client/Jamfile new file mode 100644 index 0000000000..9dc2a5d440 --- /dev/null +++ b/src/beast/example/websocket-client/Jamfile @@ -0,0 +1,13 @@ +# +# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +# + +exe websocket-client : + websocket_client.cpp + : + coverage:no + ubasan:no + ; diff --git a/src/beast/example/websocket-client/websocket_client.cpp b/src/beast/example/websocket-client/websocket_client.cpp new file mode 100644 index 0000000000..642bfe6f1d --- /dev/null +++ b/src/beast/example/websocket-client/websocket_client.cpp @@ -0,0 +1,101 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +//[example_websocket_client + +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace websocket = beast::websocket; // from + +int main() +{ + // A helper for reporting errors + auto const fail = + [](std::string what, beast::error_code ec) + { + std::cerr << what << ": " << ec.message() << std::endl; + std::cerr.flush(); + return EXIT_FAILURE; + }; + + boost::system::error_code ec; + + // Set up an asio socket + boost::asio::io_service ios; + tcp::resolver r{ios}; + tcp::socket sock{ios}; + + // Look up the domain name + std::string const host = "echo.websocket.org"; + auto const lookup = r.resolve({host, "http"}, ec); + if(ec) + return fail("resolve", ec); + + // Make the connection on the IP address we get from a lookup + boost::asio::connect(sock, lookup, ec); + if(ec) + return fail("connect", ec); + + // Wrap the now-connected socket in a websocket stream + websocket::stream ws{sock}; + + // Perform the websocket handshake + ws.handshake(host, "/", ec); + if(ec) + return fail("handshake", ec); + + // Send a message + ws.write(boost::asio::buffer(std::string("Hello, world!")), ec); + if(ec) + return fail("write", ec); + + // This buffer will hold the incoming message + beast::multi_buffer b; + + // Read the message into our buffer + ws.read(b, ec); + if(ec) + return fail("read", ec); + + // Send a "close" frame to the other end, this is a websocket thing + ws.close(websocket::close_code::normal, ec); + if(ec) + return fail("close", ec); + + // The buffers() function helps print a ConstBufferSequence + std::cout << beast::buffers(b.data()) << std::endl; + + // WebSocket says that to close a connection you have + // to keep reading messages until you receive a close frame. + // Beast delivers the close frame as an error from read. + // + beast::drain_buffer drain; // Throws everything away efficiently + for(;;) + { + // Keep reading messages... + ws.read(drain, ec); + + // ...until we get the special error code + if(ec == websocket::error::closed) + break; + + // Some other error occurred, report it and exit. + if(ec) + return fail("close", ec); + } + + // If we get here the connection was cleanly closed + return EXIT_SUCCESS; +} + +//] diff --git a/src/beast/example/websocket-server-async/CMakeLists.txt b/src/beast/example/websocket-server-async/CMakeLists.txt new file mode 100644 index 0000000000..b0f5ad1854 --- /dev/null +++ b/src/beast/example/websocket-server-async/CMakeLists.txt @@ -0,0 +1,13 @@ +# Part of Beast + +GroupSources(include/beast beast) +GroupSources(example/websocket-server-async "/") + +add_executable (websocket-server-async + ${BEAST_INCLUDES} + websocket_server_async.cpp +) + +target_link_libraries(websocket-server-async + Beast + ) diff --git a/src/beast/example/websocket-server-async/Jamfile b/src/beast/example/websocket-server-async/Jamfile new file mode 100644 index 0000000000..59571f2fd8 --- /dev/null +++ b/src/beast/example/websocket-server-async/Jamfile @@ -0,0 +1,13 @@ +# +# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +# + +exe websocket-server-async : + websocket_server_async.cpp + : + coverage:no + ubasan:no + ; diff --git a/src/beast/example/websocket-server-async/websocket_server_async.cpp b/src/beast/example/websocket-server-async/websocket_server_async.cpp new file mode 100644 index 0000000000..55978f0ab3 --- /dev/null +++ b/src/beast/example/websocket-server-async/websocket_server_async.cpp @@ -0,0 +1,463 @@ +// +// Copyright (c) 2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include "../common/helpers.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace http = beast::http; // from +namespace websocket = beast::websocket; // from +namespace ip = boost::asio::ip; // from +using tcp = boost::asio::ip::tcp; // from + +//------------------------------------------------------------------------------ +// +// Example: WebSocket echo server, asynchronous +// +//------------------------------------------------------------------------------ + +/** WebSocket asynchronous echo server + + The server holds the listening socket, the io_service, and + the threads calling io_service::run +*/ +class server +{ + using error_code = beast::error_code; // Saves typing + using clock_type = + std::chrono::steady_clock; // For the timer + using stream_type = + websocket::stream; // The type of our websocket stream + std::ostream* log_; // Used for diagnostic output, may be null + boost::asio::io_service ios_; // The io_service, required + tcp::socket sock_; // Holds accepted connections + tcp::endpoint ep_; // The remote endpoint during accept + std::vector thread_; // Threads for the io_service + boost::asio::ip::tcp::acceptor acceptor_; // The listening socket + std::function mod_; // Called on new stream + boost::optional< + boost::asio::io_service::work> work_; // Keeps io_service::run from returning + + //-------------------------------------------------------------------------- + + class connection : public std::enable_shared_from_this + { + std::ostream* log_; // Where to log, may be null + tcp::endpoint ep_; // The remote endpoing + stream_type ws_; // The websocket stream + boost::asio::basic_waitable_timer< + clock_type> timer_; // Needed for timeouts + boost::asio::io_service::strand strand_;// Needed when threads > 1 + beast::multi_buffer buffer_; // Stores the current message + beast::drain_buffer drain_; // Helps discard data on close + std::size_t id_; // A small unique id + + public: + /// Constructor + connection( + server& parent, + tcp::endpoint const& ep, + tcp::socket&& sock) + : log_(parent.log_) + , ep_(ep) + , ws_(std::move(sock)) + , timer_(ws_.get_io_service(), (clock_type::time_point::max)()) + , strand_(ws_.get_io_service()) + , id_([] + { + static std::atomic n{0}; + return ++n; + }()) + { + // Invoke the callback for new connections if set. + // This allows the settings on the websocket stream + // to be adjusted. For example to turn compression + // on or off or adjust the read and write buffer sizes. + // + if(parent.mod_) + parent.mod_(ws_); + } + + // Called immediately after the connection is created. + // We keep this separate from the constructor because + // shared_from_this may not be called from constructors. + void run() + { + // Run the timer + on_timer({}); + + // Put the handshake on the timer + timer_.expires_from_now(std::chrono::seconds(15)); + + // Read the websocket handshake and send the response + ws_.async_accept_ex( + [](websocket::response_type& res) + { + res.insert(http::field::server, "websocket-server-async"); + }, + strand_.wrap(std::bind( + &connection::on_accept, + shared_from_this(), + std::placeholders::_1))); + } + + private: + // Called when the timer expires. + // We operate the timer continuously this simplifies the code. + // + void on_timer(error_code ec) + { + if(ec && ec != boost::asio::error::operation_aborted) + return fail("timer", ec); + + // Verify that the timer really expired + // since the deadline may have moved. + // + if(timer_.expires_at() <= clock_type::now()) + { + // Closing the socket cancels all outstanding + // operations. They will complete with + // boost::asio::error::operation_aborted + // + ws_.next_layer().close(ec); + return; + } + + // Wait on the timer + timer_.async_wait( + strand_.wrap(std::bind( + &connection::on_timer, + shared_from_this(), + std::placeholders::_1))); + } + + // Called after the handshake is performed + void on_accept(error_code ec) + { + if(ec) + return fail("accept", ec); + do_read(); + } + + // Read a message from the websocket stream + void do_read() + { + // Put the read on the timer + timer_.expires_from_now(std::chrono::seconds(15)); + + // Read a message + ws_.async_read(buffer_, + strand_.wrap(std::bind( + &connection::on_read, + shared_from_this(), + std::placeholders::_1))); + } + + // Called after the message read completes + void on_read(error_code ec) + { + // This error means the other side + // closed the websocket stream. + if(ec == websocket::error::closed) + return; + + if(ec) + return fail("read", ec); + + // Put the write on the timer + timer_.expires_from_now(std::chrono::seconds(15)); + + // Write the received message back + ws_.binary(ws_.got_binary()); + ws_.async_write(buffer_.data(), + strand_.wrap(std::bind( + &connection::on_write, + shared_from_this(), + std::placeholders::_1))); + } + + // Called after the message write completes + void on_write(error_code ec) + { + if(ec) + return fail("write", ec); + + // Empty out the buffer. This is + // needed if we want to do another read. + // + buffer_.consume(buffer_.size()); + + // This shows how the server can close the + // connection. Alternatively we could call + // do_read again and the connection would + // stay open until the other side closes it. + // + do_close(); + } + + // Sends a websocket close frame + void do_close() + { + // Put the close frame on the timer + timer_.expires_from_now(std::chrono::seconds(15)); + + // Send the close frame + ws_.async_close({}, + strand_.wrap(std::bind( + &connection::on_close, + shared_from_this(), + std::placeholders::_1))); + } + + // Called when writing the close frame completes + void on_close(error_code ec) + { + if(ec) + return fail("close", ec); + + on_drain({}); + } + + // Read and discard any leftover message data + void on_drain(error_code ec) + { + if(ec == websocket::error::closed) + { + // the connection has been closed gracefully + return; + } + + if(ec) + return fail("drain", ec); + + // WebSocket says that to close a connection you have + // to keep reading messages until you receive a close frame. + // Beast delivers the close frame as an error from read. + // + ws_.async_read(drain_, + strand_.wrap(std::bind( + &connection::on_drain, + shared_from_this(), + std::placeholders::_1))); + } + + // Pretty-print an error to the log + void fail(std::string what, error_code ec) + { + if(log_) + if(ec != boost::asio::error::operation_aborted) + print(*log_, "[#", id_, " ", ep_, "] ", what, ": ", ec.message()); + } + }; + + //-------------------------------------------------------------------------- + + // Pretty-print an error to the log + void fail(std::string what, error_code ec) + { + if(log_) + print(*log_, what, ": ", ec.message()); + } + + // Initiates an accept + void do_accept() + { + acceptor_.async_accept(sock_, ep_, + std::bind(&server::on_accept, this, + std::placeholders::_1)); + } + + // Called when receiving an incoming connection + void on_accept(error_code ec) + { + // This can happen during exit + if(! acceptor_.is_open()) + return; + + // This can happen during exit + if(ec == boost::asio::error::operation_aborted) + return; + + if(ec) + fail("accept", ec); + + // Create the connection and run it + std::make_shared(*this, ep_, std::move(sock_))->run(); + + // Initiate another accept + do_accept(); + } + +public: + /** Constructor. + + @param log A pointer to a stream to log to, or `nullptr` + to disable logging. + + @param threads The number of threads in the io_service. + */ + server(std::ostream* log, std::size_t threads) + : log_(log) + , sock_(ios_) + , acceptor_(ios_) + , work_(ios_) + { + thread_.reserve(threads); + for(std::size_t i = 0; i < threads; ++i) + thread_.emplace_back( + [&]{ ios_.run(); }); + } + + /// Destructor. + ~server() + { + work_ = boost::none; + ios_.dispatch([&] + { + error_code ec; + acceptor_.close(ec); + }); + for(auto& t : thread_) + t.join(); + } + + /// Return the listening endpoint. + tcp::endpoint + local_endpoint() const + { + return acceptor_.local_endpoint(); + } + + /** Set a handler called for new streams. + + This function is called for each new stream. + It is used to set options for every connection. + */ + template + void + on_new_stream(F const& f) + { + mod_ = f; + } + + /** Open a listening port. + + @param ep The address and port to bind to. + + @param ec Set to the error, if any occurred. + */ + void + open(tcp::endpoint const& ep, error_code& ec) + { + acceptor_.open(ep.protocol(), ec); + if(ec) + return fail("open", ec); + acceptor_.set_option( + boost::asio::socket_base::reuse_address{true}); + acceptor_.bind(ep, ec); + if(ec) + return fail("bind", ec); + acceptor_.listen( + boost::asio::socket_base::max_connections, ec); + if(ec) + return fail("listen", ec); + do_accept(); + } +}; + +//------------------------------------------------------------------------------ + +// This helper will apply some settings to a WebSocket +// stream. The server applies it to all new connections. +// +class set_stream_options +{ + websocket::permessage_deflate pmd_; + +public: + set_stream_options(set_stream_options const&) = default; + + explicit + set_stream_options( + websocket::permessage_deflate const& pmd) + : pmd_(pmd) + { + } + + template + void + operator()(websocket::stream& ws) const + { + ws.set_option(pmd_); + + // Turn off the auto-fragment option. + // This improves Autobahn performance. + // + ws.auto_fragment(false); + + // 64MB message size limit. + // The high limit is needed for Autobahn. + ws.read_message_max(64 * 1024 * 1024); + } +}; + +int main(int argc, char* argv[]) +{ + // Check command line arguments. + if(argc != 4) + { + std::cerr << + "Usage: " << argv[0] << "
\n" + " For IPv4, try: " << argv[0] << " 0.0.0.0 8080 1\n" + " For IPv6, try: " << argv[0] << " 0::0 8080 1\n" + ; + return EXIT_FAILURE; + } + + // Decode command line options + auto address = ip::address::from_string(argv[1]); + unsigned short port = static_cast(std::atoi(argv[2])); + unsigned short threads = static_cast(std::atoi(argv[3])); + + // Allow permessage-deflate + // compression on all connections + websocket::permessage_deflate pmd; + pmd.client_enable = true; + pmd.server_enable = true; + pmd.compLevel = 3; + + // Create our server + server s{&std::cout, threads}; + s.on_new_stream(set_stream_options{pmd}); + + // Open the listening port + beast::error_code ec; + s.open(tcp::endpoint{address, port}, ec); + if(ec) + { + std::cerr << "Error: " << ec.message(); + return EXIT_FAILURE; + } + + // Wait for CTRL+C. After receiving CTRL+C, + // the server should shut down cleanly. + // + sig_wait(); + + return EXIT_SUCCESS; +} diff --git a/src/beast/examples/CMakeLists.txt b/src/beast/examples/CMakeLists.txt deleted file mode 100644 index edb8e3339e..0000000000 --- a/src/beast/examples/CMakeLists.txt +++ /dev/null @@ -1,76 +0,0 @@ -# Part of Beast - -GroupSources(extras/beast extras) -GroupSources(include/beast beast) - -GroupSources(examples "/") - -add_executable (http-crawl - ${BEAST_INCLUDES} - ${EXTRAS_INCLUDES} - urls_large_data.hpp - urls_large_data.cpp - http_crawl.cpp -) - -if (NOT WIN32) - target_link_libraries(http-crawl ${Boost_LIBRARIES} Threads::Threads) -else() - target_link_libraries(http-crawl ${Boost_LIBRARIES}) -endif() - -add_executable (http-server - ${BEAST_INCLUDES} - ${EXTRAS_INCLUDES} - file_body.hpp - mime_type.hpp - http_async_server.hpp - http_sync_server.hpp - http_server.cpp -) - -if (NOT WIN32) - target_link_libraries(http-server ${Boost_LIBRARIES} Threads::Threads) -else() - target_link_libraries(http-server ${Boost_LIBRARIES}) -endif() - - -add_executable (http-example - ${BEAST_INCLUDES} - ${EXTRAS_INCLUDES} - http_example.cpp -) - -if (NOT WIN32) - target_link_libraries(http-example ${Boost_LIBRARIES} Threads::Threads) -else() - target_link_libraries(http-example ${Boost_LIBRARIES}) -endif() - - -add_executable (websocket-echo - ${BEAST_INCLUDES} - websocket_async_echo_server.hpp - websocket_sync_echo_server.hpp - websocket_echo.cpp -) - -if (NOT WIN32) - target_link_libraries(websocket-echo ${Boost_LIBRARIES} Threads::Threads) -else() - target_link_libraries(websocket-echo ${Boost_LIBRARIES}) -endif() - - -add_executable (websocket-example - ${BEAST_INCLUDES} - ${EXTRAS_INCLUDES} - websocket_example.cpp -) - -if (NOT WIN32) - target_link_libraries(websocket-example ${Boost_LIBRARIES} Threads::Threads) -else() - target_link_libraries(websocket-example ${Boost_LIBRARIES}) -endif() diff --git a/src/beast/examples/file_body.hpp b/src/beast/examples/file_body.hpp deleted file mode 100644 index 00f2b5434d..0000000000 --- a/src/beast/examples/file_body.hpp +++ /dev/null @@ -1,97 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_EXAMPLE_FILE_BODY_H_INCLUDED -#define BEAST_EXAMPLE_FILE_BODY_H_INCLUDED - -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace http { - -struct file_body -{ - using value_type = std::string; - - class writer - { - std::uint64_t size_ = 0; - std::uint64_t offset_ = 0; - std::string const& path_; - FILE* file_ = nullptr; - char buf_[4096]; - std::size_t buf_len_; - - public: - writer(writer const&) = delete; - writer& operator=(writer const&) = delete; - - template - writer(message const& m) noexcept - : path_(m.body) - { - } - - ~writer() - { - if(file_) - fclose(file_); - } - - void - init(error_code& ec) noexcept - { - file_ = fopen(path_.c_str(), "rb"); - if(! file_) - ec = error_code{errno, - system_category()}; - else - size_ = boost::filesystem::file_size(path_); - } - - std::uint64_t - content_length() const noexcept - { - return size_; - } - - template - bool - write(error_code& ec, WriteFunction&& wf) noexcept - { - if(size_ - offset_ < sizeof(buf_)) - buf_len_ = static_cast( - size_ - offset_); - else - buf_len_ = sizeof(buf_); - auto const nread = fread( - buf_, 1, sizeof(buf_), file_); - if(ferror(file_)) - { - ec = error_code(errno, - system_category()); - return true; - } - BOOST_ASSERT(nread != 0); - offset_ += nread; - wf(boost::asio::buffer(buf_, nread)); - return offset_ >= size_; - } - }; -}; - -} // http -} // beast - -#endif diff --git a/src/beast/examples/http_async_server.hpp b/src/beast/examples/http_async_server.hpp deleted file mode 100644 index 33ad51a853..0000000000 --- a/src/beast/examples/http_async_server.hpp +++ /dev/null @@ -1,323 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_EXAMPLE_HTTP_ASYNC_SERVER_H_INCLUDED -#define BEAST_EXAMPLE_HTTP_ASYNC_SERVER_H_INCLUDED - -#include "file_body.hpp" -#include "mime_type.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace http { - -class http_async_server -{ - using endpoint_type = boost::asio::ip::tcp::endpoint; - using address_type = boost::asio::ip::address; - using socket_type = boost::asio::ip::tcp::socket; - - using req_type = request; - using resp_type = response; - - std::mutex m_; - bool log_ = true; - boost::asio::io_service ios_; - boost::asio::ip::tcp::acceptor acceptor_; - socket_type sock_; - std::string root_; - std::vector thread_; - -public: - http_async_server(endpoint_type const& ep, - std::size_t threads, std::string const& root) - : acceptor_(ios_) - , sock_(ios_) - , root_(root) - { - acceptor_.open(ep.protocol()); - acceptor_.bind(ep); - acceptor_.listen( - boost::asio::socket_base::max_connections); - acceptor_.async_accept(sock_, - std::bind(&http_async_server::on_accept, this, - beast::asio::placeholders::error)); - thread_.reserve(threads); - for(std::size_t i = 0; i < threads; ++i) - thread_.emplace_back( - [&] { ios_.run(); }); - } - - ~http_async_server() - { - error_code ec; - ios_.dispatch( - [&]{ acceptor_.close(ec); }); - for(auto& t : thread_) - t.join(); - } - - template - void - log(Args const&... args) - { - if(log_) - { - std::lock_guard lock(m_); - log_args(args...); - } - } - -private: - template - class write_op - { - struct data - { - bool cont; - Stream& s; - message m; - - data(Handler& handler, Stream& s_, - message&& m_) - : cont(beast_asio_helpers:: - is_continuation(handler)) - , s(s_) - , m(std::move(m_)) - { - } - }; - - handler_ptr d_; - - public: - write_op(write_op&&) = default; - write_op(write_op const&) = default; - - template - write_op(DeducedHandler&& h, Stream& s, Args&&... args) - : d_(std::forward(h), - s, std::forward(args)...) - { - (*this)(error_code{}, false); - } - - void - operator()(error_code ec, bool again = true) - { - auto& d = *d_; - d.cont = d.cont || again; - if(! again) - { - beast::http::async_write(d.s, d.m, std::move(*this)); - return; - } - d_.invoke(ec); - } - - friend - void* asio_handler_allocate( - std::size_t size, write_op* op) - { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); - } - - friend - void asio_handler_deallocate( - void* p, std::size_t size, write_op* op) - { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); - } - - friend - bool asio_handler_is_continuation(write_op* op) - { - return op->d_->cont; - } - - template - friend - void asio_handler_invoke(Function&& f, write_op* op) - { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); - } - }; - - template - static - void - async_write(Stream& stream, message< - isRequest, Body, Fields>&& msg, - DeducedHandler&& handler) - { - write_op::type, - isRequest, Body, Fields>{std::forward( - handler), stream, std::move(msg)}; - } - - class peer : public std::enable_shared_from_this - { - int id_; - streambuf sb_; - socket_type sock_; - http_async_server& server_; - boost::asio::io_service::strand strand_; - req_type req_; - - public: - peer(peer&&) = default; - peer(peer const&) = default; - peer& operator=(peer&&) = delete; - peer& operator=(peer const&) = delete; - - peer(socket_type&& sock, http_async_server& server) - : sock_(std::move(sock)) - , server_(server) - , strand_(sock_.get_io_service()) - { - static int n = 0; - id_ = ++n; - } - - void - fail(error_code ec, std::string what) - { - if(ec != boost::asio::error::operation_aborted) - server_.log("#", id_, " ", what, ": ", ec.message(), "\n"); - } - - void run() - { - do_read(); - } - - void do_read() - { - async_read(sock_, sb_, req_, strand_.wrap( - std::bind(&peer::on_read, shared_from_this(), - asio::placeholders::error))); - } - - void on_read(error_code const& ec) - { - if(ec) - return fail(ec, "read"); - auto path = req_.url; - if(path == "/") - path = "/index.html"; - path = server_.root_ + path; - if(! boost::filesystem::exists(path)) - { - response res; - res.status = 404; - res.reason = "Not Found"; - res.version = req_.version; - res.fields.insert("Server", "http_async_server"); - res.fields.insert("Content-Type", "text/html"); - res.body = "The file '" + path + "' was not found"; - prepare(res); - async_write(sock_, std::move(res), - std::bind(&peer::on_write, shared_from_this(), - asio::placeholders::error)); - return; - } - try - { - resp_type res; - res.status = 200; - res.reason = "OK"; - res.version = req_.version; - res.fields.insert("Server", "http_async_server"); - res.fields.insert("Content-Type", mime_type(path)); - res.body = path; - prepare(res); - async_write(sock_, std::move(res), - std::bind(&peer::on_write, shared_from_this(), - asio::placeholders::error)); - } - catch(std::exception const& e) - { - response res; - res.status = 500; - res.reason = "Internal Error"; - res.version = req_.version; - res.fields.insert("Server", "http_async_server"); - res.fields.insert("Content-Type", "text/html"); - res.body = - std::string{"An internal error occurred"} + e.what(); - prepare(res); - async_write(sock_, std::move(res), - std::bind(&peer::on_write, shared_from_this(), - asio::placeholders::error)); - } - } - - void on_write(error_code ec) - { - if(ec) - fail(ec, "write"); - do_read(); - } - }; - - void - log_args() - { - } - - template - void - log_args(Arg const& arg, Args const&... args) - { - std::cerr << arg; - log_args(args...); - } - - void - fail(error_code ec, std::string what) - { - log(what, ": ", ec.message(), "\n"); - } - - void - on_accept(error_code ec) - { - if(! acceptor_.is_open()) - return; - if(ec) - return fail(ec, "accept"); - socket_type sock(std::move(sock_)); - acceptor_.async_accept(sock_, - std::bind(&http_async_server::on_accept, this, - asio::placeholders::error)); - std::make_shared(std::move(sock), *this)->run(); - } -}; - -} // http -} // beast - -#endif diff --git a/src/beast/examples/http_crawl.cpp b/src/beast/examples/http_crawl.cpp deleted file mode 100644 index 1a794169b0..0000000000 --- a/src/beast/examples/http_crawl.cpp +++ /dev/null @@ -1,62 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#include "urls_large_data.hpp" - -#include -#include -#include -#include -#include - -using namespace beast::http; -using namespace boost::asio; - -template -void -err(beast::error_code const& ec, String const& what) -{ - std::cerr << what << ": " << ec.message() << std::endl; -} - -int main(int, char const*[]) -{ - io_service ios; - for(auto const& host : urls_large_data()) - { - try - { - ip::tcp::resolver r(ios); - auto it = r.resolve( - ip::tcp::resolver::query{host, "http"}); - ip::tcp::socket sock(ios); - connect(sock, it); - auto ep = sock.remote_endpoint(); - request req; - req.method = "GET"; - req.url = "/"; - req.version = 11; - req.fields.insert("Host", host + std::string(":") + - boost::lexical_cast(ep.port())); - req.fields.insert("User-Agent", "beast/http"); - prepare(req); - write(sock, req); - response res; - streambuf sb; - beast::http::read(sock, sb, res); - std::cout << res; - } - catch(beast::system_error const& ec) - { - std::cerr << host << ": " << ec.what(); - } - catch(...) - { - std::cerr << host << ": unknown exception" << std::endl; - } - } -} diff --git a/src/beast/examples/http_example.cpp b/src/beast/examples/http_example.cpp deleted file mode 100644 index 038ac2e24d..0000000000 --- a/src/beast/examples/http_example.cpp +++ /dev/null @@ -1,40 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#include -#include -#include -#include -#include - -int main() -{ - // Normal boost::asio setup - std::string const host = "boost.org"; - boost::asio::io_service ios; - boost::asio::ip::tcp::resolver r{ios}; - boost::asio::ip::tcp::socket sock{ios}; - boost::asio::connect(sock, - r.resolve(boost::asio::ip::tcp::resolver::query{host, "http"})); - - // Send HTTP request using beast - beast::http::request req; - req.method = "GET"; - req.url = "/"; - req.version = 11; - req.fields.replace("Host", host + ":" + - boost::lexical_cast(sock.remote_endpoint().port())); - req.fields.replace("User-Agent", "Beast"); - beast::http::prepare(req); - beast::http::write(sock, req); - - // Receive and print HTTP response using beast - beast::streambuf sb; - beast::http::response resp; - beast::http::read(sock, sb, resp); - std::cout << resp; -} diff --git a/src/beast/examples/http_server.cpp b/src/beast/examples/http_server.cpp deleted file mode 100644 index 7694d559b3..0000000000 --- a/src/beast/examples/http_server.cpp +++ /dev/null @@ -1,61 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#include "http_async_server.hpp" -#include "http_sync_server.hpp" - -#include -#include - -#include - -int main(int ac, char const* av[]) -{ - using namespace beast::http; - namespace po = boost::program_options; - po::options_description desc("Options"); - - desc.add_options() - ("root,r", po::value()->default_value("."), - "Set the root directory for serving files") - ("port,p", po::value()->default_value(8080), - "Set the port number for the server") - ("ip", po::value()->default_value("0.0.0.0"), - "Set the IP address to bind to, \"0.0.0.0\" for all") - ("threads,n", po::value()->default_value(4), - "Set the number of threads to use") - ("sync,s", "Launch a synchronous server") - ; - po::variables_map vm; - po::store(po::parse_command_line(ac, av, desc), vm); - - std::string root = vm["root"].as(); - - std::uint16_t port = vm["port"].as(); - - std::string ip = vm["ip"].as(); - - std::size_t threads = vm["threads"].as(); - - bool sync = vm.count("sync") > 0; - - using endpoint_type = boost::asio::ip::tcp::endpoint; - using address_type = boost::asio::ip::address; - - endpoint_type ep{address_type::from_string(ip), port}; - - if(sync) - { - http_sync_server server(ep, root); - beast::test::sig_wait(); - } - else - { - http_async_server server(ep, threads, root); - beast::test::sig_wait(); - } -} diff --git a/src/beast/examples/http_sync_server.hpp b/src/beast/examples/http_sync_server.hpp deleted file mode 100644 index 378775688d..0000000000 --- a/src/beast/examples/http_sync_server.hpp +++ /dev/null @@ -1,217 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_EXAMPLE_HTTP_SYNC_SERVER_H_INCLUDED -#define BEAST_EXAMPLE_HTTP_SYNC_SERVER_H_INCLUDED - -#include "file_body.hpp" -#include "mime_type.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -namespace beast { -namespace http { - -class http_sync_server -{ - using endpoint_type = boost::asio::ip::tcp::endpoint; - using address_type = boost::asio::ip::address; - using socket_type = boost::asio::ip::tcp::socket; - - using req_type = request; - using resp_type = response; - - bool log_ = true; - std::mutex m_; - boost::asio::io_service ios_; - socket_type sock_; - boost::asio::ip::tcp::acceptor acceptor_; - std::string root_; - std::thread thread_; - -public: - http_sync_server(endpoint_type const& ep, - std::string const& root) - : sock_(ios_) - , acceptor_(ios_) - , root_(root) - { - acceptor_.open(ep.protocol()); - acceptor_.bind(ep); - acceptor_.listen( - boost::asio::socket_base::max_connections); - acceptor_.async_accept(sock_, - std::bind(&http_sync_server::on_accept, this, - beast::asio::placeholders::error)); - thread_ = std::thread{[&]{ ios_.run(); }}; - } - - ~http_sync_server() - { - error_code ec; - ios_.dispatch( - [&]{ acceptor_.close(ec); }); - thread_.join(); - } - - template - void - log(Args const&... args) - { - if(log_) - { - std::lock_guard lock(m_); - log_args(args...); - } - } - -private: - void - log_args() - { - } - - template - void - log_args(Arg const& arg, Args const&... args) - { - std::cerr << arg; - log_args(args...); - } - - void - fail(error_code ec, std::string what) - { - log(what, ": ", ec.message(), "\n"); - } - - void - fail(int id, error_code const& ec) - { - if(ec != boost::asio::error::operation_aborted && - ec != boost::asio::error::eof) - log("#", id, " ", ec.message(), "\n"); - } - - struct lambda - { - int id; - http_sync_server& self; - socket_type sock; - boost::asio::io_service::work work; - - lambda(int id_, http_sync_server& self_, - socket_type&& sock_) - : id(id_) - , self(self_) - , sock(std::move(sock_)) - , work(sock.get_io_service()) - { - } - - void operator()() - { - self.do_peer(id, std::move(sock)); - } - }; - - void - on_accept(error_code ec) - { - if(! acceptor_.is_open()) - return; - if(ec) - return fail(ec, "accept"); - static int id_ = 0; - std::thread{lambda{++id_, *this, std::move(sock_)}}.detach(); - acceptor_.async_accept(sock_, - std::bind(&http_sync_server::on_accept, this, - asio::placeholders::error)); - } - - void - do_peer(int id, socket_type&& sock0) - { - socket_type sock(std::move(sock0)); - streambuf sb; - error_code ec; - for(;;) - { - req_type req; - http::read(sock, sb, req, ec); - if(ec) - break; - auto path = req.url; - if(path == "/") - path = "/index.html"; - path = root_ + path; - if(! boost::filesystem::exists(path)) - { - response res; - res.status = 404; - res.reason = "Not Found"; - res.version = req.version; - res.fields.insert("Server", "http_sync_server"); - res.fields.insert("Content-Type", "text/html"); - res.body = "The file '" + path + "' was not found"; - prepare(res); - write(sock, res, ec); - if(ec) - break; - return; - } - try - { - resp_type res; - res.status = 200; - res.reason = "OK"; - res.version = req.version; - res.fields.insert("Server", "http_sync_server"); - res.fields.insert("Content-Type", mime_type(path)); - res.body = path; - prepare(res); - write(sock, res, ec); - if(ec) - break; - } - catch(std::exception const& e) - { - response res; - res.status = 500; - res.reason = "Internal Error"; - res.version = req.version; - res.fields.insert("Server", "http_sync_server"); - res.fields.insert("Content-Type", "text/html"); - res.body = - std::string{"An internal error occurred: "} + e.what(); - prepare(res); - write(sock, res, ec); - if(ec) - break; - } - } - fail(id, ec); - } -}; - -} // http -} // beast - -#endif diff --git a/src/beast/examples/mime_type.hpp b/src/beast/examples/mime_type.hpp deleted file mode 100644 index babffd5f25..0000000000 --- a/src/beast/examples/mime_type.hpp +++ /dev/null @@ -1,51 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_EXAMPLE_HTTP_MIME_TYPE_H_INCLUDED -#define BEAST_EXAMPLE_HTTP_MIME_TYPE_H_INCLUDED - -#include -#include - -namespace beast { -namespace http { - -// Return the Mime-Type for a given file extension -template -std::string -mime_type(std::string const& path) -{ - auto const ext = - boost::filesystem::path{path}.extension().string(); - if(ext == ".txt") return "text/plain"; - if(ext == ".htm") return "text/html"; - if(ext == ".html") return "text/html"; - if(ext == ".php") return "text/html"; - if(ext == ".css") return "text/css"; - if(ext == ".js") return "application/javascript"; - if(ext == ".json") return "application/json"; - if(ext == ".xml") return "application/xml"; - if(ext == ".swf") return "application/x-shockwave-flash"; - if(ext == ".flv") return "video/x-flv"; - if(ext == ".png") return "image/png"; - if(ext == ".jpe") return "image/jpeg"; - if(ext == ".jpeg") return "image/jpeg"; - if(ext == ".jpg") return "image/jpeg"; - if(ext == ".gif") return "image/gif"; - if(ext == ".bmp") return "image/bmp"; - if(ext == ".ico") return "image/vnd.microsoft.icon"; - if(ext == ".tiff") return "image/tiff"; - if(ext == ".tif") return "image/tiff"; - if(ext == ".svg") return "image/svg+xml"; - if(ext == ".svgz") return "image/svg+xml"; - return "application/text"; -} - -} // http -} // beast - -#endif diff --git a/src/beast/examples/ssl/CMakeLists.txt b/src/beast/examples/ssl/CMakeLists.txt deleted file mode 100644 index df35e5f325..0000000000 --- a/src/beast/examples/ssl/CMakeLists.txt +++ /dev/null @@ -1,32 +0,0 @@ -# Part of Beast - -GroupSources(extras/beast extras) -GroupSources(include/beast beast) - -GroupSources(examples/ssl "/") - -include_directories(${OPENSSL_INCLUDE_DIR}) - -add_executable (http-ssl-example - ${BEAST_INCLUDES} - ${EXTRAS_INCLUDES} - http_ssl_example.cpp -) - -target_link_libraries(http-ssl-example ${OPENSSL_LIBRARIES}) - -if (NOT WIN32) - target_link_libraries(http-ssl-example ${Boost_LIBRARIES} Threads::Threads) -endif() - -add_executable (websocket-ssl-example - ${BEAST_INCLUDES} - ${EXTRAS_INCLUDES} - websocket_ssl_example.cpp -) - -target_link_libraries(websocket-ssl-example ${OPENSSL_LIBRARIES}) - -if (NOT WIN32) - target_link_libraries(websocket-ssl-example ${Boost_LIBRARIES} Threads::Threads) -endif() diff --git a/src/beast/examples/ssl/http_ssl_example.cpp b/src/beast/examples/ssl/http_ssl_example.cpp deleted file mode 100644 index 30c8194043..0000000000 --- a/src/beast/examples/ssl/http_ssl_example.cpp +++ /dev/null @@ -1,58 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#include -#include -#include -#include -#include -#include - -int main() -{ - using boost::asio::connect; - using socket = boost::asio::ip::tcp::socket; - using resolver = boost::asio::ip::tcp::resolver; - using io_service = boost::asio::io_service; - namespace ssl = boost::asio::ssl; - - // Normal boost::asio setup - std::string const host = "github.com"; - io_service ios; - resolver r{ios}; - socket sock{ios}; - connect(sock, r.resolve(resolver::query{host, "https"})); - - // Perform SSL handshaking - ssl::context ctx{ssl::context::sslv23}; - ssl::stream stream{sock, ctx}; - stream.set_verify_mode(ssl::verify_none); - stream.handshake(ssl::stream_base::client); - - // Send HTTP request over SSL using Beast - beast::http::request req; - req.method = "GET"; - req.url = "/"; - req.version = 11; - req.fields.insert("Host", host + ":" + - boost::lexical_cast(sock.remote_endpoint().port())); - req.fields.insert("User-Agent", "Beast"); - beast::http::prepare(req); - beast::http::write(stream, req); - - // Receive and print HTTP response using Beast - beast::streambuf sb; - beast::http::response resp; - beast::http::read(stream, sb, resp); - std::cout << resp; - - // Shut down SSL on the stream - boost::system::error_code ec; - stream.shutdown(ec); - if(ec && ec != boost::asio::error::eof) - std::cout << "error: " << ec.message(); -} diff --git a/src/beast/examples/ssl/websocket_ssl_example.cpp b/src/beast/examples/ssl/websocket_ssl_example.cpp deleted file mode 100644 index bba99c7a2f..0000000000 --- a/src/beast/examples/ssl/websocket_ssl_example.cpp +++ /dev/null @@ -1,49 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#include -#include -#include -#include -#include -#include -#include - -int main() -{ - using boost::asio::connect; - using socket = boost::asio::ip::tcp::socket; - using resolver = boost::asio::ip::tcp::resolver; - using io_service = boost::asio::io_service; - namespace ssl = boost::asio::ssl; - - // Normal boost::asio setup - std::string const host = "echo.websocket.org"; - io_service ios; - resolver r{ios}; - socket sock{ios}; - connect(sock, r.resolve(resolver::query{host, "https"})); - - // Perform SSL handshaking - using stream_type = ssl::stream; - ssl::context ctx{ssl::context::sslv23}; - stream_type stream{sock, ctx}; - stream.set_verify_mode(ssl::verify_none); - stream.handshake(ssl::stream_base::client); - - // Secure WebSocket connect and send message using Beast - beast::websocket::stream ws{stream}; - ws.handshake(host, "/"); - ws.write(boost::asio::buffer("Hello, world!")); - - // Receive Secure WebSocket message, print and close using Beast - beast::streambuf sb; - beast::websocket::opcode op; - ws.read(op, sb); - ws.close(beast::websocket::close_code::normal); - std::cout << to_string(sb.data()) << "\n"; -} diff --git a/src/beast/examples/websocket_async_echo_server.hpp b/src/beast/examples/websocket_async_echo_server.hpp deleted file mode 100644 index bd0c8481b6..0000000000 --- a/src/beast/examples/websocket_async_echo_server.hpp +++ /dev/null @@ -1,375 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef WEBSOCKET_ASYNC_ECHO_SERVER_HPP -#define WEBSOCKET_ASYNC_ECHO_SERVER_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace websocket { - -/** Asynchronous WebSocket echo client/server -*/ -class async_echo_server -{ -public: - using error_code = beast::error_code; - using address_type = boost::asio::ip::address; - using socket_type = boost::asio::ip::tcp::socket; - using endpoint_type = boost::asio::ip::tcp::endpoint; - -private: - struct identity - { - template - void - operator()(beast::http::message< - true, Body, Fields>& req) const - { - req.fields.replace("User-Agent", "async_echo_client"); - } - - template - void - operator()(beast::http::message< - false, Body, Fields>& resp) const - { - resp.fields.replace("Server", "async_echo_server"); - } - }; - - /** A container of type-erased option setters. - */ - template - class options_set - { - // workaround for std::function bug in msvc - struct callable - { - virtual ~callable() = default; - virtual void operator()( - beast::websocket::stream&) = 0; - }; - - template - class callable_impl : public callable - { - T t_; - - public: - template - callable_impl(U&& u) - : t_(std::forward(u)) - { - } - - void - operator()(beast::websocket::stream& ws) - { - t_(ws); - } - }; - - template - class lambda - { - Opt opt_; - - public: - lambda(lambda&&) = default; - lambda(lambda const&) = default; - - lambda(Opt const& opt) - : opt_(opt) - { - } - - void - operator()(beast::websocket::stream& ws) const - { - ws.set_option(opt_); - } - }; - - std::unordered_map> list_; - - public: - template - void - set_option(Opt const& opt) - { - std::unique_ptr p; - p.reset(new callable_impl>{opt}); - list_[std::type_index{ - typeid(Opt)}] = std::move(p); - } - - void - set_options(beast::websocket::stream& ws) - { - for(auto const& op : list_) - (*op.second)(ws); - } - }; - - std::ostream* log_; - boost::asio::io_service ios_; - socket_type sock_; - endpoint_type ep_; - boost::asio::ip::tcp::acceptor acceptor_; - std::vector thread_; - boost::optional work_; - options_set opts_; - -public: - async_echo_server(async_echo_server const&) = delete; - async_echo_server& operator=(async_echo_server const&) = delete; - - /** Constructor. - - @param log A pointer to a stream to log to, or `nullptr` - to disable logging. - - @param threads The number of threads in the io_service. - */ - async_echo_server(std::ostream* log, - std::size_t threads) - : log_(log) - , sock_(ios_) - , acceptor_(ios_) - , work_(ios_) - { - opts_.set_option( - beast::websocket::decorate(identity{})); - thread_.reserve(threads); - for(std::size_t i = 0; i < threads; ++i) - thread_.emplace_back( - [&]{ ios_.run(); }); - } - - /** Destructor. - */ - ~async_echo_server() - { - work_ = boost::none; - error_code ec; - ios_.dispatch( - [&]{ acceptor_.close(ec); }); - for(auto& t : thread_) - t.join(); - } - - /** Return the listening endpoint. - */ - endpoint_type - local_endpoint() const - { - return acceptor_.local_endpoint(); - } - - /** Set a websocket option. - - The option will be applied to all new connections. - - @param opt The option to apply. - */ - template - void - set_option(Opt const& opt) - { - opts_.set_option(opt); - } - - /** Open a listening port. - - @param ep The address and port to bind to. - - @param ec Set to the error, if any occurred. - */ - void - open(endpoint_type const& ep, error_code& ec) - { - acceptor_.open(ep.protocol(), ec); - if(ec) - return fail("open", ec); - acceptor_.set_option( - boost::asio::socket_base::reuse_address{true}); - acceptor_.bind(ep, ec); - if(ec) - return fail("bind", ec); - acceptor_.listen( - boost::asio::socket_base::max_connections, ec); - if(ec) - return fail("listen", ec); - acceptor_.async_accept(sock_, ep_, - std::bind(&async_echo_server::on_accept, this, - beast::asio::placeholders::error)); - } - -private: - class peer - { - struct data - { - async_echo_server& server; - endpoint_type ep; - int state = 0; - beast::websocket::stream ws; - boost::asio::io_service::strand strand; - beast::websocket::opcode op; - beast::streambuf db; - std::size_t id; - - data(async_echo_server& server_, - endpoint_type const& ep_, - socket_type&& sock_) - : server(server_) - , ep(ep_) - , ws(std::move(sock_)) - , strand(ws.get_io_service()) - , id([] - { - static std::atomic n{0}; - return ++n; - }()) - { - } - }; - - // VFALCO This could be unique_ptr in [Net.TS] - std::shared_ptr d_; - - public: - peer(peer&&) = default; - peer(peer const&) = default; - peer& operator=(peer&&) = delete; - peer& operator=(peer const&) = delete; - - template - explicit - peer(async_echo_server& server, - endpoint_type const& ep, socket_type&& sock, - Args&&... args) - : d_(std::make_shared(server, ep, - std::forward(sock), - std::forward(args)...)) - { - auto& d = *d_; - d.server.opts_.set_options(d.ws); - run(); - } - - void run() - { - auto& d = *d_; - d.ws.async_accept(std::move(*this)); - } - - void operator()(error_code ec, std::size_t) - { - (*this)(ec); - } - - void operator()(error_code ec) - { - using boost::asio::buffer; - using boost::asio::buffer_copy; - auto& d = *d_; - switch(d.state) - { - // did accept - case 0: - if(ec) - return fail("async_accept", ec); - - // start - case 1: - if(ec) - return fail("async_handshake", ec); - d.db.consume(d.db.size()); - // read message - d.state = 2; - d.ws.async_read(d.op, d.db, - d.strand.wrap(std::move(*this))); - return; - - // got message - case 2: - if(ec == beast::websocket::error::closed) - return; - if(ec) - return fail("async_read", ec); - // write message - d.state = 1; - d.ws.set_option( - beast::websocket::message_type(d.op)); - d.ws.async_write(d.db.data(), - d.strand.wrap(std::move(*this))); - return; - } - } - - private: - void - fail(std::string what, error_code ec) - { - auto& d = *d_; - if(d.server.log_) - if(ec != beast::websocket::error::closed) - d.server.fail("[#" + std::to_string(d.id) + - " " + boost::lexical_cast(d.ep) + - "] " + what, ec); - } - }; - - void - fail(std::string what, error_code ec) - { - if(log_) - { - static std::mutex m; - std::lock_guard lock{m}; - (*log_) << what << ": " << - ec.message() << std::endl; - } - } - - void - on_accept(error_code ec) - { - if(! acceptor_.is_open()) - return; - if(ec == boost::asio::error::operation_aborted) - return; - if(ec) - fail("accept", ec); - peer{*this, ep_, std::move(sock_)}; - acceptor_.async_accept(sock_, ep_, - std::bind(&async_echo_server::on_accept, this, - beast::asio::placeholders::error)); - } -}; - -} // websocket - -#endif diff --git a/src/beast/examples/websocket_echo.cpp b/src/beast/examples/websocket_echo.cpp deleted file mode 100644 index e8171526fd..0000000000 --- a/src/beast/examples/websocket_echo.cpp +++ /dev/null @@ -1,56 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#include "websocket_async_echo_server.hpp" -#include "websocket_sync_echo_server.hpp" -#include -#include -#include - -/// Block until SIGINT or SIGTERM is received. -void -sig_wait() -{ - boost::asio::io_service ios; - boost::asio::signal_set signals( - ios, SIGINT, SIGTERM); - signals.async_wait( - [&](boost::system::error_code const&, int) - { - }); - ios.run(); -} - -int main() -{ - using namespace beast::websocket; - using endpoint_type = boost::asio::ip::tcp::endpoint; - using address_type = boost::asio::ip::address; - - beast::error_code ec; - - permessage_deflate pmd; - pmd.client_enable = true; - pmd.server_enable = true; - pmd.compLevel = 3; - - websocket::async_echo_server s1{&std::cout, 1}; - s1.set_option(read_message_max{64 * 1024 * 1024}); - s1.set_option(auto_fragment{false}); - s1.set_option(pmd); - s1.open(endpoint_type{ - address_type::from_string("127.0.0.1"), 6000 }, ec); - - websocket::sync_echo_server s2{&std::cout}; - s2.set_option(read_message_max{64 * 1024 * 1024}); - s2.set_option(auto_fragment{false}); - s2.set_option(pmd); - s2.open(endpoint_type{ - address_type::from_string("127.0.0.1"), 6001 }, ec); - - sig_wait(); -} diff --git a/src/beast/examples/websocket_example.cpp b/src/beast/examples/websocket_example.cpp deleted file mode 100644 index bda489be68..0000000000 --- a/src/beast/examples/websocket_example.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#include -#include -#include -#include -#include - -int main() -{ - // Normal boost::asio setup - std::string const host = "echo.websocket.org"; - boost::asio::io_service ios; - boost::asio::ip::tcp::resolver r{ios}; - boost::asio::ip::tcp::socket sock{ios}; - boost::asio::connect(sock, - r.resolve(boost::asio::ip::tcp::resolver::query{host, "80"})); - - // WebSocket connect and send message using beast - beast::websocket::stream ws{sock}; - ws.handshake(host, "/"); - ws.write(boost::asio::buffer(std::string("Hello, world!"))); - - // Receive WebSocket message, print and close using beast - beast::streambuf sb; - beast::websocket::opcode op; - ws.read(op, sb); - ws.close(beast::websocket::close_code::normal); - std::cout << beast::to_string(sb.data()) << "\n"; -} diff --git a/src/beast/examples/websocket_sync_echo_server.hpp b/src/beast/examples/websocket_sync_echo_server.hpp deleted file mode 100644 index 1f41b52ce1..0000000000 --- a/src/beast/examples/websocket_sync_echo_server.hpp +++ /dev/null @@ -1,326 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef WEBSOCKET_SYNC_ECHO_SERVER_HPP -#define WEBSOCKET_SYNC_ECHO_SERVER_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace websocket { - -/** Synchronous WebSocket echo client/server -*/ -class sync_echo_server -{ -public: - using error_code = beast::error_code; - using endpoint_type = boost::asio::ip::tcp::endpoint; - using address_type = boost::asio::ip::address; - using socket_type = boost::asio::ip::tcp::socket; - -private: - struct identity - { - template - void - operator()(beast::http::message< - true, Body, Fields>& req) const - { - req.fields.replace("User-Agent", "sync_echo_client"); - } - - template - void - operator()(beast::http::message< - false, Body, Fields>& resp) const - { - resp.fields.replace("Server", "sync_echo_server"); - } - }; - - /** A container of type-erased option setters. - */ - template - class options_set - { - // workaround for std::function bug in msvc - struct callable - { - virtual ~callable() = default; - virtual void operator()( - beast::websocket::stream&) = 0; - }; - - template - class callable_impl : public callable - { - T t_; - - public: - template - callable_impl(U&& u) - : t_(std::forward(u)) - { - } - - void - operator()(beast::websocket::stream& ws) - { - t_(ws); - } - }; - - template - class lambda - { - Opt opt_; - - public: - lambda(lambda&&) = default; - lambda(lambda const&) = default; - - lambda(Opt const& opt) - : opt_(opt) - { - } - - void - operator()(beast::websocket::stream& ws) const - { - ws.set_option(opt_); - } - }; - - std::unordered_map> list_; - - public: - template - void - set_option(Opt const& opt) - { - std::unique_ptr p; - p.reset(new callable_impl>{opt}); - list_[std::type_index{ - typeid(Opt)}] = std::move(p); - } - - void - set_options(beast::websocket::stream& ws) - { - for(auto const& op : list_) - (*op.second)(ws); - } - }; - - std::ostream* log_; - boost::asio::io_service ios_; - socket_type sock_; - endpoint_type ep_; - boost::asio::ip::tcp::acceptor acceptor_; - std::thread thread_; - options_set opts_; - -public: - /** Constructor. - - @param log A pointer to a stream to log to, or `nullptr` - to disable logging. - */ - sync_echo_server(std::ostream* log) - : log_(log) - , sock_(ios_) - , acceptor_(ios_) - { - opts_.set_option( - beast::websocket::decorate(identity{})); - } - - /** Destructor. - */ - ~sync_echo_server() - { - if(thread_.joinable()) - { - error_code ec; - ios_.dispatch( - [&]{ acceptor_.close(ec); }); - thread_.join(); - } - } - - /** Return the listening endpoint. - */ - endpoint_type - local_endpoint() const - { - return acceptor_.local_endpoint(); - } - - /** Set a websocket option. - - The option will be applied to all new connections. - - @param opt The option to apply. - */ - template - void - set_option(Opt const& opt) - { - opts_.set_option(opt); - } - - /** Open a listening port. - - @param ep The address and port to bind to. - - @param ec Set to the error, if any occurred. - */ - void - open(endpoint_type const& ep, error_code& ec) - { - acceptor_.open(ep.protocol(), ec); - if(ec) - return fail("open", ec); - acceptor_.set_option( - boost::asio::socket_base::reuse_address{true}); - acceptor_.bind(ep, ec); - if(ec) - return fail("bind", ec); - acceptor_.listen( - boost::asio::socket_base::max_connections, ec); - if(ec) - return fail("listen", ec); - acceptor_.async_accept(sock_, ep_, - std::bind(&sync_echo_server::on_accept, this, - beast::asio::placeholders::error)); - thread_ = std::thread{[&]{ ios_.run(); }}; - } - -private: - void - fail(std::string what, error_code ec) - { - if(log_) - { - static std::mutex m; - std::lock_guard lock{m}; - (*log_) << what << ": " << - ec.message() << std::endl; - } - } - - void - fail(std::string what, error_code ec, - int id, endpoint_type const& ep) - { - if(log_) - if(ec != beast::websocket::error::closed) - fail("[#" + std::to_string(id) + " " + - boost::lexical_cast(ep) + - "] " + what, ec); - } - - void - on_accept(error_code ec) - { - if(ec == boost::asio::error::operation_aborted) - return; - if(ec) - return fail("accept", ec); - struct lambda - { - std::size_t id; - endpoint_type ep; - sync_echo_server& self; - boost::asio::io_service::work work; - // Must be destroyed before work otherwise the - // io_service could be destroyed before the socket. - socket_type sock; - - lambda(sync_echo_server& self_, - endpoint_type const& ep_, - socket_type&& sock_) - : id([] - { - static std::atomic n{0}; - return ++n; - }()) - , ep(ep_) - , self(self_) - , work(sock_.get_io_service()) - , sock(std::move(sock_)) - { - } - - void operator()() - { - self.do_peer(id, ep, std::move(sock)); - } - }; - std::thread{lambda{*this, ep_, std::move(sock_)}}.detach(); - acceptor_.async_accept(sock_, ep_, - std::bind(&sync_echo_server::on_accept, this, - beast::asio::placeholders::error)); - } - - void - do_peer(std::size_t id, - endpoint_type const& ep, socket_type&& sock) - { - using boost::asio::buffer; - using boost::asio::buffer_copy; - beast::websocket::stream< - socket_type> ws{std::move(sock)}; - opts_.set_options(ws); - error_code ec; - ws.accept(ec); - if(ec) - { - fail("accept", ec, id, ep); - return; - } - for(;;) - { - beast::websocket::opcode op; - beast::streambuf sb; - ws.read(op, sb, ec); - if(ec) - { - auto const s = ec.message(); - break; - } - ws.set_option(beast::websocket::message_type{op}); - ws.write(sb.data(), ec); - if(ec) - break; - } - if(ec && ec != beast::websocket::error::closed) - { - fail("read", ec, id, ep); - } - } -}; - -} // websocket - -#endif diff --git a/src/beast/extras/beast/doc_debug.hpp b/src/beast/extras/beast/doc_debug.hpp index 5b5587f0dd..7023c8a2cf 100644 --- a/src/beast/extras/beast/doc_debug.hpp +++ b/src/beast/extras/beast/doc_debug.hpp @@ -10,7 +10,7 @@ namespace beast { -#if GENERATING_DOCS +#if BEAST_DOXYGEN /// doc type (documentation debug helper) using doc_type = int; diff --git a/src/beast/extras/beast/test/fail_counter.hpp b/src/beast/extras/beast/test/fail_counter.hpp index 7bfe486bf3..8c577cbea8 100644 --- a/src/beast/extras/beast/test/fail_counter.hpp +++ b/src/beast/extras/beast/test/fail_counter.hpp @@ -9,6 +9,7 @@ #define BEAST_TEST_FAIL_COUNTER_HPP #include +#include namespace beast { namespace test { @@ -82,6 +83,26 @@ make_error_code(error ev) detail::get_error_category()}; } +/** An error code with an error set on default construction + + Default constructed versions of this object will have + an error code set right away. This helps tests find code + which forgets to clear the error code on success. +*/ +struct fail_error_code : error_code +{ + fail_error_code() + : error_code(make_error_code(error::fail_error)) + { + } + + template + fail_error_code(Arg0&& arg0, ArgN&&... argn) + : error_code(arg0, std::forward(argn)...) + { + } +}; + /** A countdown to simulated failure. On the Nth operation, the class will fail with the specified @@ -114,7 +135,7 @@ public: if(n_ > 0) --n_; if(! n_) - throw system_error{ec_}; + BOOST_THROW_EXCEPTION(system_error{ec_}); } /// Set an error code on the Nth failure @@ -128,6 +149,7 @@ public: ec = ec_; return true; } + ec.assign(0, ec.category()); return false; } }; diff --git a/src/beast/extras/beast/test/fail_stream.hpp b/src/beast/extras/beast/test/fail_stream.hpp index 8ec63dc524..c4b38538bf 100644 --- a/src/beast/extras/beast/test/fail_stream.hpp +++ b/src/beast/extras/beast/test/fail_stream.hpp @@ -8,10 +8,10 @@ #ifndef BEAST_TEST_FAIL_STREAM_HPP #define BEAST_TEST_FAIL_STREAM_HPP -#include +#include #include #include -#include +#include #include #include #include @@ -36,8 +36,7 @@ public: typename std::remove_reference::type; using lowest_layer_type = - typename beast::detail::get_lowest_layer< - next_layer_type>::type; + typename get_lowest_layer::type; fail_stream(fail_stream&&) = delete; fail_stream(fail_stream const&) = delete; @@ -103,20 +102,19 @@ public: } template - typename async_completion< - ReadHandler, void(error_code)>::result_type + async_return_type< + ReadHandler, void(error_code, std::size_t)> async_read_some(MutableBufferSequence const& buffers, ReadHandler&& handler) { error_code ec; if(pfc_->fail(ec)) { - async_completion< - ReadHandler, void(error_code, std::size_t) - > completion{handler}; + async_completion init{handler}; next_layer_.get_io_service().post( - bind_handler(completion.handler, ec, 0)); - return completion.result.get(); + bind_handler(init.completion_handler, ec, 0)); + return init.result.get(); } return next_layer_.async_read_some(buffers, std::forward(handler)); @@ -140,20 +138,19 @@ public: } template - typename async_completion< - WriteHandler, void(error_code)>::result_type + async_return_type< + WriteHandler, void(error_code, std::size_t)> async_write_some(ConstBufferSequence const& buffers, WriteHandler&& handler) { error_code ec; if(pfc_->fail(ec)) { - async_completion< - WriteHandler, void(error_code, std::size_t) - > completion{handler}; + async_completion init{handler}; next_layer_.get_io_service().post( - bind_handler(completion.handler, ec, 0)); - return completion.result.get(); + bind_handler(init.completion_handler, ec, 0)); + return init.result.get(); } return next_layer_.async_write_some(buffers, std::forward(handler)); diff --git a/src/beast/extras/beast/test/pipe_stream.hpp b/src/beast/extras/beast/test/pipe_stream.hpp new file mode 100644 index 0000000000..b40690242a --- /dev/null +++ b/src/beast/extras/beast/test/pipe_stream.hpp @@ -0,0 +1,529 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_TEST_PIPE_STREAM_HPP +#define BEAST_TEST_PIPE_STREAM_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace test { + +/** A bidirectional in-memory communication channel + + An instance of this class provides a client and server + endpoint that are automatically connected to each other + similarly to a connected socket. + + Test pipes are used to facilitate writing unit tests + where the behavior of the transport is tightly controlled + to help illuminate all code paths (for code coverage) +*/ +class pipe +{ +public: + using buffer_type = flat_buffer; + +private: + struct read_op + { + virtual ~read_op() = default; + virtual void operator()() = 0; + }; + + struct state + { + std::mutex m; + buffer_type b; + std::condition_variable cv; + std::unique_ptr op; + bool eof = false; + }; + + state s_[2]; + +public: + /** Represents an endpoint. + + Each pipe has a client stream and a server stream. + */ + class stream + { + friend class pipe; + + template + class read_op_impl; + + state& in_; + state& out_; + boost::asio::io_service& ios_; + fail_counter* fc_ = nullptr; + std::size_t read_max_ = + (std::numeric_limits::max)(); + std::size_t write_max_ = + (std::numeric_limits::max)(); + + stream(state& in, state& out, + boost::asio::io_service& ios) + : in_(in) + , out_(out) + , ios_(ios) + , buffer(in_.b) + { + } + + public: + using buffer_type = pipe::buffer_type; + + /// Direct access to the underlying buffer + buffer_type& buffer; + + /// Counts the number of read calls + std::size_t nread = 0; + + /// Counts the number of write calls + std::size_t nwrite = 0; + + ~stream() = default; + stream(stream&&) = default; + + /// Set the fail counter on the object + void + fail(fail_counter& fc) + { + fc_ = &fc; + } + + /// Return the `io_service` associated with the object + boost::asio::io_service& + get_io_service() + { + return ios_; + } + + /// Set the maximum number of bytes returned by read_some + void + read_size(std::size_t n) + { + read_max_ = n; + } + + /// Set the maximum number of bytes returned by write_some + void + write_size(std::size_t n) + { + write_max_ = n; + } + + /// Returns a string representing the pending input data + string_view + str() const + { + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + return { + buffer_cast(*in_.b.data().begin()), + buffer_size(*in_.b.data().begin())}; + } + + /// Clear the buffer holding the input data + void + clear() + { + in_.b.consume((std::numeric_limits< + std::size_t>::max)()); + } + + /** Close the stream. + + The other end of the pipe will see + `boost::asio::error::eof` on read. + */ + template + void + close(); + + template + std::size_t + read_some(MutableBufferSequence const& buffers); + + template + std::size_t + read_some(MutableBufferSequence const& buffers, + error_code& ec); + + template + async_return_type< + ReadHandler, void(error_code, std::size_t)> + async_read_some(MutableBufferSequence const& buffers, + ReadHandler&& handler); + + template + std::size_t + write_some(ConstBufferSequence const& buffers); + + template + std::size_t + write_some( + ConstBufferSequence const& buffers, error_code&); + + template + async_return_type< + WriteHandler, void(error_code, std::size_t)> + async_write_some(ConstBufferSequence const& buffers, + WriteHandler&& handler); + + friend + void + teardown(websocket::teardown_tag, + stream&, boost::system::error_code& ec) + { + ec.assign(0, ec.category()); + } + + template + friend + void + async_teardown(websocket::teardown_tag, + stream& s, TeardownHandler&& handler) + { + s.get_io_service().post( + bind_handler(std::move(handler), + error_code{})); + } + }; + + /** Constructor. + + The client and server endpoints will use the same `io_service`. + */ + explicit + pipe(boost::asio::io_service& ios) + : client(s_[0], s_[1], ios) + , server(s_[1], s_[0], ios) + { + } + + /** Constructor. + + The client and server endpoints will different `io_service` objects. + */ + explicit + pipe(boost::asio::io_service& ios1, + boost::asio::io_service& ios2) + : client(s_[0], s_[1], ios1) + , server(s_[1], s_[0], ios2) + { + } + + /// Represents the client endpoint + stream client; + + /// Represents the server endpoint + stream server; +}; + +//------------------------------------------------------------------------------ + +template +class pipe::stream::read_op_impl : + public pipe::read_op +{ + stream& s_; + Buffers b_; + Handler h_; + +public: + read_op_impl(stream& s, + Buffers const& b, Handler&& h) + : s_(s) + , b_(b) + , h_(std::move(h)) + { + } + + read_op_impl(stream& s, + Buffers const& b, Handler const& h) + : s_(s) + , b_(b) + , h_(h) + { + } + + void + operator()() override; +}; + +//------------------------------------------------------------------------------ + +template +void +pipe::stream:: +read_op_impl::operator()() +{ + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + s_.ios_.post( + [&]() + { + BOOST_ASSERT(s_.in_.op); + std::unique_lock lock{s_.in_.m}; + if(s_.in_.b.size() > 0) + { + auto const bytes_transferred = buffer_copy( + b_, s_.in_.b.data(), s_.read_max_); + s_.in_.b.consume(bytes_transferred); + auto& s = s_; + Handler h{std::move(h_)}; + lock.unlock(); + s.in_.op.reset(nullptr); + ++s.nread; + s.ios_.post(bind_handler(std::move(h), + error_code{}, bytes_transferred)); + } + else + { + BOOST_ASSERT(s_.in_.eof); + auto& s = s_; + Handler h{std::move(h_)}; + lock.unlock(); + s.in_.op.reset(nullptr); + ++s.nread; + s.ios_.post(bind_handler(std::move(h), + boost::asio::error::eof, 0)); + } + }); +} + +//------------------------------------------------------------------------------ + +template +void +pipe::stream:: +close() +{ + std::lock_guard lock{out_.m}; + out_.eof = true; + if(out_.op) + out_.op.get()->operator()(); + else + out_.cv.notify_all(); +} + + +template +std::size_t +pipe::stream:: +read_some(MutableBufferSequence const& buffers) +{ + static_assert(is_mutable_buffer_sequence< + MutableBufferSequence>::value, + "MutableBufferSequence requirements not met"); + error_code ec; + auto const n = read_some(buffers, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); + return n; +} + +template +std::size_t +pipe::stream:: +read_some(MutableBufferSequence const& buffers, + error_code& ec) +{ + static_assert(is_mutable_buffer_sequence< + MutableBufferSequence>::value, + "MutableBufferSequence requirements not met"); + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + BOOST_ASSERT(! in_.op); + BOOST_ASSERT(buffer_size(buffers) > 0); + if(fc_ && fc_->fail(ec)) + return 0; + std::unique_lock lock{in_.m}; + in_.cv.wait(lock, + [&]() + { + return in_.b.size() > 0 || in_.eof; + }); + std::size_t bytes_transferred; + if(in_.b.size() > 0) + { + ec.assign(0, ec.category()); + bytes_transferred = buffer_copy( + buffers, in_.b.data(), read_max_); + in_.b.consume(bytes_transferred); + } + else + { + BOOST_ASSERT(in_.eof); + bytes_transferred = 0; + ec = boost::asio::error::eof; + } + ++nread; + return bytes_transferred; +} + +template +async_return_type< + ReadHandler, void(error_code, std::size_t)> +pipe::stream:: +async_read_some(MutableBufferSequence const& buffers, + ReadHandler&& handler) +{ + static_assert(is_mutable_buffer_sequence< + MutableBufferSequence>::value, + "MutableBufferSequence requirements not met"); + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + BOOST_ASSERT(! in_.op); + BOOST_ASSERT(buffer_size(buffers) > 0); + async_completion init{handler}; + if(fc_) + { + error_code ec; + if(fc_->fail(ec)) + return ios_.post(bind_handler( + init.completion_handler, ec, 0)); + } + { + std::unique_lock lock{in_.m}; + if(in_.eof) + { + lock.unlock(); + ++nread; + ios_.post(bind_handler(init.completion_handler, + boost::asio::error::eof, 0)); + } + else if(buffer_size(buffers) == 0 || + buffer_size(in_.b.data()) > 0) + { + auto const bytes_transferred = buffer_copy( + buffers, in_.b.data(), read_max_); + in_.b.consume(bytes_transferred); + lock.unlock(); + ++nread; + ios_.post(bind_handler(init.completion_handler, + error_code{}, bytes_transferred)); + } + else + { + in_.op.reset(new read_op_impl, + MutableBufferSequence>{*this, buffers, + init.completion_handler}); + } + } + return init.result.get(); +} + +template +std::size_t +pipe::stream:: +write_some(ConstBufferSequence const& buffers) +{ + static_assert(is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + BOOST_ASSERT(! out_.eof); + error_code ec; + auto const bytes_transferred = + write_some(buffers, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); + return bytes_transferred; +} + +template +std::size_t +pipe::stream:: +write_some( + ConstBufferSequence const& buffers, error_code& ec) +{ + static_assert(is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + BOOST_ASSERT(! out_.eof); + if(fc_ && fc_->fail(ec)) + return 0; + auto const n = (std::min)( + buffer_size(buffers), write_max_); + std::unique_lock lock{out_.m}; + auto const bytes_transferred = + buffer_copy(out_.b.prepare(n), buffers); + out_.b.commit(bytes_transferred); + lock.unlock(); + if(out_.op) + out_.op.get()->operator()(); + else + out_.cv.notify_all(); + ++nwrite; + ec.assign(0, ec.category()); + return bytes_transferred; +} + +template +async_return_type< + WriteHandler, void(error_code, std::size_t)> +pipe::stream:: +async_write_some(ConstBufferSequence const& buffers, + WriteHandler&& handler) +{ + static_assert(is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + BOOST_ASSERT(! out_.eof); + async_completion init{handler}; + if(fc_) + { + error_code ec; + if(fc_->fail(ec)) + return ios_.post(bind_handler( + init.completion_handler, ec, 0)); + } + auto const n = + (std::min)(buffer_size(buffers), write_max_); + std::unique_lock lock{out_.m}; + auto const bytes_transferred = + buffer_copy(out_.b.prepare(n), buffers); + out_.b.commit(bytes_transferred); + lock.unlock(); + if(out_.op) + out_.op.get()->operator()(); + else + out_.cv.notify_all(); + ++nwrite; + ios_.post(bind_handler(init.completion_handler, + error_code{}, bytes_transferred)); + return init.result.get(); +} + +} // test +} // beast + +#endif diff --git a/src/beast/extras/beast/test/string_iostream.hpp b/src/beast/extras/beast/test/string_iostream.hpp new file mode 100644 index 0000000000..eeeb7e21ab --- /dev/null +++ b/src/beast/extras/beast/test/string_iostream.hpp @@ -0,0 +1,173 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_TEST_STRING_IOSTREAM_HPP +#define BEAST_TEST_STRING_IOSTREAM_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace test { + +/** A SyncStream and AsyncStream that reads from a string and writes to another string. + + This class behaves like a socket, except that written data is + appended to a string exposed as a public data member, and when + data is read it comes from a string provided at construction. +*/ +class string_iostream +{ + std::string s_; + boost::asio::const_buffer cb_; + boost::asio::io_service& ios_; + std::size_t read_max_; + +public: + std::string str; + + string_iostream(boost::asio::io_service& ios, + std::string s, std::size_t read_max = + (std::numeric_limits::max)()) + : s_(std::move(s)) + , cb_(boost::asio::buffer(s_)) + , ios_(ios) + , read_max_(read_max) + { + } + + boost::asio::io_service& + get_io_service() + { + return ios_; + } + + template + std::size_t + read_some(MutableBufferSequence const& buffers) + { + error_code ec; + auto const n = read_some(buffers, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); + return n; + } + + template + std::size_t + read_some(MutableBufferSequence const& buffers, + error_code& ec) + { + auto const n = boost::asio::buffer_copy( + buffers, buffer_prefix(read_max_, cb_)); + if(n > 0) + { + ec.assign(0, ec.category()); + cb_ = cb_ + n; + } + else + { + ec = boost::asio::error::eof; + } + return n; + } + + template + async_return_type< + ReadHandler, void(error_code, std::size_t)> + async_read_some(MutableBufferSequence const& buffers, + ReadHandler&& handler) + { + auto const n = boost::asio::buffer_copy( + buffers, boost::asio::buffer(s_)); + error_code ec; + if(n > 0) + s_.erase(0, n); + else + ec = boost::asio::error::eof; + async_completion init{handler}; + ios_.post(bind_handler( + init.completion_handler, ec, n)); + return init.result.get(); + } + + template + std::size_t + write_some(ConstBufferSequence const& buffers) + { + error_code ec; + auto const n = write_some(buffers, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); + return n; + } + + template + std::size_t + write_some( + ConstBufferSequence const& buffers, error_code& ec) + { + ec.assign(0, ec.category()); + using boost::asio::buffer_size; + using boost::asio::buffer_cast; + auto const n = buffer_size(buffers); + str.reserve(str.size() + n); + for(boost::asio::const_buffer buffer : buffers) + str.append(buffer_cast(buffer), + buffer_size(buffer)); + return n; + } + + template + async_return_type< + WriteHandler, void(error_code, std::size_t)> + async_write_some(ConstBufferSequence const& buffers, + WriteHandler&& handler) + { + error_code ec; + auto const bytes_transferred = write_some(buffers, ec); + async_completion init{handler}; + get_io_service().post( + bind_handler(init.completion_handler, ec, bytes_transferred)); + return init.result.get(); + } + + friend + void + teardown(websocket::teardown_tag, + string_iostream&, + boost::system::error_code& ec) + { + ec.assign(0, ec.category()); + } + + template + friend + void + async_teardown(websocket::teardown_tag, + string_iostream& stream, + TeardownHandler&& handler) + { + stream.get_io_service().post( + bind_handler(std::move(handler), + error_code{})); + } +}; + +} // test +} // beast + +#endif diff --git a/src/beast/extras/beast/test/string_istream.hpp b/src/beast/extras/beast/test/string_istream.hpp index e7c32064e1..c9692b8726 100644 --- a/src/beast/extras/beast/test/string_istream.hpp +++ b/src/beast/extras/beast/test/string_istream.hpp @@ -8,12 +8,13 @@ #ifndef BEAST_TEST_STRING_ISTREAM_HPP #define BEAST_TEST_STRING_ISTREAM_HPP -#include +#include #include #include -#include +#include #include #include +#include #include namespace beast { @@ -56,7 +57,7 @@ public: error_code ec; auto const n = read_some(buffers, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); return n; } @@ -66,17 +67,22 @@ public: error_code& ec) { auto const n = boost::asio::buffer_copy( - buffers, prepare_buffer(read_max_, cb_)); + buffers, cb_, read_max_); if(n > 0) + { + ec.assign(0, ec.category()); cb_ = cb_ + n; + } else + { ec = boost::asio::error::eof; + } return n; } template - typename async_completion::result_type + async_return_type< + ReadHandler, void(error_code, std::size_t)> async_read_some(MutableBufferSequence const& buffers, ReadHandler&& handler) { @@ -88,10 +94,10 @@ public: else ec = boost::asio::error::eof; async_completion completion{handler}; + void(error_code, std::size_t)> init{handler}; ios_.post(bind_handler( - completion.handler, ec, n)); - return completion.result.get(); + init.completion_handler, ec, n)); + return init.result.get(); } template @@ -101,29 +107,51 @@ public: error_code ec; auto const n = write_some(buffers, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); return n; } template std::size_t write_some(ConstBufferSequence const& buffers, - error_code&) + error_code& ec) { + ec.assign(0, ec.category()); return boost::asio::buffer_size(buffers); } template - typename async_completion::result_type + async_return_type< + WriteHandler, void(error_code, std::size_t)> async_write_some(ConstBuffeSequence const& buffers, WriteHandler&& handler) { async_completion completion{handler}; - ios_.post(bind_handler(completion.handler, + void(error_code, std::size_t)> init{handler}; + ios_.post(bind_handler(init.completion_handler, error_code{}, boost::asio::buffer_size(buffers))); - return completion.result.get(); + return init.result.get(); + } + + friend + void + teardown(websocket::teardown_tag, + string_istream&, + boost::system::error_code& ec) + { + ec.assign(0, ec.category()); + } + + template + friend + void + async_teardown(websocket::teardown_tag, + string_istream& stream, + TeardownHandler&& handler) + { + stream.get_io_service().post( + bind_handler(std::move(handler), + error_code{})); } }; diff --git a/src/beast/extras/beast/test/string_ostream.hpp b/src/beast/extras/beast/test/string_ostream.hpp index a939da7aab..dd57d8a999 100644 --- a/src/beast/extras/beast/test/string_ostream.hpp +++ b/src/beast/extras/beast/test/string_ostream.hpp @@ -8,11 +8,14 @@ #ifndef BEAST_TEST_STRING_OSTREAM_HPP #define BEAST_TEST_STRING_OSTREAM_HPP -#include +#include #include +#include #include +#include #include #include +#include #include namespace beast { @@ -21,13 +24,17 @@ namespace test { class string_ostream { boost::asio::io_service& ios_; + std::size_t write_max_; public: std::string str; explicit - string_ostream(boost::asio::io_service& ios) + string_ostream(boost::asio::io_service& ios, + std::size_t write_max = + (std::numeric_limits::max)()) : ios_(ios) + , write_max_(write_max) { } @@ -44,29 +51,30 @@ public: error_code ec; auto const n = read_some(buffers, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); return n; } template std::size_t - read_some(MutableBufferSequence const& buffers, + read_some(MutableBufferSequence const&, error_code& ec) { + ec = boost::asio::error::eof; return 0; } template - typename async_completion::result_type - async_read_some(MutableBufferSequence const& buffers, + async_return_type< + ReadHandler, void(error_code, std::size_t)> + async_read_some(MutableBufferSequence const&, ReadHandler&& handler) { async_completion completion{handler}; - ios_.post(bind_handler(completion.handler, - error_code{}, 0)); - return completion.result.get(); + void(error_code, std::size_t)> init{handler}; + ios_.post(bind_handler(init.completion_handler, + boost::asio::error::eof, 0)); + return init.result.get(); } template @@ -76,39 +84,62 @@ public: error_code ec; auto const n = write_some(buffers, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); return n; } template std::size_t write_some( - ConstBufferSequence const& buffers, error_code&) + ConstBufferSequence const& buffers, error_code& ec) { - auto const n = buffer_size(buffers); + ec.assign(0, ec.category()); using boost::asio::buffer_size; using boost::asio::buffer_cast; + auto const n = + (std::min)(buffer_size(buffers), write_max_); str.reserve(str.size() + n); - for(auto const& buffer : buffers) + for(boost::asio::const_buffer buffer : + buffer_prefix(n, buffers)) str.append(buffer_cast(buffer), buffer_size(buffer)); return n; } template - typename async_completion< - WriteHandler, void(error_code)>::result_type + async_return_type< + WriteHandler, void(error_code, std::size_t)> async_write_some(ConstBufferSequence const& buffers, WriteHandler&& handler) { error_code ec; auto const bytes_transferred = write_some(buffers, ec); - async_completion< - WriteHandler, void(error_code, std::size_t) - > completion{handler}; + async_completion init{handler}; get_io_service().post( - bind_handler(completion.handler, ec, bytes_transferred)); - return completion.result.get(); + bind_handler(init.completion_handler, ec, bytes_transferred)); + return init.result.get(); + } + + friend + void + teardown(websocket::teardown_tag, + string_ostream&, + boost::system::error_code& ec) + { + ec.assign(0, ec.category()); + } + + template + friend + void + async_teardown(websocket::teardown_tag, + string_ostream& stream, + TeardownHandler&& handler) + { + stream.get_io_service().post( + bind_handler(std::move(handler), + error_code{})); } }; diff --git a/src/beast/extras/beast/test/test_allocator.hpp b/src/beast/extras/beast/test/test_allocator.hpp new file mode 100644 index 0000000000..bc468de2ae --- /dev/null +++ b/src/beast/extras/beast/test/test_allocator.hpp @@ -0,0 +1,166 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_TEST_TEST_ALLOCATOR_HPP +#define BEAST_TEST_TEST_ALLOCATOR_HPP + +#include +#include +#include + +namespace beast { +namespace test { + +struct test_allocator_info +{ + std::size_t id; + std::size_t ncopy = 0; + std::size_t nmove = 0; + std::size_t nmassign = 0; + std::size_t ncpassign = 0; + std::size_t nselect = 0; + + test_allocator_info() + : id([] + { + static std::atomic sid(0); + return ++sid; + }()) + { + } +}; + +template +class test_allocator; + +template +struct test_allocator_base +{ +}; + +template +struct test_allocator_base +{ + static + test_allocator + select_on_container_copy_construction(test_allocator< + T, Equal, Assign, Move, Swap, true> const& a) + { + return test_allocator{}; + } +}; + +template +class test_allocator : public test_allocator_base< + T, Equal, Assign, Move, Swap, Select> +{ + std::shared_ptr info_; + + template + friend class test_allocator; + +public: + using value_type = T; + + using propagate_on_container_copy_assignment = + std::integral_constant; + + using propagate_on_container_move_assignment = + std::integral_constant; + + using propagate_on_container_swap = + std::integral_constant; + + template + struct rebind + { + using other = test_allocator; + }; + + test_allocator() + : info_(std::make_shared()) + { + } + + test_allocator(test_allocator const& u) noexcept + : info_(u.info_) + { + ++info_->ncopy; + } + + template + test_allocator(test_allocator const& u) noexcept + : info_(u.info_) + { + ++info_->ncopy; + } + + test_allocator(test_allocator&& t) + : info_(t.info_) + { + ++info_->nmove; + } + + test_allocator& + operator=(test_allocator const& u) noexcept + { + info_ = u.info_; + ++info_->ncpassign; + return *this; + } + + test_allocator& + operator=(test_allocator&& u) noexcept + { + info_ = u.info_; + ++info_->nmassign; + return *this; + } + + value_type* + allocate(std::size_t n) + { + return static_cast( + ::operator new (n*sizeof(value_type))); + } + + void + deallocate(value_type* p, std::size_t) noexcept + { + ::operator delete(p); + } + + bool + operator==(test_allocator const& other) const + { + return id() == other.id() || Equal; + } + + bool + operator!=(test_allocator const& other) const + { + return ! this->operator==(other); + } + + std::size_t + id() const + { + return info_->id; + } + + test_allocator_info const* + operator->() const + { + return info_.get(); + } +}; + +} // test +} // beast + +#endif diff --git a/src/beast/extras/beast/test/yield_to.hpp b/src/beast/extras/beast/test/yield_to.hpp index c6b3a8679a..df5b8a341d 100644 --- a/src/beast/extras/beast/test/yield_to.hpp +++ b/src/beast/extras/beast/test/yield_to.hpp @@ -15,6 +15,7 @@ #include #include #include +#include namespace beast { namespace test { @@ -32,30 +33,31 @@ protected: private: boost::optional work_; - std::thread thread_; + std::vector threads_; std::mutex m_; std::condition_variable cv_; - bool running_ = false; + std::size_t running_ = 0; public: /// The type of yield context passed to functions. using yield_context = boost::asio::yield_context; - enable_yield_to() + explicit + enable_yield_to(std::size_t concurrency = 1) : work_(ios_) - , thread_([&] - { - ios_.run(); - } - ) { + threads_.reserve(concurrency); + while(concurrency--) + threads_.emplace_back( + [&]{ ios_.run(); }); } ~enable_yield_to() { work_ = boost::none; - thread_.join(); + for(auto& t : threads_) + t.join(); } /// Return the `io_service` associated with the object @@ -65,61 +67,65 @@ public: return ios_; } - /** Run a function in a coroutine. + /** Run one or more functions, each in a coroutine. - This call will block until the coroutine terminates. - - Function will be called with this signature: + This call will block until all coroutines terminate. + Each functions should have this signature: @code - void f(args..., yield_context); + void f(yield_context); @endcode - @param f The Callable object to invoke. - - @param args Optional arguments forwarded to the callable object. + @param fn... One or more functions to invoke. */ -#if GENERATING_DOCS - template +#if BEAST_DOXYGEN + template void - yield_to(F&& f, Args&&... args); + yield_to(FN&&... fn) #else - template + template void - yield_to(F&& f); - - template - void - yield_to(Function&& f, Arg&& arg, Args&&... args) - { - yield_to(std::bind(f, - std::forward(arg), - std::forward(args)..., - std::placeholders::_1)); - } + yield_to(F0&& f0, FN&&... fn); #endif + +private: + void + spawn() + { + } + + template + void + spawn(F0&& f, FN&&... fn); }; -template +template void -enable_yield_to::yield_to(Function&& f) +enable_yield_to:: +yield_to(F0&& f0, FN&&... fn) +{ + running_ = 1 + sizeof...(FN); + spawn(f0, fn...); + std::unique_lock lock{m_}; + cv_.wait(lock, [&]{ return running_ == 0; }); +} + +template +inline +void +enable_yield_to:: +spawn(F0&& f, FN&&... fn) { - { - std::lock_guard lock(m_); - running_ = true; - } boost::asio::spawn(ios_, - [&](boost::asio::yield_context do_yield) + [&](yield_context yield) { - f(do_yield); - std::lock_guard lock(m_); - running_ = false; - cv_.notify_all(); + f(yield); + std::lock_guard lock{m_}; + if(--running_ == 0) + cv_.notify_all(); } , boost::coroutines::attributes(2 * 1024 * 1024)); - - std::unique_lock lock(m_); - cv_.wait(lock, [&]{ return ! running_; }); + spawn(fn...); } } // test diff --git a/src/beast/extras/beast/unit_test/dstream.hpp b/src/beast/extras/beast/unit_test/dstream.hpp index 44eb599beb..fe02a26edd 100644 --- a/src/beast/extras/beast/unit_test/dstream.hpp +++ b/src/beast/extras/beast/unit_test/dstream.hpp @@ -8,28 +8,22 @@ #ifndef BEAST_UNIT_TEST_DSTREAM_HPP #define BEAST_UNIT_TEST_DSTREAM_HPP +#include #include #include #include #include #include -#ifdef _MSC_VER -# ifndef NOMINMAX -# define NOMINMAX 1 -# endif -# ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -# endif -# include -# undef WIN32_LEAN_AND_MEAN -# undef NOMINMAX +#ifdef BOOST_WINDOWS +#include +//#include #endif namespace beast { namespace unit_test { -#ifdef _MSC_VER +#ifdef BOOST_WINDOWS namespace detail { @@ -48,14 +42,14 @@ class dstream_buf void write(char const* s) { if(dbg_) - OutputDebugStringA(s); + /*boost::detail::winapi*/::OutputDebugStringA(s); os_ << s; } void write(wchar_t const* s) { if(dbg_) - OutputDebugStringW(s); + /*boost::detail::winapi*/::OutputDebugStringW(s); os_ << s; } @@ -63,7 +57,7 @@ public: explicit dstream_buf(ostream& os) : os_(os) - , dbg_(IsDebuggerPresent() != FALSE) + , dbg_(/*boost::detail::winapi*/::IsDebuggerPresent() != 0) { } diff --git a/src/beast/extras/beast/unit_test/main.cpp b/src/beast/extras/beast/unit_test/main.cpp index 93435ab6c2..e6c1464ac5 100644 --- a/src/beast/extras/beast/unit_test/main.cpp +++ b/src/beast/extras/beast/unit_test/main.cpp @@ -11,12 +11,13 @@ #include #include #include +#include #include #include #include #include -#ifdef _MSC_VER +#ifdef BOOST_MSVC # ifndef WIN32_LEAN_AND_MEAN // VC_EXTRALEAN # define WIN32_LEAN_AND_MEAN # include @@ -78,7 +79,7 @@ int main(int ac, char const* av[]) using namespace std; using namespace beast::unit_test; -#ifdef _MSC_VER +#if BOOST_MSVC { int flags = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); flags |= _CRTDBG_LEAK_CHECK_DF; @@ -99,7 +100,7 @@ int main(int ac, char const* av[]) po::store(po::parse_command_line(ac, av, desc), vm); po::notify(vm); - dstream log{std::cerr}; + dstream log(std::cerr); std::unitbuf(log); if(vm.count("help")) diff --git a/src/beast/extras/beast/unit_test/suite.hpp b/src/beast/extras/beast/unit_test/suite.hpp index 1120daf41c..ce0ec909a4 100644 --- a/src/beast/extras/beast/unit_test/suite.hpp +++ b/src/beast/extras/beast/unit_test/suite.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -551,7 +552,7 @@ fail(std::string const& reason) if(abort_) { aborted_ = true; - throw abort_exception(); + BOOST_THROW_EXCEPTION(abort_exception()); } } @@ -569,7 +570,7 @@ suite:: propagate_abort() { if(abort_ && aborted_) - throw abort_exception(); + BOOST_THROW_EXCEPTION(abort_exception()); } template diff --git a/src/beast/include/beast.hpp b/src/beast/include/beast.hpp new file mode 100644 index 0000000000..fe5add2cf4 --- /dev/null +++ b/src/beast/include/beast.hpp @@ -0,0 +1,19 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HPP +#define BEAST_HPP + +#include + +#include +#include +#include +#include +#include + +#endif diff --git a/src/beast/include/beast/config.hpp b/src/beast/include/beast/config.hpp index b87262c779..9c92df041f 100644 --- a/src/beast/include/beast/config.hpp +++ b/src/beast/include/beast/config.hpp @@ -8,17 +8,23 @@ #ifndef BEAST_CONFIG_HPP #define BEAST_CONFIG_HPP +#include + +// Available to every header +#include +#include + /* _MSC_VER and _MSC_FULL_VER by version: - 14.0 (2015) 1900 190023026 - 14.0 (2015 Update 1) 1900 190023506 - 14.0 (2015 Update 2) 1900 190023918 - 14.0 (2015 Update 3) 1900 190024210 + 14.0 (2015) 1900 190023026 + 14.0 (2015 Update 1) 1900 190023506 + 14.0 (2015 Update 2) 1900 190023918 + 14.0 (2015 Update 3) 1900 190024210 */ -#if defined(_MSC_FULL_VER) -#if _MSC_FULL_VER < 190024210 +#ifdef BOOST_MSVC +#if BOOST_MSVC_FULL_VER < 190024210 static_assert(false, "This library requires Visual Studio 2015 Update 3 or later"); #endif diff --git a/src/beast/include/beast/core.hpp b/src/beast/include/beast/core.hpp index 9377cd99fe..3084c3e886 100644 --- a/src/beast/include/beast/core.hpp +++ b/src/beast/include/beast/core.hpp @@ -10,25 +10,31 @@ #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 +#include +#include +#include #include -#include -#include -#include -#include -#include +#include +#include +#include #endif diff --git a/src/beast/include/beast/core/async_completion.hpp b/src/beast/include/beast/core/async_completion.hpp deleted file mode 100644 index 8c7f64d9ff..0000000000 --- a/src/beast/include/beast/core/async_completion.hpp +++ /dev/null @@ -1,88 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_ASYNC_COMPLETION_HPP -#define BEAST_ASYNC_COMPLETION_HPP - -#include -#include -#include -#include -#include -#include - -namespace beast { - -/** Helper for customizing the return type of asynchronous initiation functions. - - This class template is used to transform caller-provided completion - handlers in calls to asynchronous initiation functions. The transformation - allows customization of the return type of the initiating function, and the - function signature of the final handler. - - @tparam CompletionHandler A completion handler, or a user defined type - with specializations for customizing the return type (for example, - `boost::asio::use_future` or `boost::asio::yield_context`). - - @tparam Signature The callable signature of the final completion handler. - - Example: - @code - ... - template - typename async_completion::result_type - async_initfn(..., CompletionHandler&& handler) - { - async_completion completion{handler}; - ... - return completion.result.get(); - } - @endcode - - @note See - Library Foundations For Asynchronous Operations -*/ -template -struct async_completion -{ - /** The type of the final handler called by the asynchronous initiation function. - - Objects of this type will be callable with the specified signature. - */ - using handler_type = - typename boost::asio::handler_type< - CompletionHandler, Signature>::type; - - /// The type of the value returned by the asynchronous initiation function. - using result_type = typename - boost::asio::async_result::type; - - /** Construct the helper. - - @param token The completion handler. Copies will be made as - required. If `CompletionHandler` is movable, it may also be moved. - */ - async_completion(typename std::remove_reference::type& token) - : handler(std::forward(token)) - , result(handler) - { - static_assert(is_CompletionHandler::value, - "Handler requirements not met"); - } - - /// The final completion handler, callable with the specified signature. - handler_type handler; - - /// The return value of the asynchronous initiation function. - boost::asio::async_result result; -}; - -} // beast - -#endif diff --git a/src/beast/include/beast/core/async_result.hpp b/src/beast/include/beast/core/async_result.hpp new file mode 100644 index 0000000000..8465ba0bc2 --- /dev/null +++ b/src/beast/include/beast/core/async_result.hpp @@ -0,0 +1,205 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_ASYNC_COMPLETION_HPP +#define BEAST_ASYNC_COMPLETION_HPP + +#include +#include +#include +#include +#include +#include + +namespace beast { + +/** An interface for customising the behaviour of an asynchronous initiation function. + + This class is used for determining: + + @li The concrete completion handler type to be called at the end of the + asynchronous operation; + + @li the initiating function return type; and + + @li how the return value of the initiating function is obtained. + + The trait allows the handler and return types to be determined at the point + where the specific completion handler signature is known. + + This template takes advantage of specializations of both + `boost::asio::async_result` and `boost::asio::handler_type` for user-defined + completion token types. The primary template assumes that the + @b CompletionToken is the completion handler. + + @par Example + + The example shows how to define an asynchronous initiation function + whose completion handler receives an error code: + + @code + template< + class AsyncStream, // A stream supporting asynchronous read and write + class Handler // The handler to call with signature void(error_code) + > + async_return_type< // This provides the return type customization + Handler, void(error_code)> + do_async( + AsyncStream& stream, // The stream to work on + Handler&& handler) // Could be an rvalue or const reference + { + // Make sure we have an async stream + static_assert(is_async_stream::value, + "AsyncStream requirements not met"); + + // This helper converts the handler into the real handler type + async_completion init{handler}; + + ... // Create and invoke the composed operation + + // This provides the return value and executor customization + return init.result.get(); + } + @endcode + + @see @ref async_completion, @ref async_return_type, @ref handler_type +*/ +template +class async_result +{ + BOOST_STATIC_ASSERT( + ! std::is_reference::value); + + boost::asio::async_result::type> impl_; + + async_result(async_result const&) = delete; + async_result& operator=(async_result const&) = delete; + +public: + /// The concrete completion handler type for the specific signature. + using completion_handler_type = + typename boost::asio::handler_type< + CompletionToken, Signature>::type; + + /// The return type of the initiating function. + using return_type = + typename boost::asio::async_result< + completion_handler_type>::type; + + /** Construct an async result from a given handler. + + When using a specalised async_result, the constructor has + an opportunity to initialise some state associated with the + completion handler, which is then returned from the initiating + function. + */ + explicit + async_result(completion_handler_type& h) + : impl_(h) + { + } + + /// Obtain the value to be returned from the initiating function. + return_type + get() + { + return impl_.get(); + } +}; + +/** Helper for customizing the return type of asynchronous initiation functions. + + This class template is used to transform caller-provided completion + handlers in calls to asynchronous initiation functions. The transformation + allows customization of the return type of the initiating function, and the + function signature of the final handler. + + Example: + @code + ... + template + typename async_completion::result_type + async_initfn(..., CompletionToken&& handler) + { + async_completion completion{handler}; + ... + return completion.result.get(); + } + @endcode + + @tparam CompletionToken Specifies the model used to obtain the result of + the asynchronous operation. + + @tparam Signature The call signature for the completion handler type invoked + on completion of the asynchronous operation. + + @note See + Working Draft, C++ Extensions for Networking + + @see @ref async_return_type, @ref handler_type +*/ +template +struct async_completion +{ + /** The type of the final handler called by the asynchronous initiation function. + + Objects of this type will be callable with the specified signature. + */ + using completion_handler_type = typename async_result< + typename std::decay::type, + Signature>::completion_handler_type; + + /** Constructor + + The constructor creates the concrete completion handler and + makes the link between the handler and the asynchronous + result. + + @param token The completion token. If this is a regular completion + handler, copies may be made as needed. If the handler is movable, + it may also be moved. + */ + explicit + async_completion(CompletionToken& token) + : completion_handler(static_cast::value, + completion_handler_type&, CompletionToken&&>::type>(token)) + , result(completion_handler) + { + // CompletionToken is not invokable with the given signature + static_assert(is_completion_handler< + completion_handler_type, Signature>::value, + "CompletionToken requirements not met: signature mismatch"); + } + + /// The final completion handler, callable with the specified signature. + typename std::conditional::value, + completion_handler_type&, + completion_handler_type + >::type completion_handler; + + /// The return value of the asynchronous initiation function. + async_result::type, Signature> result; +}; + +template +using handler_type = typename beast::async_result< + typename std::decay::type, + Signature>::completion_handler_type; + +template +using async_return_type = typename beast::async_result< + typename std::decay::type, + Signature>::return_type; + +} // beast + +#endif diff --git a/src/beast/include/beast/core/bind_handler.hpp b/src/beast/include/beast/core/bind_handler.hpp index 1b2d11daf8..a4a30280a6 100644 --- a/src/beast/include/beast/core/bind_handler.hpp +++ b/src/beast/include/beast/core/bind_handler.hpp @@ -9,7 +9,7 @@ #define BEAST_BIND_HANDLER_HPP #include -#include +#include #include #include #include @@ -24,24 +24,23 @@ namespace beast { the returned handler, which provides the same `io_service` execution guarantees as the original handler. - Unlike `io_service::wrap`, the returned handler can be used in - a subsequent call to `io_service::post` instead of - `io_service::dispatch`, to ensure that the handler will not be - invoked immediately by the calling function. + Unlike `boost::asio::io_service::wrap`, the returned handler can + be used in a subsequent call to `boost::asio::io_service::post` + instead of `boost::asio::io_service::dispatch`, to ensure that + the handler will not be invoked immediately by the calling + function. Example: @code - template void - do_cancel(AsyncReadStream& stream, ReadHandler&& handler) + signal_aborted(AsyncReadStream& stream, ReadHandler&& handler) { stream.get_io_service().post( bind_handler(std::forward(handler), boost::asio::error::operation_aborted, 0)); } - @endcode @param handler The handler to wrap. @@ -50,7 +49,7 @@ namespace beast { arguments are forwarded into the returned object. */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN implementation_defined #else detail::bound_handler< @@ -58,7 +57,7 @@ detail::bound_handler< #endif bind_handler(Handler&& handler, Args&&... args) { - static_assert(is_CompletionHandler< + static_assert(is_completion_handler< Handler, void(Args...)>::value, "Handler requirements not met"); return detail::bound_handler -#include -#include -#include -#include -#include -#include +#include #include -#include namespace beast { +/** A buffer sequence representing a concatenation of buffer sequences. + + @see @ref buffer_cat +*/ +template +class buffer_cat_view +{ +#if 0 + static_assert( + detail::is_all_const_buffer_sequence::value, + "BufferSequence requirements not met"); +#endif + + std::tuple bn_; + +public: + /** The type of buffer returned when dereferencing an iterator. + + If every buffer sequence in the view is a @b MutableBufferSequence, + then `value_type` will be `boost::asio::mutable_buffer`. + Otherwise, `value_type` will be `boost::asio::const_buffer`. + */ + using value_type = + #if BEAST_DOXYGEN + implementation_defined; + #else + typename detail::common_buffers_type::type; + #endif + + /// The type of iterator used by the concatenated sequence + class const_iterator; + + /// Move constructor + buffer_cat_view(buffer_cat_view&&) = default; + + /// Copy constructor + buffer_cat_view(buffer_cat_view const&) = default; + + /// Move assignment + buffer_cat_view& operator=(buffer_cat_view&&) = default; + + // Copy assignment + buffer_cat_view& operator=(buffer_cat_view const&) = default; + + /** Constructor + + @param buffers The list of buffer sequences to concatenate. + Copies of the arguments will be made; however, the ownership + of memory is not transferred. + */ + explicit + buffer_cat_view(Buffers const&... buffers); + + /// Return an iterator to the beginning of the concatenated sequence. + const_iterator + begin() const; + + /// Return an iterator to the end of the concatenated sequence. + const_iterator + end() const; +}; + /** Concatenate 2 or more buffer sequences. This function returns a constant or mutable buffer sequence which, @@ -34,26 +90,30 @@ namespace beast { @return A new buffer sequence that represents the concatenation of the input buffer sequences. This buffer sequence will be a @b MutableBufferSequence if each of the passed buffer sequences is - also a @b MutableBufferSequence, else the returned buffer sequence - will be a @b ConstBufferSequence. + also a @b MutableBufferSequence; otherwise the returned buffer + sequence will be a @b ConstBufferSequence. + + @see @ref buffer_cat_view */ -#if GENERATING_DOCS +#if BEAST_DOXYGEN template -implementation_defined +buffer_cat_view buffer_cat(BufferSequence const&... buffers) #else template -detail::buffer_cat_helper +inline +buffer_cat_view buffer_cat(B1 const& b1, B2 const& b2, Bn const&... bn) #endif { static_assert( - detail::is_all_ConstBufferSequence::value, + detail::is_all_const_buffer_sequence::value, "BufferSequence requirements not met"); - return detail::buffer_cat_helper< - B1, B2, Bn...>{b1, b2, bn...}; + return buffer_cat_view{b1, b2, bn...}; } } // beast +#include + #endif diff --git a/src/beast/include/beast/core/buffer_concepts.hpp b/src/beast/include/beast/core/buffer_concepts.hpp deleted file mode 100644 index 545689eb3f..0000000000 --- a/src/beast/include/beast/core/buffer_concepts.hpp +++ /dev/null @@ -1,62 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_BUFFER_CONCEPTS_HPP -#define BEAST_BUFFER_CONCEPTS_HPP - -#include -#include -#include -#include - -namespace beast { - -/// Determine if `T` meets the requirements of @b `BufferSequence`. -template -#if GENERATING_DOCS -struct is_BufferSequence : std::integral_constant -#else -struct is_BufferSequence : detail::is_BufferSequence::type -#endif -{ -}; - -/// Determine if `T` meets the requirements of @b `ConstBufferSequence`. -template -#if GENERATING_DOCS -struct is_ConstBufferSequence : std::integral_constant -#else -struct is_ConstBufferSequence : - is_BufferSequence -#endif -{ -}; - -/// Determine if `T` meets the requirements of @b `DynamicBuffer`. -template -#if GENERATING_DOCS -struct is_DynamicBuffer : std::integral_constant -#else -struct is_DynamicBuffer : detail::is_DynamicBuffer::type -#endif -{ -}; - -/// Determine if `T` meets the requirements of @b `MutableBufferSequence`. -template -#if GENERATING_DOCS -struct is_MutableBufferSequence : std::integral_constant -#else -struct is_MutableBufferSequence : - is_BufferSequence -#endif -{ -}; - -} // beast - -#endif diff --git a/src/beast/include/beast/core/buffer_prefix.hpp b/src/beast/include/beast/core/buffer_prefix.hpp new file mode 100644 index 0000000000..1450504f78 --- /dev/null +++ b/src/beast/include/beast/core/buffer_prefix.hpp @@ -0,0 +1,208 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_BUFFER_PREFIX_HPP +#define BEAST_BUFFER_PREFIX_HPP + +#include +#include +#include +#include +#include +#include + +namespace beast { + +/** A buffer sequence adapter that shortens the sequence size. + + The class adapts a buffer sequence to efficiently represent + a shorter subset of the original list of buffers starting + with the first byte of the original sequence. + + @tparam BufferSequence The buffer sequence to adapt. +*/ +template +class buffer_prefix_view +{ + using buffers_type = typename + std::decay::type; + + using iter_type = typename buffers_type::const_iterator; + + BufferSequence bs_; + iter_type back_; + iter_type end_; + std::size_t size_; + + template + buffer_prefix_view(Deduced&& other, + std::size_t nback, std::size_t nend) + : bs_(std::forward(other).bs_) + , back_(std::next(bs_.begin(), nback)) + , end_(std::next(bs_.begin(), nend)) + , size_(other.size_) + { + } + + void + setup(std::size_t n); + +public: + /// The type for each element in the list of buffers. + using value_type = typename std::conditional< + std::is_convertible::value_type, + boost::asio::mutable_buffer>::value, + boost::asio::mutable_buffer, + boost::asio::const_buffer>::type; + +#if BEAST_DOXYGEN + /// A bidirectional iterator type that may be used to read elements. + using const_iterator = implementation_defined; + +#else + class const_iterator; + +#endif + + /// Move constructor. + buffer_prefix_view(buffer_prefix_view&&); + + /// Copy constructor. + buffer_prefix_view(buffer_prefix_view const&); + + /// Move assignment. + buffer_prefix_view& operator=(buffer_prefix_view&&); + + /// Copy assignment. + buffer_prefix_view& operator=(buffer_prefix_view const&); + + /** Construct a buffer sequence prefix. + + @param n The maximum number of bytes in the prefix. + If this is larger than the size of passed, buffers, + the resulting sequence will represent the entire + input sequence. + + @param buffers The buffer sequence to adapt. A copy of + the sequence will be made, but ownership of the underlying + memory is not transferred. + */ + buffer_prefix_view(std::size_t n, BufferSequence const& buffers); + + /** Construct a buffer sequence prefix in-place. + + @param n The maximum number of bytes in the prefix. + If this is larger than the size of passed, buffers, + the resulting sequence will represent the entire + input sequence. + + @param args Arguments forwarded to the contained buffers constructor. + */ + template + buffer_prefix_view(std::size_t n, + boost::in_place_init_t, Args&&... args); + + /// Get a bidirectional iterator to the first element. + const_iterator + begin() const; + + /// Get a bidirectional iterator to one past the last element. + const_iterator + end() const; +}; + +/** Returns a prefix of a constant buffer. + + The returned buffer points to the same memory as the + passed buffer, but with a size that is equal to or less + than the size of the original buffer. + + @param n The size of the returned buffer. + + @param buffer The buffer to shorten. The underlying + memory is not modified. + + @return A new buffer that points to the first `n` bytes + of the original buffer. +*/ +inline +boost::asio::const_buffer +buffer_prefix(std::size_t n, + boost::asio::const_buffer buffer) +{ + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + return { buffer_cast(buffer), + (std::min)(n, buffer_size(buffer)) }; +} + +/** Returns a prefix of a mutable buffer. + + The returned buffer points to the same memory as the + passed buffer, but with a size that is equal to or less + than the size of the original buffer. + + @param n The size of the returned buffer. + + @param buffer The buffer to shorten. The underlying + memory is not modified. + + @return A new buffer that points to the first `n` bytes + of the original buffer. +*/ +inline +boost::asio::mutable_buffer +buffer_prefix(std::size_t n, + boost::asio::mutable_buffer buffer) +{ + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + return { buffer_cast(buffer), + (std::min)(n, buffer_size(buffer)) }; +} + +/** Returns a prefix of a buffer sequence. + + This function returns a new buffer sequence which when iterated, + presents a shorter subset of the original list of buffers starting + with the first byte of the original sequence. + + @param n The maximum number of bytes in the wrapped + sequence. If this is larger than the size of passed, + buffers, the resulting sequence will represent the + entire input sequence. + + @param buffers An instance of @b ConstBufferSequence or + @b MutableBufferSequence to adapt. A copy of the sequence + will be made, but ownership of the underlying memory is + not transferred. +*/ +template +#if BEAST_DOXYGEN +buffer_prefix_view +#else +inline +typename std::enable_if< + ! std::is_same::value && + ! std::is_same::value, + buffer_prefix_view>::type +#endif +buffer_prefix(std::size_t n, BufferSequence const& buffers) +{ + static_assert( + is_const_buffer_sequence::value || + is_mutable_buffer_sequence::value, + "BufferSequence requirements not met"); + return buffer_prefix_view(n, buffers); +} + +} // beast + +#include + +#endif diff --git a/src/beast/include/beast/core/dynabuf_readstream.hpp b/src/beast/include/beast/core/buffered_read_stream.hpp similarity index 87% rename from src/beast/include/beast/core/dynabuf_readstream.hpp rename to src/beast/include/beast/core/buffered_read_stream.hpp index e914d434d3..a365babe01 100644 --- a/src/beast/include/beast/core/dynabuf_readstream.hpp +++ b/src/beast/include/beast/core/buffered_read_stream.hpp @@ -5,16 +5,14 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef BEAST_DYNABUF_READSTREAM_HPP -#define BEAST_DYNABUF_READSTREAM_HPP +#ifndef BEAST_BUFFERED_READ_STREAM_HPP +#define BEAST_BUFFERED_READ_STREAM_HPP #include -#include -#include +#include #include -#include -#include -#include +#include +#include #include #include #include @@ -22,11 +20,11 @@ namespace beast { -/** A @b `Stream` with attached @b `DynamicBuffer` to buffer reads. +/** A @b Stream with attached @b DynamicBuffer to buffer reads. - This wraps a @b `Stream` implementation so that calls to write are + This wraps a @b Stream implementation so that calls to write are passed through to the underlying stream, while calls to read will - first consume the input sequence stored in a @b `DynamicBuffer` which + first consume the input sequence stored in a @b DynamicBuffer which is part of the object. The use-case for this class is different than that of the @@ -52,7 +50,7 @@ namespace beast { // template void process_http_message( - dynabuf_readstream& stream) + buffered_read_stream& stream) { // Read up to and including the end of the HTTP // header, leaving the sequence in the stream's @@ -64,11 +62,11 @@ namespace beast { boost::asio::read_until( stream.next_layer(), stream.buffer(), "\r\n\r\n"); - // Use prepare_buffers() to limit the input + // Use buffer_prefix() to limit the input // sequence to only the data up to and including // the trailing "\r\n\r\n". // - auto header_buffers = prepare_buffers( + auto header_buffers = buffer_prefix( bytes_transferred, stream.buffer().data()); ... @@ -88,9 +86,9 @@ namespace beast { @tparam DynamicBuffer The type of stream buffer to use. */ template -class dynabuf_readstream +class buffered_read_stream { - static_assert(is_DynamicBuffer::value, + static_assert(is_dynamic_buffer::value, "DynamicBuffer requirements not met"); template @@ -102,7 +100,7 @@ class dynabuf_readstream public: /// The type of the internal buffer - using dynabuf_type = DynamicBuffer; + using buffer_type = DynamicBuffer; /// The type of the next layer. using next_layer_type = @@ -110,26 +108,21 @@ public: /// The type of the lowest layer. using lowest_layer_type = -#if GENERATING_DOCS - implementation_defined; -#else - typename detail::get_lowest_layer< - next_layer_type>::type; -#endif + typename get_lowest_layer::type; /** Move constructor. @note The behavior of move assignment on or from streams with active or pending operations is undefined. */ - dynabuf_readstream(dynabuf_readstream&&) = default; + buffered_read_stream(buffered_read_stream&&) = default; /** Move assignment. @note The behavior of move assignment on or from streams with active or pending operations is undefined. */ - dynabuf_readstream& operator=(dynabuf_readstream&&) = default; + buffered_read_stream& operator=(buffered_read_stream&&) = default; /** Construct the wrapping stream. @@ -137,7 +130,7 @@ public: */ template explicit - dynabuf_readstream(Args&&... args); + buffered_read_stream(Args&&... args); /// Get a reference to the next layer. next_layer_type& @@ -272,10 +265,10 @@ public: manner equivalent to using `boost::asio::io_service::post`. */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN void_or_deduced #else - typename async_completion::result_type + async_return_type #endif async_read_some(MutableBufferSequence const& buffers, ReadHandler&& handler); @@ -296,7 +289,7 @@ public: std::size_t write_some(ConstBufferSequence const& buffers) { - static_assert(is_SyncWriteStream::value, + static_assert(is_sync_write_stream::value, "SyncWriteStream requirements not met"); return next_layer_.write_some(buffers); } @@ -318,7 +311,7 @@ public: write_some(ConstBufferSequence const& buffers, error_code& ec) { - static_assert(is_SyncWriteStream::value, + static_assert(is_sync_write_stream::value, "SyncWriteStream requirements not met"); return next_layer_.write_some(buffers, ec); } @@ -347,10 +340,10 @@ public: manner equivalent to using `boost::asio::io_service::post`. */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN void_or_deduced #else - typename async_completion::result_type + async_return_type #endif async_write_some(ConstBufferSequence const& buffers, WriteHandler&& handler); @@ -358,6 +351,6 @@ public: } // beast -#include +#include #endif diff --git a/src/beast/include/beast/core/buffers_adapter.hpp b/src/beast/include/beast/core/buffers_adapter.hpp index 36220d0564..6cff9923ca 100644 --- a/src/beast/include/beast/core/buffers_adapter.hpp +++ b/src/beast/include/beast/core/buffers_adapter.hpp @@ -9,16 +9,16 @@ #define BEAST_BUFFERS_ADAPTER_HPP #include -#include +#include #include #include namespace beast { -/** Adapts a @b `MutableBufferSequence` into a @b `DynamicBuffer`. +/** Adapts a @b MutableBufferSequence into a @b DynamicBuffer. - This class wraps a @b `MutableBufferSequence` to meet the requirements - of @b `DynamicBuffer`. Upon construction the input and output sequences are + This class wraps a @b MutableBufferSequence to meet the requirements + of @b DynamicBuffer. Upon construction the input and output sequences are empty. A copy of the mutable buffer sequence object is stored; however, ownership of the underlying memory is not transferred. The caller is responsible for making sure that referenced memory remains valid @@ -32,7 +32,7 @@ namespace beast { template class buffers_adapter { - static_assert(is_MutableBufferSequence::value, + static_assert(is_mutable_buffer_sequence::value, "MutableBufferSequence requirements not met"); using iter_type = typename MutableBufferSequence::const_iterator; @@ -64,7 +64,7 @@ class buffers_adapter } public: -#if GENERATING_DOCS +#if BEAST_DOXYGEN /// The type used to represent the input sequence as a list of buffers. using const_buffers_type = implementation_defined; @@ -112,6 +112,13 @@ public: { return in_size_; } + + /// Returns the maximum sum of the sizes of the input sequence and output sequence the buffer can hold without requiring reallocation. + std::size_t + capacity() const + { + return max_size_; + } /** Get a list of buffers that represents the output sequence, with the given size. diff --git a/src/beast/include/beast/core/consuming_buffers.hpp b/src/beast/include/beast/core/consuming_buffers.hpp index 4d8d42a641..6baffaa470 100644 --- a/src/beast/include/beast/core/consuming_buffers.hpp +++ b/src/beast/include/beast/core/consuming_buffers.hpp @@ -9,8 +9,9 @@ #define BEAST_CONSUMING_BUFFERS_HPP #include -#include +#include #include +#include #include #include #include @@ -40,7 +41,6 @@ class consuming_buffers BufferSequence bs_; iter_type begin_; - iter_type end_; std::size_t skip_ = 0; template @@ -59,7 +59,7 @@ public: `boost::asio::mutable_buffer`, else this type will be `boost::asio::const_buffer`. */ -#if GENERATING_DOCS +#if BEAST_DOXYGEN using value_type = ...; #else using value_type = typename std::conditional< @@ -70,7 +70,7 @@ public: boost::asio::const_buffer>::type; #endif -#if GENERATING_DOCS +#if BEAST_DOXYGEN /// A bidirectional iterator type that may be used to read elements. using const_iterator = implementation_defined; @@ -79,18 +79,15 @@ public: #endif - /// Move constructor. + /// Constructor + consuming_buffers(); + + /// Move constructor consuming_buffers(consuming_buffers&&); - /// Copy constructor. + /// Copy constructor consuming_buffers(consuming_buffers const&); - /// Move assignment. - consuming_buffers& operator=(consuming_buffers&&); - - /// Copy assignment. - consuming_buffers& operator=(consuming_buffers const&); - /** Construct to represent a buffer sequence. A copy of the buffer sequence is made. Ownership of the @@ -99,6 +96,19 @@ public: explicit consuming_buffers(BufferSequence const& buffers); + /** Construct a buffer sequence in-place. + + @param args Arguments forwarded to the contained buffers constructor. + */ + template + consuming_buffers(boost::in_place_init_t, Args&&... args); + + /// Move assignment + consuming_buffers& operator=(consuming_buffers&&); + + /// Copy assignmen + consuming_buffers& operator=(consuming_buffers const&); + /// Get a bidirectional iterator to the first element. const_iterator begin() const; diff --git a/src/beast/include/beast/core/detail/base64.hpp b/src/beast/include/beast/core/detail/base64.hpp index 407b79db37..31b426a61e 100644 --- a/src/beast/include/beast/core/detail/base64.hpp +++ b/src/beast/include/beast/core/detail/base64.hpp @@ -5,15 +5,6 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef BEAST_DETAIL_BASE64_HPP -#define BEAST_DETAIL_BASE64_HPP - -#include -#include - -namespace beast { -namespace detail { - /* Portions from http://www.adp-gmbh.ch/cpp/common/base64.html Copyright notice: @@ -44,77 +35,195 @@ namespace detail { */ -template -std::string const& -base64_alphabet() +#ifndef BEAST_DETAIL_BASE64_HPP +#define BEAST_DETAIL_BASE64_HPP + +#include +#include +#include + +namespace beast { +namespace detail { + +namespace base64 { + +inline +char const* +get_alphabet() { - static std::string const alphabet = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; - return alphabet; + static char constexpr tab[] = { + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + }; + return &tab[0]; } inline -bool -is_base64(unsigned char c) +signed char const* +get_inverse() { - return (std::isalnum(c) || (c == '+') || (c == '/')); + static signed char constexpr tab[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0-15 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 16-31 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, // 32-47 + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, // 48-63 + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 64-79 + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, // 80-95 + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 96-111 + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, // 112-127 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 128-143 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 144-159 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 160-175 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 176-191 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 192-207 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 208-223 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 224-239 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 // 240-255 + }; + return &tab[0]; } -template -std::string -base64_encode (std::uint8_t const* data, - std::size_t in_len) + +/// Returns max chars needed to encode a base64 string +inline +std::size_t constexpr +encoded_size(std::size_t n) { + return 4 * ((n + 2) / 3); +} + +/// Returns max bytes needed to decode a base64 string +inline +std::size_t constexpr +decoded_size(std::size_t n) +{ + return n / 4 * 3; // requires n&3==0, smaller + //return 3 * n / 4; +} + +/** Encode a series of octets as a padded, base64 string. + + The resulting string will not be null terminated. + + @par Requires + + The memory pointed to by `out` points to valid memory + of at least `encoded_size(len)` bytes. + + @return The number of characters written to `out`. This + will exclude any null termination. +*/ +template +std::size_t +encode(void* dest, void const* src, std::size_t len) +{ + char* out = static_cast(dest); + char const* in = static_cast(src); + auto const tab = base64::get_alphabet(); + + for(auto n = len / 3; n--;) + { + *out++ = tab[ (in[0] & 0xfc) >> 2]; + *out++ = tab[((in[0] & 0x03) << 4) + ((in[1] & 0xf0) >> 4)]; + *out++ = tab[((in[2] & 0xc0) >> 6) + ((in[1] & 0x0f) << 2)]; + *out++ = tab[ in[2] & 0x3f]; + in += 3; + } + + switch(len % 3) + { + case 2: + *out++ = tab[ (in[0] & 0xfc) >> 2]; + *out++ = tab[((in[0] & 0x03) << 4) + ((in[1] & 0xf0) >> 4)]; + *out++ = tab[ (in[1] & 0x0f) << 2]; + *out++ = '='; + break; + + case 1: + *out++ = tab[ (in[0] & 0xfc) >> 2]; + *out++ = tab[((in[0] & 0x03) << 4)]; + *out++ = '='; + *out++ = '='; + break; + + case 0: + break; + } + + return out - static_cast(dest); +} + +/** Decode a padded base64 string into a series of octets. + + @par Requires + + The memory pointed to by `out` points to valid memory + of at least `decoded_size(len)` bytes. + + @return The number of octets written to `out`, and + the number of characters read from the input string, + expressed as a pair. +*/ +template +std::pair +decode(void* dest, char const* src, std::size_t len) +{ + char* out = static_cast(dest); + auto in = reinterpret_cast(src); unsigned char c3[3], c4[4]; int i = 0; int j = 0; - std::string ret; - ret.reserve (3 + in_len * 8 / 6); + auto const inverse = base64::get_inverse(); - char const* alphabet (base64_alphabet().data()); - - while(in_len--) + while(len-- && *in != '=') { - c3[i++] = *(data++); - if(i == 3) + auto const v = inverse[*in]; + if(v == -1) + break; + ++in; + c4[i] = v; + if(++i == 4) { - c4[0] = (c3[0] & 0xfc) >> 2; - c4[1] = ((c3[0] & 0x03) << 4) + ((c3[1] & 0xf0) >> 4); - c4[2] = ((c3[1] & 0x0f) << 2) + ((c3[2] & 0xc0) >> 6); - c4[3] = c3[2] & 0x3f; - for(i = 0; (i < 4); i++) - ret += alphabet[c4[i]]; + c3[0] = (c4[0] << 2) + ((c4[1] & 0x30) >> 4); + c3[1] = ((c4[1] & 0xf) << 4) + ((c4[2] & 0x3c) >> 2); + c3[2] = ((c4[2] & 0x3) << 6) + c4[3]; + + for(i = 0; i < 3; i++) + *out++ = c3[i]; i = 0; } } if(i) { - for(j = i; j < 3; j++) - c3[j] = '\0'; + c3[0] = ( c4[0] << 2) + ((c4[1] & 0x30) >> 4); + c3[1] = ((c4[1] & 0xf) << 4) + ((c4[2] & 0x3c) >> 2); + c3[2] = ((c4[2] & 0x3) << 6) + c4[3]; - c4[0] = (c3[0] & 0xfc) >> 2; - c4[1] = ((c3[0] & 0x03) << 4) + ((c3[1] & 0xf0) >> 4); - c4[2] = ((c3[1] & 0x0f) << 2) + ((c3[2] & 0xc0) >> 6); - c4[3] = c3[2] & 0x3f; - - for(j = 0; (j < i + 1); j++) - ret += alphabet[c4[j]]; - - while((i++ < 3)) - ret += '='; + for(j = 0; j < i - 1; j++) + *out++ = c3[j]; } - return ret; - + return {out - static_cast(dest), + in - reinterpret_cast(src)}; } +} // base64 + template std::string -base64_encode (std::string const& s) +base64_encode (std::uint8_t const* data, + std::size_t len) +{ + std::string dest; + dest.resize(base64::encoded_size(len)); + dest.resize(base64::encode(&dest[0], data, len)); + return dest; +} + +inline +std::string +base64_encode(std::string const& s) { return base64_encode (reinterpret_cast < std::uint8_t const*> (s.data()), s.size()); @@ -124,52 +233,12 @@ template std::string base64_decode(std::string const& data) { - auto in_len = data.size(); - unsigned char c3[3], c4[4]; - int i = 0; - int j = 0; - int in_ = 0; - - std::string ret; - ret.reserve (in_len * 6 / 8); // ??? - - while(in_len-- && (data[in_] != '=') && - is_base64(data[in_])) - { - c4[i++] = data[in_]; in_++; - if(i == 4) { - for(i = 0; i < 4; i++) - c4[i] = static_cast( - base64_alphabet().find(c4[i])); - - c3[0] = (c4[0] << 2) + ((c4[1] & 0x30) >> 4); - c3[1] = ((c4[1] & 0xf) << 4) + ((c4[2] & 0x3c) >> 2); - c3[2] = ((c4[2] & 0x3) << 6) + c4[3]; - - for(i = 0; (i < 3); i++) - ret += c3[i]; - i = 0; - } - } - - if(i) - { - for(j = i; j < 4; j++) - c4[j] = 0; - - for(j = 0; j < 4; j++) - c4[j] = static_cast( - base64_alphabet().find(c4[j])); - - c3[0] = (c4[0] << 2) + ((c4[1] & 0x30) >> 4); - c3[1] = ((c4[1] & 0xf) << 4) + ((c4[2] & 0x3c) >> 2); - c3[2] = ((c4[2] & 0x3) << 6) + c4[3]; - - for(j = 0; (j < i - 1); j++) - ret += c3[j]; - } - - return ret; + std::string dest; + dest.resize(base64::decoded_size(data.size())); + auto const result = base64::decode( + &dest[0], data.data(), data.size()); + dest.resize(result.first); + return dest; } } // detail diff --git a/src/beast/include/beast/core/detail/bind_handler.hpp b/src/beast/include/beast/core/detail/bind_handler.hpp index d3cc3cdcea..ad9f4e48c7 100644 --- a/src/beast/include/beast/core/detail/bind_handler.hpp +++ b/src/beast/include/beast/core/detail/bind_handler.hpp @@ -8,8 +8,10 @@ #ifndef BEAST_BIND_DETAIL_HANDLER_HPP #define BEAST_BIND_DETAIL_HANDLER_HPP -#include #include +#include +#include +#include #include namespace beast { @@ -68,8 +70,9 @@ public: asio_handler_allocate( std::size_t size, bound_handler* h) { - return beast_asio_helpers:: - allocate(size, h->h_); + using boost::asio::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(h->h_)); } friend @@ -77,16 +80,17 @@ public: asio_handler_deallocate( void* p, std::size_t size, bound_handler* h) { - beast_asio_helpers:: - deallocate(p, size, h->h_); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(h->h_)); } friend bool asio_handler_is_continuation(bound_handler* h) { - return beast_asio_helpers:: - is_continuation (h->h_); + using boost::asio::asio_handler_is_continuation; + return asio_handler_is_continuation(std::addressof(h->h_)); } template @@ -94,8 +98,9 @@ public: void asio_handler_invoke(F&& f, bound_handler* h) { - beast_asio_helpers:: - invoke(f, h->h_); + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(h->h_)); } }; diff --git a/src/beast/include/beast/core/detail/buffer_concepts.hpp b/src/beast/include/beast/core/detail/buffer_concepts.hpp deleted file mode 100644 index 6391de4c3a..0000000000 --- a/src/beast/include/beast/core/detail/buffer_concepts.hpp +++ /dev/null @@ -1,180 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_DETAIL_BUFFER_CONCEPTS_HPP -#define BEAST_DETAIL_BUFFER_CONCEPTS_HPP - -#include -#include -#include - -namespace beast { -namespace detail { - -// Types that meet the requirements, -// for use with std::declval only. -template -struct BufferSequence -{ - using value_type = BufferType; - using const_iterator = BufferType const*; - ~BufferSequence(); - BufferSequence(BufferSequence const&) = default; - const_iterator begin() const noexcept; - const_iterator end() const noexcept; -}; -using ConstBufferSequence = - BufferSequence; -using MutableBufferSequence = - BufferSequence; - -template -class is_BufferSequence -{ - template > - static R check1(int); - template - static std::false_type check1(...); - using type1 = decltype(check1(0)); - - template::iterator_category>> - #else - // workaround: - // boost::asio::detail::consuming_buffers::const_iterator - // is not bidirectional - std::forward_iterator_tag, - typename std::iterator_traits< - typename U::const_iterator>::iterator_category>> - #endif - static R check2(int); - template - static std::false_type check2(...); - using type2 = decltype(check2(0)); - - template().begin()), - typename U::const_iterator>::type> - static R check3(int); - template - static std::false_type check3(...); - using type3 = decltype(check3(0)); - - template().end()), - typename U::const_iterator>::type> - static R check4(int); - template - static std::false_type check4(...); - using type4 = decltype(check4(0)); - -public: - using type = std::integral_constant::value && - std::is_destructible::value && - type1::value && type2::value && - type3::value && type4::value>; -}; - -template -struct is_all_ConstBufferSequence - : std::integral_constant::type::value && - is_all_ConstBufferSequence::value> -{ -}; - -template -struct is_all_ConstBufferSequence - : is_BufferSequence::type -{ -}; - -template -class is_DynamicBuffer -{ - // size() - template().size()), std::size_t>> - static R check1(int); - template - static std::false_type check1(...); - using type1 = decltype(check1(0)); - - // max_size() - template().max_size()), std::size_t>> - static R check2(int); - template - static std::false_type check2(...); - using type2 = decltype(check2(0)); - - // capacity() - template().capacity()), std::size_t>> - static R check3(int); - template - static std::false_type check3(...); - using type3 = decltype(check3(0)); - - // data() - template().data()), - boost::asio::const_buffer>::type::value>> - static R check4(int); - template - static std::false_type check4(...); - using type4 = decltype(check4(0)); - - // prepare() - template().prepare(1)), - boost::asio::mutable_buffer>::type::value>> - static R check5(int); - template - static std::false_type check5(...); - using type5 = decltype(check5(0)); - - // commit() - template().commit(1), std::true_type{})> - static R check6(int); - template - static std::false_type check6(...); - using type6 = decltype(check6(0)); - - // consume - template().consume(1), std::true_type{})> - static R check7(int); - template - static std::false_type check7(...); - using type7 = decltype(check7(0)); - -public: - using type = std::integral_constant; -}; - -} // detail -} // beast - -#endif diff --git a/src/beast/include/beast/core/detail/buffers_ref.hpp b/src/beast/include/beast/core/detail/buffers_ref.hpp new file mode 100644 index 0000000000..92fe9f5356 --- /dev/null +++ b/src/beast/include/beast/core/detail/buffers_ref.hpp @@ -0,0 +1,61 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DETAIL_BUFFERS_REF_HPP +#define BEAST_DETAIL_BUFFERS_REF_HPP + +#include + +namespace beast { +namespace detail { + +// A very lightweight reference to a buffer sequence +template +class buffers_ref +{ + BufferSequence const& buffers_; + +public: + using value_type = + typename BufferSequence::value_type; + + using const_iterator = + typename BufferSequence::const_iterator; + + buffers_ref(buffers_ref const&) = default; + + explicit + buffers_ref(BufferSequence const& buffers) + : buffers_(buffers) + { + } + + const_iterator + begin() const + { + return buffers_.begin(); + } + + const_iterator + end() const + { + return buffers_.end(); + } +}; + +// Return a reference to a buffer sequence +template +buffers_ref +make_buffers_ref(BufferSequence const& buffers) +{ + return buffers_ref(buffers); +} + +} // detail +} // beast + +#endif diff --git a/src/beast/include/beast/core/detail/ci_char_traits.hpp b/src/beast/include/beast/core/detail/ci_char_traits.hpp deleted file mode 100644 index 7dfd0c18ce..0000000000 --- a/src/beast/include/beast/core/detail/ci_char_traits.hpp +++ /dev/null @@ -1,106 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_DETAIL_CI_CHAR_TRAITS_HPP -#define BEAST_DETAIL_CI_CHAR_TRAITS_HPP - -#include -#include -#include -#include - -namespace beast { -namespace detail { - -inline -char -tolower(char c) -{ - static std::array constexpr tab = {{ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, - 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, - 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, - 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 91, 92, 93, 94, 95, - 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, - 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, - 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, - 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, - 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, - 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, - 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, - 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, - 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, - 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 - }}; - return static_cast(tab[static_cast(c)]); -} - -template -inline -boost::string_ref -string_helper(const char (&s)[N]) -{ - return boost::string_ref{s, N-1}; -} - -template -inline -T const& -string_helper(T const& t) -{ - return t; -} - -// Case-insensitive less -struct ci_less -{ - static bool const is_transparent = true; - - template - bool - operator()(S1 const& lhs, S2 const& rhs) const noexcept - { - using std::begin; - using std::end; - auto const s1 = string_helper(lhs); - auto const s2 = string_helper(rhs); - return std::lexicographical_compare( - begin(s1), end(s1), begin(s2), end(s2), - [](char lhs, char rhs) - { - return tolower(lhs) < tolower(rhs); - } - ); - } -}; - -// Case-insensitive equal -struct ci_equal_pred -{ - bool - operator()(char c1, char c2) const noexcept - { - return tolower(c1) == tolower(c2); - } -}; - -// Case-insensitive equal -template -bool -ci_equal(S1 const& lhs, S2 const& rhs) -{ - return boost::range::equal( - string_helper(lhs), string_helper(rhs), - ci_equal_pred{}); -} - -} // detail -} // beast - -#endif diff --git a/src/beast/include/beast/core/detail/config.hpp b/src/beast/include/beast/core/detail/config.hpp new file mode 100644 index 0000000000..461ca38bf8 --- /dev/null +++ b/src/beast/include/beast/core/detail/config.hpp @@ -0,0 +1,20 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_CORE_DETAIL_CONFIG_HPP +#define BEAST_CORE_DETAIL_CONFIG_HPP + +#include +#include + +#if BOOST_VERSION >= 106500 || ! defined(BOOST_GCC) || BOOST_GCC < 70000 +# define BEAST_FALLTHROUGH BOOST_FALLTHROUGH +#else +# define BEAST_FALLTHROUGH __attribute__((fallthrough)) +#endif + +#endif diff --git a/src/beast/include/beast/core/detail/cpu_info.hpp b/src/beast/include/beast/core/detail/cpu_info.hpp new file mode 100644 index 0000000000..3a1e24cb38 --- /dev/null +++ b/src/beast/include/beast/core/detail/cpu_info.hpp @@ -0,0 +1,95 @@ +// +// Copyright (c) 2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DETAIL_CPU_INFO_HPP +#define BEAST_DETAIL_CPU_INFO_HPP + +#include + +#ifndef BEAST_NO_INTRINSICS +# if defined(BOOST_MSVC) || ((defined(BOOST_GCC) || defined(BOOST_CLANG)) && defined(__SSE4_2__)) +# define BEAST_NO_INTRINSICS 0 +# else +# define BEAST_NO_INTRINSICS 1 +# endif +#endif + +#if ! BEAST_NO_INTRINSICS + +#ifdef BOOST_MSVC +#include // __cpuid +#else +#include // __get_cpuid +#endif + +namespace beast { +namespace detail { + +/* Portions from Boost, + Copyright Andrey Semashev 2007 - 2015. +*/ +template +void +cpuid( + std::uint32_t id, + std::uint32_t& eax, + std::uint32_t& ebx, + std::uint32_t& ecx, + std::uint32_t& edx) +{ +#ifdef BOOST_MSVC + int regs[4]; + __cpuid(regs, id); + eax = regs[0]; + ebx = regs[1]; + ecx = regs[2]; + edx = regs[3]; +#else + __get_cpuid(id, &eax, &ebx, &ecx, &edx); +#endif +} + +struct cpu_info +{ + bool sse42 = false; + + cpu_info(); +}; + +inline +cpu_info:: +cpu_info() +{ + constexpr std::uint32_t SSE42 = 1 << 20; + + std::uint32_t eax = 0; + std::uint32_t ebx = 0; + std::uint32_t ecx = 0; + std::uint32_t edx = 0; + + cpuid(0, eax, ebx, ecx, edx); + if(eax >= 1) + { + cpuid(1, eax, ebx, ecx, edx); + sse42 = (ecx & SSE42) != 0; + } +} + +template +cpu_info const& +get_cpu_info() +{ + static cpu_info const ci; + return ci; +} + +} // detail +} // beast + +#endif + +#endif diff --git a/src/beast/include/beast/core/detail/get_lowest_layer.hpp b/src/beast/include/beast/core/detail/get_lowest_layer.hpp deleted file mode 100644 index 4b199ce665..0000000000 --- a/src/beast/include/beast/core/detail/get_lowest_layer.hpp +++ /dev/null @@ -1,53 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_DETAIL_GET_LOWEST_LAYER_HPP -#define BEAST_DETAIL_GET_LOWEST_LAYER_HPP - -#include - -namespace beast { -namespace detail { - -template -class has_lowest_layer -{ - template - static std::true_type check(int); - template - static std::false_type check(...); - using type = decltype(check(0)); -public: - static bool constexpr value = type::value; -}; - -template -struct maybe_get_lowest_layer -{ - using type = T; -}; - -template -struct maybe_get_lowest_layer -{ - using type = typename T::lowest_layer_type; -}; - -// If T has a nested type lowest_layer_type, -// returns that, else returns T. -template -struct get_lowest_layer -{ - using type = typename maybe_get_lowest_layer::value>::type; -}; - -} // detail -} // beast - -#endif diff --git a/src/beast/include/beast/core/detail/in_place_init.hpp b/src/beast/include/beast/core/detail/in_place_init.hpp new file mode 100644 index 0000000000..2a0ab615f9 --- /dev/null +++ b/src/beast/include/beast/core/detail/in_place_init.hpp @@ -0,0 +1,41 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DETAIL_IN_PLACE_INIT_HPP +#define BEAST_DETAIL_IN_PLACE_INIT_HPP + +#include +#include + +// Provide boost::in_place_init_t and boost::in_place_init +// for Boost versions earlier than 1.63.0. + +#if BOOST_VERSION < 106300 + +namespace boost { + +namespace optional_ns { + +// a tag for in-place initialization of contained value +struct in_place_init_t +{ + struct init_tag{}; + explicit in_place_init_t(init_tag){} +}; +const in_place_init_t in_place_init ((in_place_init_t::init_tag())); + +} // namespace optional_ns + +using optional_ns::in_place_init_t; +using optional_ns::in_place_init; + +} + +#endif + +#endif + diff --git a/src/beast/include/beast/core/detail/integer_sequence.hpp b/src/beast/include/beast/core/detail/integer_sequence.hpp index 3a2fa418da..6d0cc8a7f3 100644 --- a/src/beast/include/beast/core/detail/integer_sequence.hpp +++ b/src/beast/include/beast/core/detail/integer_sequence.hpp @@ -5,9 +5,10 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef BEAST_DETAIL_INTEGER_SEQUENCE_H_INCLUDED -#define BEAST_DETAIL_INTEGER_SEQUENCE_H_INCLUDED +#ifndef BEAST_DETAIL_INTEGER_SEQUENCE_HPP +#define BEAST_DETAIL_INTEGER_SEQUENCE_HPP +#include #include #include #include @@ -19,8 +20,7 @@ template struct integer_sequence { using value_type = T; - static_assert (std::is_integral::value, - "std::integer_sequence can only be instantiated with an integral type" ); + BOOST_STATIC_ASSERT(std::is_integral::value); static std::size_t constexpr static_size = sizeof...(Ints); @@ -40,9 +40,9 @@ struct sizeof_workaround static std::size_t constexpr size = sizeof... (Args); }; -#ifdef _MSC_VER +#ifdef BOOST_MSVC -// This implementation compiles on MSVC and clang but not gcc +// This implementation compiles on real MSVC and clang but not gcc template struct make_integer_sequence_unchecked; @@ -65,11 +65,8 @@ struct make_integer_sequence_unchecked< template struct make_integer_sequence_checked { - static_assert (std::is_integral::value, - "T must be an integral type"); - - static_assert (N >= 0, - "N must be non-negative"); + BOOST_STATIC_ASSERT(std::is_integral::value); + BOOST_STATIC_ASSERT(N >= 0); using type = typename make_integer_sequence_unchecked< T, N, integer_sequence>::type; @@ -117,11 +114,8 @@ struct integer_sequence_helper; template struct integer_sequence_helper> { - static_assert (std::is_integral::value, - "T must be an integral type"); - - static_assert (N >= 0, - "N must be non-negative"); + BOOST_STATIC_ASSERT(std::is_integral::value); + BOOST_STATIC_ASSERT(N >= 0); using type = integer_sequence (Ints)...>; }; diff --git a/src/beast/include/beast/core/detail/is_call_possible.hpp b/src/beast/include/beast/core/detail/is_call_possible.hpp deleted file mode 100644 index bafc2a33d4..0000000000 --- a/src/beast/include/beast/core/detail/is_call_possible.hpp +++ /dev/null @@ -1,54 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_DETAIL_IS_CALL_POSSIBLE_HPP -#define BEAST_DETAIL_IS_CALL_POSSIBLE_HPP - -#include - -namespace beast { -namespace detail { - -template -auto -is_call_possible_test(C&& c, int, A&& ...a) - -> decltype(std::is_convertible< - decltype(c(a...)), R>::value || - std::is_same::value, - std::true_type()); - -template -std::false_type -is_call_possible_test(C&& c, long, A&& ...a); - -/** Metafunction returns `true` if F callable as R(A...) - - Example: - - @code - is_call_possible - @endcode -*/ -/** @{ */ -template -struct is_call_possible - : std::false_type -{ -}; - -template -struct is_call_possible - : decltype(is_call_possible_test( - std::declval(), 1, std::declval()...)) -{ -}; -/** @} */ - -} // detail -} // beast - -#endif diff --git a/src/beast/include/beast/core/detail/ostream.hpp b/src/beast/include/beast/core/detail/ostream.hpp new file mode 100644 index 0000000000..c02211246d --- /dev/null +++ b/src/beast/include/beast/core/detail/ostream.hpp @@ -0,0 +1,318 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DETAIL_OSTREAM_HPP +#define BEAST_DETAIL_OSTREAM_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace detail { + +template +class buffers_helper +{ + Buffers b_; + +public: + explicit + buffers_helper(Buffers const& b) + : b_(b) + { + } + + template + friend + std::ostream& + operator<<(std::ostream& os, + buffers_helper const& v); +}; + +template +std::ostream& +operator<<(std::ostream& os, + buffers_helper const& v) +{ + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + for(boost::asio::const_buffer b : v.b_) + os.write(buffer_cast(b), + buffer_size(b)); + return os; +} + +//------------------------------------------------------------------------------ + +struct basic_streambuf_movable_helper : + std::basic_streambuf> +{ + basic_streambuf_movable_helper( + basic_streambuf_movable_helper&&) = default; +}; + +using basic_streambuf_movable = + std::is_move_constructible; + +//------------------------------------------------------------------------------ + +template +class ostream_buffer; + +template +class ostream_buffer + + : public std::basic_streambuf +{ + using int_type = typename + std::basic_streambuf::int_type; + + using traits_type = typename + std::basic_streambuf::traits_type; + + static std::size_t constexpr max_size = 512; + + DynamicBuffer& buf_; + +public: + ostream_buffer(ostream_buffer&&) = default; + ostream_buffer(ostream_buffer const&) = delete; + + ~ostream_buffer() noexcept + { + sync(); + } + + explicit + ostream_buffer(DynamicBuffer& buf) + : buf_(buf) + { + prepare(); + } + + int_type + overflow(int_type ch) override + { + if(! Traits::eq_int_type(ch, Traits::eof())) + { + Traits::assign(*this->pptr(), + static_cast(ch)); + flush(1); + prepare(); + return ch; + } + flush(); + return traits_type::eof(); + } + + int + sync() override + { + flush(); + prepare(); + return 0; + } + +private: + void + prepare() + { + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + auto mbs = buf_.prepare( + read_size_or_throw(buf_, max_size)); + auto const mb = *mbs.begin(); + auto const p = buffer_cast(mb); + this->setp(p, + p + buffer_size(mb) / sizeof(CharT) - 1); + } + + void + flush(int extra = 0) + { + buf_.commit( + (this->pptr() - this->pbase() + extra) * + sizeof(CharT)); + } +}; + +// This nonsense is all to work around a glitch in libstdc++ +// where std::basic_streambuf copy constructor is private: +// https://github.com/gcc-mirror/gcc/blob/gcc-4_8-branch/libstdc%2B%2B-v3/include/std/streambuf#L799 + +template +class ostream_buffer + + : public std::basic_streambuf +{ + using int_type = typename + std::basic_streambuf::int_type; + + using traits_type = typename + std::basic_streambuf::traits_type; + + static std::size_t constexpr max_size = 512; + + DynamicBuffer& buf_; + +public: + ostream_buffer(ostream_buffer&&) = delete; + ostream_buffer(ostream_buffer const&) = delete; + + ~ostream_buffer() noexcept + { + sync(); + } + + explicit + ostream_buffer(DynamicBuffer& buf) + : buf_(buf) + { + prepare(); + } + + int_type + overflow(int_type ch) override + { + if(! Traits::eq_int_type(ch, Traits::eof())) + { + Traits::assign(*this->pptr(), + static_cast(ch)); + flush(1); + prepare(); + return ch; + } + flush(); + return traits_type::eof(); + } + + int + sync() override + { + flush(); + prepare(); + return 0; + } + +private: + void + prepare() + { + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + auto mbs = buf_.prepare( + read_size_or_throw(buf_, max_size)); + auto const mb = *mbs.begin(); + auto const p = buffer_cast(mb); + this->setp(p, + p + buffer_size(mb) / sizeof(CharT) - 1); + } + + void + flush(int extra = 0) + { + buf_.commit( + (this->pptr() - this->pbase() + extra) * + sizeof(CharT)); + } +}; + +//------------------------------------------------------------------------------ + +template +class ostream_helper; + +template +class ostream_helper< + DynamicBuffer, CharT, Traits, true> + : public std::basic_ostream +{ + ostream_buffer< + DynamicBuffer, CharT, Traits, true> osb_; + +public: + explicit + ostream_helper(DynamicBuffer& buf); + + ostream_helper(ostream_helper&& other); +}; + +template +ostream_helper:: +ostream_helper(DynamicBuffer& buf) + : std::basic_ostream( + &this->osb_) + , osb_(buf) +{ +} + +template +ostream_helper:: +ostream_helper( + ostream_helper&& other) + : std::basic_ostream(&osb_) + , osb_(std::move(other.osb_)) +{ +} + +// This work-around is for libstdc++ versions that +// don't have a movable std::basic_streambuf + +template +class ostream_helper_base +{ +protected: + std::unique_ptr member; + + ostream_helper_base( + ostream_helper_base&&) = default; + + explicit + ostream_helper_base(T* t) + : member(t) + { + } +}; + +template +class ostream_helper< + DynamicBuffer, CharT, Traits, false> + : private ostream_helper_base> + , public std::basic_ostream +{ +public: + explicit + ostream_helper(DynamicBuffer& buf) + : ostream_helper_base>( + new ostream_buffer(buf)) + , std::basic_ostream(this->member.get()) + { + } + + ostream_helper(ostream_helper&& other) + : ostream_helper_base>( + std::move(other)) + , std::basic_ostream(this->member.get()) + { + } +}; + +} // detail +} // beast + +#endif diff --git a/src/beast/include/beast/core/detail/static_ostream.hpp b/src/beast/include/beast/core/detail/static_ostream.hpp new file mode 100644 index 0000000000..99f4282d91 --- /dev/null +++ b/src/beast/include/beast/core/detail/static_ostream.hpp @@ -0,0 +1,138 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DETAIL_STATIC_OSTREAM_HPP +#define BEAST_DETAIL_STATIC_OSTREAM_HPP + +#include +#include +#include + +namespace beast { +namespace detail { + +// http://www.mr-edd.co.uk/blog/beginners_guide_streambuf + +class static_ostream_buffer + : public std::basic_streambuf +{ + using CharT = char; + using Traits = std::char_traits; + using int_type = typename + std::basic_streambuf::int_type; + using traits_type = typename + std::basic_streambuf::traits_type; + + char* data_; + std::size_t size_; + std::size_t len_ = 0; + std::string s_; + +public: + static_ostream_buffer(static_ostream_buffer&&) = delete; + static_ostream_buffer(static_ostream_buffer const&) = delete; + + static_ostream_buffer(char* data, std::size_t size) + : data_(data) + , size_(size) + { + this->setp(data_, data_ + size - 1); + } + + ~static_ostream_buffer() noexcept + { + } + + string_view + str() const + { + if(! s_.empty()) + return {s_.data(), len_}; + return {data_, len_}; + } + + int_type + overflow(int_type ch) override + { + if(! Traits::eq_int_type(ch, Traits::eof())) + { + Traits::assign(*this->pptr(), + static_cast(ch)); + flush(1); + prepare(); + return ch; + } + flush(); + return traits_type::eof(); + } + + int + sync() override + { + flush(); + prepare(); + return 0; + } + +private: + void + prepare() + { + static auto const growth_factor = 1.5; + + if(len_ < size_ - 1) + { + this->setp( + data_ + len_, data_ + size_ - 2); + return; + } + if(s_.empty()) + { + s_.resize(static_cast( + growth_factor * len_)); + Traits::copy(&s_[0], data_, len_); + } + else + { + s_.resize(static_cast( + growth_factor * len_)); + } + this->setp(&s_[len_], &s_[len_] + + s_.size() - len_ - 1); + } + + void + flush(int extra = 0) + { + len_ += static_cast( + this->pptr() - this->pbase() + extra); + } +}; + +class static_ostream : public std::basic_ostream +{ + static_ostream_buffer osb_; + +public: + static_ostream(char* data, std::size_t size) + : std::basic_ostream(&this->osb_) + , osb_(data, size) + { + imbue(std::locale::classic()); + } + + string_view + str() const + { + return osb_.str(); + } +}; + +} // detail +} // beast + +#endif diff --git a/src/beast/include/beast/core/detail/static_string.hpp b/src/beast/include/beast/core/detail/static_string.hpp new file mode 100644 index 0000000000..7ded7fff0f --- /dev/null +++ b/src/beast/include/beast/core/detail/static_string.hpp @@ -0,0 +1,131 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DETAIL_STATIC_STRING_HPP +#define BEAST_DETAIL_STATIC_STRING_HPP + +#include +#include +#include +#include + +namespace beast { +namespace detail { + +// Because k-ballo said so +template +using is_input_iterator = + std::integral_constant::value>; + +template +int +lexicographical_compare( + CharT const* s1, std::size_t n1, + CharT const* s2, std::size_t n2) +{ + if(n1 < n2) + return Traits::compare( + s1, s2, n1) <= 0 ? -1 : 1; + if(n1 > n2) + return Traits::compare( + s1, s2, n2) >= 0 ? 1 : -1; + return Traits::compare(s1, s2, n1); +} + +template +inline +int +lexicographical_compare( + basic_string_view s1, + CharT const* s2, std::size_t n2) +{ + return lexicographical_compare( + s1.data(), s1.size(), s2, n2); +} + +template +inline +int +lexicographical_compare( + basic_string_view s1, + basic_string_view s2) +{ + return lexicographical_compare( + s1.data(), s1.size(), s2.data(), s2.size()); +} + +// Maximum number of characters in the decimal +// representation of a binary number. This includes +// the potential minus sign. +// +inline +std::size_t constexpr +max_digits(std::size_t bytes) +{ + return static_cast( + bytes * 2.41) + 1 + 1; +} + +template +CharT* +raw_to_string( + CharT* buf, Integer x, std::true_type) +{ + if(x == 0) + { + Traits::assign(*--buf, '0'); + return buf; + } + if(x < 0) + { + x = -x; + for(;x > 0; x /= 10) + Traits::assign(*--buf , + "0123456789"[x % 10]); + Traits::assign(*--buf, '-'); + return buf; + } + for(;x > 0; x /= 10) + Traits::assign(*--buf , + "0123456789"[x % 10]); + return buf; +} + +template +CharT* +raw_to_string( + CharT* buf, Integer x, std::false_type) +{ + if(x == 0) + { + *--buf = '0'; + return buf; + } + for(;x > 0; x /= 10) + Traits::assign(*--buf , + "0123456789"[x % 10]); + return buf; +} + +template< + class CharT, + class Integer, + class Traits = std::char_traits> +CharT* +raw_to_string(CharT* last, std::size_t size, Integer i) +{ + boost::ignore_unused(size); + BOOST_ASSERT(size >= max_digits(sizeof(Integer))); + return raw_to_string( + last, i, std::is_signed{}); +} + +} // detail +} // beast + +#endif diff --git a/src/beast/include/beast/core/detail/stream_concepts.hpp b/src/beast/include/beast/core/detail/stream_concepts.hpp deleted file mode 100644 index c51991eb06..0000000000 --- a/src/beast/include/beast/core/detail/stream_concepts.hpp +++ /dev/null @@ -1,134 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_DETAIL_STREAM_CONCEPTS_HPP -#define BEAST_DETAIL_STREAM_CONCEPTS_HPP - -#include -#include -#include -#include -#include - -namespace beast { -namespace detail { - -// Types that meet the requirements, -// for use with std::declval only. -struct StreamHandler -{ - StreamHandler(StreamHandler const&) = default; - void operator()(error_code ec, std::size_t); -}; -using ReadHandler = StreamHandler; -using WriteHandler = StreamHandler; - -template -class has_get_io_service -{ - template().get_io_service()), - boost::asio::io_service&>> - static R check(int); - template - static std::false_type check(...); -public: - using type = decltype(check(0)); -}; - -template -class is_AsyncReadStream -{ - template().async_read_some( - std::declval(), - std::declval()), - std::true_type{})> - static R check(int); - template - static std::false_type check(...); - using type1 = decltype(check(0)); -public: - using type = std::integral_constant::type::value>; -}; - -template -class is_AsyncWriteStream -{ - template().async_write_some( - std::declval(), - std::declval()), - std::true_type{})> - static R check(int); - template - static std::false_type check(...); - using type1 = decltype(check(0)); -public: - using type = std::integral_constant::type::value>; -}; - -template -class is_SyncReadStream -{ - template().read_some( - std::declval())), - std::size_t>> - static R check1(int); - template - static std::false_type check1(...); - using type1 = decltype(check1(0)); - - template().read_some( - std::declval(), - std::declval())), std::size_t>> - static R check2(int); - template - static std::false_type check2(...); - using type2 = decltype(check2(0)); - -public: - using type = std::integral_constant; -}; - -template -class is_SyncWriteStream -{ - template().write_some( - std::declval())), - std::size_t>> - static R check1(int); - template - static std::false_type check1(...); - using type1 = decltype(check1(0)); - - template().write_some( - std::declval(), - std::declval())), std::size_t>> - static R check2(int); - template - static std::false_type check2(...); - using type2 = decltype(check2(0)); - -public: - using type = std::integral_constant; -}; - -} // detail -} // beast - -#endif diff --git a/src/beast/include/beast/core/detail/sync_ostream.hpp b/src/beast/include/beast/core/detail/sync_ostream.hpp deleted file mode 100644 index 5a4e0b47a2..0000000000 --- a/src/beast/include/beast/core/detail/sync_ostream.hpp +++ /dev/null @@ -1,93 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_DETAIL_SYNC_OSTREAM_HPP -#define BEAST_DETAIL_SYNC_OSTREAM_HPP - -#include -#include -#include -#include - -namespace beast { -namespace detail { - -/** A SyncWriteStream which outputs to a `std::ostream` - - Objects of this type meet the requirements of @b SyncWriteStream. -*/ -class sync_ostream -{ - std::ostream& os_; - -public: - /** Construct the stream. - - @param os The associated `std::ostream`. All buffers - written will be passed to the associated output stream. - */ - sync_ostream(std::ostream& os) - : os_(os) - { - } - - template - std::size_t - write_some(ConstBufferSequence const& buffers); - - template - std::size_t - write_some(ConstBufferSequence const& buffers, - error_code& ec); -}; - -template -std::size_t -sync_ostream:: -write_some(ConstBufferSequence const& buffers) -{ - static_assert( - is_ConstBufferSequence::value, - "ConstBufferSequence requirements not met"); - error_code ec; - auto const n = write_some(buffers, ec); - if(ec) - throw system_error{ec}; - return n; -} - -template -std::size_t -sync_ostream:: -write_some(ConstBufferSequence const& buffers, - error_code& ec) -{ - static_assert( - is_ConstBufferSequence::value, - "ConstBufferSequence requirements not met"); - std::size_t n = 0; - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - for(auto const& buffer : buffers) - { - os_.write(buffer_cast(buffer), - buffer_size(buffer)); - if(os_.fail()) - { - ec = errc::make_error_code( - errc::no_stream_resources); - break; - } - n += buffer_size(buffer); - } - return n; -} - -} // detail -} // beast - -#endif diff --git a/src/beast/include/beast/core/detail/type_traits.hpp b/src/beast/include/beast/core/detail/type_traits.hpp index 4b6acbd445..e375b976d8 100644 --- a/src/beast/include/beast/core/detail/type_traits.hpp +++ b/src/beast/include/beast/core/detail/type_traits.hpp @@ -8,14 +8,46 @@ #ifndef BEAST_DETAIL_TYPE_TRAITS_HPP #define BEAST_DETAIL_TYPE_TRAITS_HPP +#include +#include +#include +#include #include #include -#include #include +// A few workarounds to keep things working + +namespace boost { +namespace asio { + +// for has_get_io_service +class io_service; + +// for is_dynamic_buffer +template +class basic_streambuf; + +namespace detail { + +// for is_buffer_sequence +template +class consuming_buffers; + +} // detail + +} // asio +} // boost + +//------------------------------------------------------------------------------ + namespace beast { namespace detail { +// +// utilities +// + template struct make_void { @@ -25,18 +57,10 @@ struct make_void template using void_t = typename make_void::type; -template +template inline void -ignore_unused(Ts const& ...) -{ -} - -template -inline -void -ignore_unused() -{} +accept_rv(T){} template std::size_t constexpr @@ -80,17 +104,159 @@ struct repeat_tuple<0, T> using type = std::tuple<>; }; -template -Exception -make_exception(char const* reason, char const* file, int line) +template +auto +is_invocable_test(C&& c, int, A&& ...a) + -> decltype(std::is_convertible< + decltype(c(a...)), R>::value || + std::is_same::value, + std::true_type()); + +template +std::false_type +is_invocable_test(C&& c, long, A&& ...a); + +/** Metafunction returns `true` if F callable as R(A...) + + Example: + + @code + is_invocable + @endcode +*/ +/** @{ */ +template +struct is_invocable : std::false_type { - char const* n = file; - for(auto p = file; *p; ++p) - if(*p == '\\' || *p == '/') - n = p + 1; - return Exception{std::string(reason) + " (" + - n + ":" + std::to_string(line) + ")"}; -} +}; + +template +struct is_invocable + : decltype(is_invocable_test( + std::declval(), 1, std::declval()...)) +{ +}; +/** @} */ + +// for span +template +struct is_contiguous_container: std::false_type {}; + +template +struct is_contiguous_container() = std::declval().size(), + std::declval() = std::declval().data(), + (void)0), + typename std::enable_if< + std::is_same< + typename std::remove_cv::type, + typename std::remove_cv< + typename std::remove_pointer< + decltype(std::declval().data()) + >::type + >::type + >::value + >::type>>: std::true_type +{}; + +//------------------------------------------------------------------------------ + +// +// buffer concepts +// + +// Types that meet the requirements, +// for use with std::declval only. +template +struct BufferSequence +{ + using value_type = BufferType; + using const_iterator = BufferType const*; + ~BufferSequence(); + BufferSequence(BufferSequence const&) = default; + const_iterator begin() const noexcept; + const_iterator end() const noexcept; +}; +using ConstBufferSequence = + BufferSequence; +using MutableBufferSequence = + BufferSequence; + +template +struct is_buffer_sequence : std::false_type {}; + +template +struct is_buffer_sequence(), + std::declval() = + std::declval().begin(), + std::declval() = + std::declval().end(), + (void)0)>> : std::integral_constant::value && +#if 0 + std::is_base_of::iterator_category>::value +#else + // workaround: + // boost::asio::detail::consuming_buffers::const_iterator + // is not bidirectional + std::is_base_of::iterator_category>::value +#endif + > +{ +}; + +#if 0 +// workaround: +// boost::asio::detail::consuming_buffers::const_iterator +// is not bidirectional +template +struct is_buffer_sequence< + boost::asio::detail::consuming_buffers> + : std::true_type +{ +}; +#endif + +template +struct is_all_const_buffer_sequence + : std::integral_constant::value && + is_all_const_buffer_sequence::value> +{ +}; + +template +struct is_all_const_buffer_sequence + : is_buffer_sequence +{ +}; + +template +struct common_buffers_type +{ + using type = typename std::conditional< + std::is_convertible, + typename repeat_tuple::type>::value, + boost::asio::mutable_buffer, + boost::asio::const_buffer>::type; +}; + +// Types that meet the requirements, +// for use with std::declval only. +struct StreamHandler +{ + StreamHandler(StreamHandler const&) = default; + void operator()(error_code ec, std::size_t); +}; +using ReadHandler = StreamHandler; +using WriteHandler = StreamHandler; } // detail } // beast diff --git a/src/beast/include/beast/core/detail/write_dynabuf.hpp b/src/beast/include/beast/core/detail/write_dynabuf.hpp deleted file mode 100644 index dcef73afc0..0000000000 --- a/src/beast/include/beast/core/detail/write_dynabuf.hpp +++ /dev/null @@ -1,140 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_DETAIL_WRITE_DYNABUF_HPP -#define BEAST_DETAIL_WRITE_DYNABUF_HPP - -#include -#include -#include -#include - -namespace beast { -namespace detail { - -// detects string literals. -template -struct is_string_literal : std::integral_constant::type>::value && - std::is_same::type>::value> -{ -}; - -// `true` if a call to boost::asio::buffer(T const&) is possible -// note: we exclude string literals because boost::asio::buffer() -// will include the null terminator, which we don't want. -template -class is_BufferConvertible -{ - template()), - std::true_type{})> - static R check(int); - template - static std::false_type check(...); - using type = decltype(check(0)); -public: - static bool const value = type::value && - ! is_string_literal::value; -}; - -template -void -write_dynabuf(DynamicBuffer& dynabuf, - boost::asio::const_buffer const& buffer) -{ - using boost::asio::buffer_copy; - using boost::asio::buffer_size; - dynabuf.commit(buffer_copy( - dynabuf.prepare(buffer_size(buffer)), - buffer)); -} - -template -void -write_dynabuf(DynamicBuffer& dynabuf, - boost::asio::mutable_buffer const& buffer) -{ - using boost::asio::buffer_copy; - using boost::asio::buffer_size; - dynabuf.commit(buffer_copy( - dynabuf.prepare(buffer_size(buffer)), - buffer)); -} - -template -typename std::enable_if< - is_BufferConvertible::value && - ! std::is_convertible::value && - ! std::is_convertible::value ->::type -write_dynabuf(DynamicBuffer& dynabuf, T const& t) -{ - using boost::asio::buffer_copy; - using boost::asio::buffer_size; - auto const buffers = boost::asio::buffer(t); - dynabuf.commit(buffer_copy( - dynabuf.prepare(buffer_size(buffers)), - buffers)); -} - -template -typename std::enable_if< - is_ConstBufferSequence::value && - ! is_BufferConvertible::value && - ! std::is_convertible::value && - ! std::is_convertible::value ->::type -write_dynabuf(DynamicBuffer& dynabuf, Buffers const& buffers) -{ - using boost::asio::buffer_copy; - using boost::asio::buffer_size; - dynabuf.commit(buffer_copy( - dynabuf.prepare(buffer_size(buffers)), - buffers)); -} - -template -void -write_dynabuf(DynamicBuffer& dynabuf, const char (&s)[N]) -{ - using boost::asio::buffer_copy; - dynabuf.commit(buffer_copy( - dynabuf.prepare(N - 1), - boost::asio::buffer(s, N - 1))); -} - -template -typename std::enable_if< - ! is_string_literal::value && - ! is_ConstBufferSequence::value && - ! is_BufferConvertible::value && - ! std::is_convertible::value && - ! std::is_convertible::value ->::type -write_dynabuf(DynamicBuffer& dynabuf, T const& t) -{ - using boost::asio::buffer; - using boost::asio::buffer_copy; - auto const s = boost::lexical_cast(t); - dynabuf.commit(buffer_copy( - dynabuf.prepare(s.size()), buffer(s))); -} - -template -void -write_dynabuf(DynamicBuffer& dynabuf, - T0 const& t0, T1 const& t1, TN const&... tn) -{ - write_dynabuf(dynabuf, t0); - write_dynabuf(dynabuf, t1, tn...); -} - -} // detail -} // beast - -#endif diff --git a/src/beast/include/beast/core/drain_buffer.hpp b/src/beast/include/beast/core/drain_buffer.hpp new file mode 100644 index 0000000000..15f89a694f --- /dev/null +++ b/src/beast/include/beast/core/drain_buffer.hpp @@ -0,0 +1,122 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DRAIN_BUFFER_HPP +#define BEAST_DRAIN_BUFFER_HPP + +#include +#include +#include + +namespace beast { + +/** A @b DynamicBuffer which does not retain its input sequence. + + This object implements a dynamic buffer with a fixed size + output area, not dynamically allocated, and whose input + sequence is always length zero. Bytes committed from the + output area to the input area are always discarded. This + is useful for calling interfaces that require a dynamic + buffer for storage, but where the caller does not want + to retain the data. +*/ +class drain_buffer +{ + char buf_[512]; + std::size_t n_ = 0; + +public: + /// The type used to represent the input sequence as a list of buffers. + using const_buffers_type = boost::asio::null_buffers; + + /// The type used to represent the output sequence as a list of buffers. + using mutable_buffers_type = boost::asio::mutable_buffers_1; + + /// Constructor + drain_buffer() = default; + + /// Copy constructor + drain_buffer(drain_buffer const&) + { + // Previously returned ranges are invalidated + } + + /// Copy assignment + drain_buffer& + operator=(drain_buffer const&) + { + n_ = 0; + return *this; + } + + /// Return the size of the input sequence. + std::size_t + size() const + { + return 0; + } + + /// Return the maximum sum of the input and output sequence sizes. + std::size_t + max_size() const + { + return sizeof(buf_); + } + + /// Return the maximum sum of input and output sizes that can be held without an allocation. + std::size_t + capacity() const + { + return max_size(); + } + + /** Get a list of buffers that represent the input sequence. + + @note These buffers remain valid across subsequent calls to `prepare`. + */ + const_buffers_type + data() const + { + return {}; + } + + /** Get a list of buffers that represent the output sequence, with the given size. + + @throws std::length_error if the size would exceed the buffer limit + */ + mutable_buffers_type + prepare(std::size_t n) + { + if(n > sizeof(buf_)) + BOOST_THROW_EXCEPTION(std::length_error{ + "buffer overflow"}); + n_ = n; + return {buf_, n_}; + } + + /** Move bytes from the output sequence to the input sequence. + + This call always discards the output sequence. + The size of the input sequence will remain at zero. + */ + void + commit(std::size_t) + { + } + + /** Remove bytes from the input sequence. + + This call has no effect. + */ + void + consume(std::size_t) const + { + } +}; +} // beast + +#endif diff --git a/src/beast/include/beast/core/error.hpp b/src/beast/include/beast/core/error.hpp index b8c78687c0..f98f6d7af6 100644 --- a/src/beast/include/beast/core/error.hpp +++ b/src/beast/include/beast/core/error.hpp @@ -23,8 +23,16 @@ using system_error = boost::system::system_error; /// The type of error category used by the library using error_category = boost::system::error_category; +/// A function to return the generic error category used by the library +#if BEAST_DOXYGEN +error_category const& +generic_category(); +#else +using boost::system::generic_category; +#endif + /// A function to return the system error category used by the library -#if GENERATING_DOCS +#if BEAST_DOXYGEN error_category const& system_category(); #else @@ -35,7 +43,7 @@ using boost::system::system_category; using error_condition = boost::system::error_condition; /// The set of constants used for cross-platform error codes -#if GENERATING_DOCS +#if BEAST_DOXYGEN enum errc{}; #else namespace errc = boost::system::errc; diff --git a/src/beast/include/beast/core/file.hpp b/src/beast/include/beast/core/file.hpp new file mode 100644 index 0000000000..8b3eecc874 --- /dev/null +++ b/src/beast/include/beast/core/file.hpp @@ -0,0 +1,41 @@ +// +// Copyright (c) 2015-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_CORE_FILE_HPP +#define BEAST_CORE_FILE_HPP + +#include +#include +#include +#include +#include +#include + +namespace beast { + +/** An implementation of File. + + This alias is set to the best available implementation + of @b File given the platform and build settings. +*/ +#if BEAST_DOXYGEN +struct file : file_stdio +{ +}; +#else +#if BEAST_USE_WIN32_FILE +using file = file_win32; +#elif BEAST_USE_POSIX_FILE +using file = file_posix; +#else +using file = file_stdio; +#endif +#endif + +} // beast + +#endif diff --git a/src/beast/include/beast/core/file_base.hpp b/src/beast/include/beast/core/file_base.hpp new file mode 100644 index 0000000000..d39ebb520f --- /dev/null +++ b/src/beast/include/beast/core/file_base.hpp @@ -0,0 +1,88 @@ +// +// Copyright (c) 2015-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_CORE_FILE_BASE_HPP +#define BEAST_CORE_FILE_BASE_HPP + +#include +#include + +namespace beast { + +/// The type of file path used by the library +using file_path = string_view; + +/** File open modes + + These modes are used when opening files using + instances of the @b File concept. + + @see file_stdio +*/ +enum class file_mode +{ + /// Random reading + read, + + /// Sequential reading + scan, + + /** Random writing to a new or truncated file + + @li If the file does not exist, it is created. + + @li If the file exists, it is truncated to + zero size upon opening. + */ + write, + + /** Random writing to new file only + + If the file exists, an error is generated. + */ + write_new, + + /** Random writing to existing file + + If the file does not exist, an error is generated. + */ + write_existing, + + /** Appending to a new or truncated file + + The current file position shall be set to the end of + the file prior to each write. + + @li If the file does not exist, it is created. + + @li If the file exists, it is truncated to + zero size upon opening. + */ + append, + + /** Appending to a new file only + + The current file position shall be set to the end of + the file prior to each write. + + If the file exists, an error is generated. + */ + append_new, + + /** Appending to an existing file + + The current file position shall be set to the end of + the file prior to each write. + + If the file does not exist, an error is generated. + */ + append_existing +}; + +} // beast + +#endif diff --git a/src/beast/include/beast/core/file_posix.hpp b/src/beast/include/beast/core/file_posix.hpp new file mode 100644 index 0000000000..7e675f2402 --- /dev/null +++ b/src/beast/include/beast/core/file_posix.hpp @@ -0,0 +1,171 @@ +// +// Copyright (c) 2015-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_CORE_FILE_POSIX_HPP +#define BEAST_CORE_FILE_POSIX_HPP + +#include + +#if ! defined(BEAST_NO_POSIX_FILE) +# if ! defined(__APPLE__) && ! defined(__linux__) +# define BEAST_NO_POSIX_FILE +# endif +#endif + +#if ! defined(BEAST_USE_POSIX_FILE) +# if ! defined(BEAST_NO_POSIX_FILE) +# define BEAST_USE_POSIX_FILE 1 +# else +# define BEAST_USE_POSIX_FILE 0 +# endif +#endif + +#if BEAST_USE_POSIX_FILE + +#include +#include +#include + +namespace beast { + +/** An implementation of File for POSIX systems. + + This class implements a @b File using POSIX interfaces. +*/ +class file_posix +{ + int fd_ = -1; + +public: + /** The type of the underlying file handle. + + This is platform-specific. + */ + using native_handle_type = int; + + /** Destructor + + If the file is open it is first closed. + */ + ~file_posix(); + + /** Constructor + + There is no open file initially. + */ + file_posix() = default; + + /** Constructor + + The moved-from object behaves as if default constructed. + */ + file_posix(file_posix&& other); + + /** Assignment + + The moved-from object behaves as if default constructed. + */ + file_posix& operator=(file_posix&& other); + + /// Returns the native handle associated with the file. + native_handle_type + native_handle() const + { + return fd_; + } + + /** Set the native handle associated with the file. + + If the file is open it is first closed. + + @param fd The native file handle to assign. + */ + void + native_handle(native_handle_type fd); + + /// Returns `true` if the file is open + bool + is_open() const + { + return fd_ != -1; + } + + /** Close the file if open + + @param ec Set to the error, if any occurred. + */ + void + close(error_code& ec); + + /** Open a file at the given path with the specified mode + + @param path The utf-8 encoded path to the file + + @param mode The file mode to use + + @param ec Set to the error, if any occurred + */ + void + open(char const* path, file_mode mode, error_code& ec); + + /** Return the size of the open file + + @param ec Set to the error, if any occurred + + @return The size in bytes + */ + std::uint64_t + size(error_code& ec) const; + + /** Return the current position in the open file + + @param ec Set to the error, if any occurred + + @return The offset in bytes from the beginning of the file + */ + std::uint64_t + pos(error_code& ec) const; + + /** Adjust the current position in the open file + + @param offset The offset in bytes from the beginning of the file + + @param ec Set to the error, if any occurred + */ + void + seek(std::uint64_t offset, error_code& ec); + + /** Read from the open file + + @param buffer The buffer for storing the result of the read + + @param n The number of bytes to read + + @param ec Set to the error, if any occurred + */ + std::size_t + read(void* buffer, std::size_t n, error_code& ec) const; + + /** Write to the open file + + @param buffer The buffer holding the data to write + + @param n The number of bytes to write + + @param ec Set to the error, if any occurred + */ + std::size_t + write(void const* buffer, std::size_t n, error_code& ec); +}; + +} // beast + +#include + +#endif + +#endif diff --git a/src/beast/include/beast/core/file_stdio.hpp b/src/beast/include/beast/core/file_stdio.hpp new file mode 100644 index 0000000000..5b8753c609 --- /dev/null +++ b/src/beast/include/beast/core/file_stdio.hpp @@ -0,0 +1,154 @@ +// +// Copyright (c) 2015-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_CORE_FILE_STDIO_HPP +#define BEAST_CORE_FILE_STDIO_HPP + +#include +#include +#include +#include +#include + +namespace beast { + +/** An implementation of File which uses cstdio. + + This class implements a file using the interfaces present + in the C++ Standard Library, in ``. +*/ +class file_stdio +{ + FILE* f_ = nullptr; + +public: + /** The type of the underlying file handle. + + This is platform-specific. + */ + using native_handle_type = FILE*; + + /** Destructor + + If the file is open it is first closed. + */ + ~file_stdio(); + + /** Constructor + + There is no open file initially. + */ + file_stdio() = default; + + /** Constructor + + The moved-from object behaves as if default constructed. + */ + file_stdio(file_stdio&& other); + + /** Assignment + + The moved-from object behaves as if default constructed. + */ + file_stdio& operator=(file_stdio&& other); + + /// Returns the native handle associated with the file. + FILE* + native_handle() const + { + return f_; + } + + /** Set the native handle associated with the file. + + If the file is open it is first closed. + + @param f The native file handle to assign. + */ + void + native_handle(FILE* f); + + /// Returns `true` if the file is open + bool + is_open() const + { + return f_ != nullptr; + } + + /** Close the file if open + + @param ec Set to the error, if any occurred. + */ + void + close(error_code& ec); + + /** Open a file at the given path with the specified mode + + @param path The utf-8 encoded path to the file + + @param mode The file mode to use + + @param ec Set to the error, if any occurred + */ + void + open(char const* path, file_mode mode, error_code& ec); + + /** Return the size of the open file + + @param ec Set to the error, if any occurred + + @return The size in bytes + */ + std::uint64_t + size(error_code& ec) const; + + /** Return the current position in the open file + + @param ec Set to the error, if any occurred + + @return The offset in bytes from the beginning of the file + */ + std::uint64_t + pos(error_code& ec) const; + + /** Adjust the current position in the open file + + @param offset The offset in bytes from the beginning of the file + + @param ec Set to the error, if any occurred + */ + void + seek(std::uint64_t offset, error_code& ec); + + /** Read from the open file + + @param buffer The buffer for storing the result of the read + + @param n The number of bytes to read + + @param ec Set to the error, if any occurred + */ + std::size_t + read(void* buffer, std::size_t n, error_code& ec) const; + + /** Write to the open file + + @param buffer The buffer holding the data to write + + @param n The number of bytes to write + + @param ec Set to the error, if any occurred + */ + std::size_t + write(void const* buffer, std::size_t n, error_code& ec); +}; + +} // beast + +#include + +#endif diff --git a/src/beast/include/beast/core/file_win32.hpp b/src/beast/include/beast/core/file_win32.hpp new file mode 100644 index 0000000000..8aafd3450f --- /dev/null +++ b/src/beast/include/beast/core/file_win32.hpp @@ -0,0 +1,173 @@ +// +// Copyright (c) 2015-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_CORE_FILE_WIN32_HPP +#define BEAST_CORE_FILE_WIN32_HPP + +#include + +#if ! defined(BEAST_USE_WIN32_FILE) +# ifdef BOOST_MSVC +# define BEAST_USE_WIN32_FILE 1 +# else +# define BEAST_USE_WIN32_FILE 0 +# endif +#endif + +#if BEAST_USE_WIN32_FILE + +#include +#include +#include +#include +#include +#include + +namespace beast { + +/** An implementation of File for Win32. + + This class implements a @b File using Win32 native interfaces. +*/ +class file_win32 +{ + boost::detail::winapi::HANDLE_ h_ = + boost::detail::winapi::INVALID_HANDLE_VALUE_; + +public: + /** The type of the underlying file handle. + + This is platform-specific. + */ +#if BEAST_DOXYGEN + using native_handle_type = HANDLE; +#else + using native_handle_type = boost::detail::winapi::HANDLE_; +#endif + + /** Destructor + + If the file is open it is first closed. + */ + ~file_win32(); + + /** Constructor + + There is no open file initially. + */ + file_win32() = default; + + /** Constructor + + The moved-from object behaves as if default constructed. + */ + file_win32(file_win32&& other); + + /** Assignment + + The moved-from object behaves as if default constructed. + */ + file_win32& operator=(file_win32&& other); + + /// Returns the native handle associated with the file. + native_handle_type + native_handle() + { + return h_; + } + + /** Set the native handle associated with the file. + + If the file is open it is first closed. + + @param h The native file handle to assign. + */ + void + native_handle(native_handle_type h); + + /// Returns `true` if the file is open + bool + is_open() const + { + return h_ != boost::detail::winapi::INVALID_HANDLE_VALUE_; + } + + /** Close the file if open + + @param ec Set to the error, if any occurred. + */ + void + close(error_code& ec); + + /** Open a file at the given path with the specified mode + + @param path The utf-8 encoded path to the file + + @param mode The file mode to use + + @param ec Set to the error, if any occurred + */ + void + open(char const* path, file_mode mode, error_code& ec); + + /** Return the size of the open file + + @param ec Set to the error, if any occurred + + @return The size in bytes + */ + std::uint64_t + size(error_code& ec) const; + + /** Return the current position in the open file + + @param ec Set to the error, if any occurred + + @return The offset in bytes from the beginning of the file + */ + std::uint64_t + pos(error_code& ec); + + /** Adjust the current position in the open file + + @param offset The offset in bytes from the beginning of the file + + @param ec Set to the error, if any occurred + */ + void + seek(std::uint64_t offset, error_code& ec); + + /** Read from the open file + + @param buffer The buffer for storing the result of the read + + @param n The number of bytes to read + + @param ec Set to the error, if any occurred + */ + std::size_t + read(void* buffer, std::size_t n, error_code& ec); + + /** Write to the open file + + @param buffer The buffer holding the data to write + + @param n The number of bytes to write + + @param ec Set to the error, if any occurred + */ + std::size_t + write(void const* buffer, std::size_t n, error_code& ec); +}; + +} // beast + +#include + +#endif + +#endif diff --git a/src/beast/include/beast/core/flat_buffer.hpp b/src/beast/include/beast/core/flat_buffer.hpp new file mode 100644 index 0000000000..3d19b71db3 --- /dev/null +++ b/src/beast/include/beast/core/flat_buffer.hpp @@ -0,0 +1,341 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_FLAT_BUFFER_HPP +#define BEAST_FLAT_BUFFER_HPP + +#include +#include +#include +#include +#include + +namespace beast { + +/** A linear dynamic buffer. + + Objects of this type meet the requirements of @b DynamicBuffer + and offer additional invariants: + + @li Buffer sequences returned by @ref data and @ref prepare + will always be of length one. + + @li A configurable maximum buffer size may be set upon + construction. Attempts to exceed the buffer size will throw + `std::length_error`. + + Upon construction, a maximum size for the buffer may be + specified. If this limit is exceeded, the `std::length_error` + exception will be thrown. + + @note This class is designed for use with algorithms that + take dynamic buffers as parameters, and are optimized + for the case where the input sequence or output sequence + is stored in a single contiguous buffer. +*/ +template +class basic_flat_buffer +#if ! BEAST_DOXYGEN + : private detail::empty_base_optimization< + typename std::allocator_traits:: + template rebind_alloc> +#endif +{ +public: +#if BEAST_DOXYGEN + /// The type of allocator used. + using allocator_type = Allocator; +#else + using allocator_type = typename + std::allocator_traits:: + template rebind_alloc; +#endif + +private: + enum + { + min_size = 512 + }; + + template + friend class basic_flat_buffer; + + using alloc_traits = + std::allocator_traits; + + static + inline + std::size_t + dist(char const* first, char const* last) + { + return static_cast(last - first); + } + + char* begin_; + char* in_; + char* out_; + char* last_; + char* end_; + std::size_t max_; + +public: + /// The type used to represent the input sequence as a list of buffers. + using const_buffers_type = boost::asio::const_buffers_1; + + /// The type used to represent the output sequence as a list of buffers. + using mutable_buffers_type = boost::asio::mutable_buffers_1; + + /// Destructor + ~basic_flat_buffer(); + + /** Constructor + + Upon construction, capacity will be zero. + */ + basic_flat_buffer(); + + /** Constructor + + Upon construction, capacity will be zero. + + @param limit The setting for @ref max_size. + */ + explicit + basic_flat_buffer(std::size_t limit); + + /** Constructor + + Upon construction, capacity will be zero. + + @param alloc The allocator to construct with. + */ + explicit + basic_flat_buffer(Allocator const& alloc); + + /** Constructor + + Upon construction, capacity will be zero. + + @param limit The setting for @ref max_size. + + @param alloc The allocator to use. + */ + basic_flat_buffer( + std::size_t limit, Allocator const& alloc); + + /** Move constructor + + After the move, `*this` will have an empty output sequence. + + @param other The object to move from. After the move, + The object's state will be as if constructed using + its current allocator and limit. + */ + basic_flat_buffer(basic_flat_buffer&& other); + + /** Move constructor + + After the move, `*this` will have an empty output sequence. + + @param other The object to move from. After the move, + The object's state will be as if constructed using + its current allocator and limit. + + @param alloc The allocator to use. + */ + basic_flat_buffer( + basic_flat_buffer&& other, Allocator const& alloc); + + /** Copy constructor + + @param other The object to copy from. + */ + basic_flat_buffer(basic_flat_buffer const& other); + + /** Copy constructor + + @param other The object to copy from. + + @param alloc The allocator to use. + */ + basic_flat_buffer(basic_flat_buffer const& other, + Allocator const& alloc); + + /** Copy constructor + + @param other The object to copy from. + */ + template + basic_flat_buffer( + basic_flat_buffer const& other); + + /** Copy constructor + + @param other The object to copy from. + + @param alloc The allocator to use. + */ + template + basic_flat_buffer( + basic_flat_buffer const& other, + Allocator const& alloc); + + /** Move assignment + + After the move, `*this` will have an empty output sequence. + + @param other The object to move from. After the move, + The object's state will be as if constructed using + its current allocator and limit. + */ + basic_flat_buffer& + operator=(basic_flat_buffer&& other); + + /** Copy assignment + + After the copy, `*this` will have an empty output sequence. + + @param other The object to copy from. + */ + basic_flat_buffer& + operator=(basic_flat_buffer const& other); + + /** Copy assignment + + After the copy, `*this` will have an empty output sequence. + + @param other The object to copy from. + */ + template + basic_flat_buffer& + operator=(basic_flat_buffer const& other); + + /// Returns a copy of the associated allocator. + allocator_type + get_allocator() const + { + return this->member(); + } + + /// Returns the size of the input sequence. + std::size_t + size() const + { + return dist(in_, out_); + } + + /// Return the maximum sum of the input and output sequence sizes. + std::size_t + max_size() const + { + return max_; + } + + /// Return the maximum sum of input and output sizes that can be held without an allocation. + std::size_t + capacity() const + { + return dist(begin_, end_); + } + + /// Get a list of buffers that represent the input sequence. + const_buffers_type + data() const + { + return {in_, dist(in_, out_)}; + } + + /** Get a list of buffers that represent the output sequence, with the given size. + + @throws std::length_error if `size() + n` exceeds `max_size()`. + + @note All previous buffers sequences obtained from + calls to @ref data or @ref prepare are invalidated. + */ + mutable_buffers_type + prepare(std::size_t n); + + /** Move bytes from the output sequence to the input sequence. + + @param n The number of bytes to move. If this is larger than + the number of bytes in the output sequences, then the entire + output sequences is moved. + + @note All previous buffers sequences obtained from + calls to @ref data or @ref prepare are invalidated. + */ + void + commit(std::size_t n) + { + out_ += (std::min)(n, dist(out_, last_)); + } + + /** Remove bytes from the input sequence. + + If `n` is greater than the number of bytes in the input + sequence, all bytes in the input sequence are removed. + + @note All previous buffers sequences obtained from + calls to @ref data or @ref prepare are invalidated. + */ + void + consume(std::size_t n); + + /** Reallocate the buffer to fit the input sequence. + + @note All previous buffers sequences obtained from + calls to @ref data or @ref prepare are invalidated. + */ + void + shrink_to_fit(); + + /// Exchange two flat buffers + template + friend + void + swap( + basic_flat_buffer& lhs, + basic_flat_buffer& rhs); + +private: + void + reset(); + + template + void + copy_from(DynamicBuffer const& other); + + void + move_assign(basic_flat_buffer&, std::true_type); + + void + move_assign(basic_flat_buffer&, std::false_type); + + void + copy_assign(basic_flat_buffer const&, std::true_type); + + void + copy_assign(basic_flat_buffer const&, std::false_type); + + void + swap(basic_flat_buffer&); + + void + swap(basic_flat_buffer&, std::true_type); + + void + swap(basic_flat_buffer&, std::false_type); +}; + +using flat_buffer = + basic_flat_buffer>; + +} // beast + +#include + +#endif diff --git a/src/beast/include/beast/core/handler_alloc.hpp b/src/beast/include/beast/core/handler_alloc.hpp index 08a395c3ec..b7656a8835 100644 --- a/src/beast/include/beast/core/handler_alloc.hpp +++ b/src/beast/include/beast/core/handler_alloc.hpp @@ -9,7 +9,9 @@ #define BEAST_HANDLER_ALLOC_HPP #include -#include +#include +#include +#include #include #include #include @@ -35,7 +37,7 @@ namespace beast { the handler is invoked or undefined behavior results. This behavior is described as the "deallocate before invocation" Asio guarantee. */ -#if GENERATING_DOCS +#if BEAST_DOXYGEN template class handler_alloc; #else @@ -56,6 +58,12 @@ private: public: using value_type = T; using is_always_equal = std::true_type; + using pointer = T*; + using reference = T&; + using const_pointer = T const*; + using const_reference = T const&; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; template struct rebind @@ -89,38 +97,46 @@ public: } value_type* - allocate(std::ptrdiff_t n) + allocate(size_type n) { auto const size = n * sizeof(T); + using boost::asio::asio_handler_allocate; return static_cast( - beast_asio_helpers::allocate( - size, h_)); + asio_handler_allocate(size, std::addressof(h_))); } void - deallocate(value_type* p, std::ptrdiff_t n) + deallocate(value_type* p, size_type n) { auto const size = n * sizeof(T); - beast_asio_helpers::deallocate( - p, size, h_); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate(p, size, std::addressof(h_)); } -#ifdef _MSC_VER - // Work-around for MSVC not using allocator_traits - // in the implementation of shared_ptr - // +//#if BOOST_WORKAROUND(BOOST_GCC, < 60000) // Works, but too coarse + +#if defined(BOOST_LIBSTDCXX_VERSION) && BOOST_LIBSTDCXX_VERSION < 60000 + template void - destroy(T* t) + construct(U* ptr, Args&&... args) { - t->~T(); + ::new((void*)ptr) U(std::forward(args)...); + } + + template + void + destroy(U* ptr) + { + ptr->~U(); } #endif template friend bool - operator==(handler_alloc const& lhs, - handler_alloc const& rhs) + operator==( + handler_alloc const&, + handler_alloc const&) { return true; } @@ -128,7 +144,8 @@ public: template friend bool - operator!=(handler_alloc const& lhs, + operator!=( + handler_alloc const& lhs, handler_alloc const& rhs) { return ! (lhs == rhs); diff --git a/src/beast/include/beast/core/handler_concepts.hpp b/src/beast/include/beast/core/handler_concepts.hpp deleted file mode 100644 index e118072aeb..0000000000 --- a/src/beast/include/beast/core/handler_concepts.hpp +++ /dev/null @@ -1,28 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_HANDLER_CONCEPTS_HPP -#define BEAST_HANDLER_CONCEPTS_HPP - -#include -#include -#include - -namespace beast { - -/// Determine if `T` meets the requirements of @b `CompletionHandler`. -template -#if GENERATING_DOCS -using is_CompletionHandler = std::integral_constant; -#else -using is_CompletionHandler = std::integral_constant::type>::value && - detail::is_call_possible::value>; -#endif -} // beast - -#endif diff --git a/src/beast/include/beast/core/handler_helpers.hpp b/src/beast/include/beast/core/handler_helpers.hpp deleted file mode 100644 index 53112e7f15..0000000000 --- a/src/beast/include/beast/core/handler_helpers.hpp +++ /dev/null @@ -1,105 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_HANDLER_HELPERS_HPP -#define BEAST_HANDLER_HELPERS_HPP - -#include -#include -#include -#include -#include - -/* Calls to: - - * asio_handler_allocate - * asio_handler_deallocate - * asio_handler_invoke - * asio_handler_is_continuation - - must be made from a namespace that does not - contain overloads of this function. The beast_asio_helpers - namespace is defined here for that purpose. -*/ - -namespace beast_asio_helpers { - -/// Allocation function for handlers. -template -inline -void* -allocate(std::size_t s, Handler& handler) -{ -#if !defined(BOOST_ASIO_HAS_HANDLER_HOOKS) - return ::operator new(s); -#else - using boost::asio::asio_handler_allocate; - return asio_handler_allocate(s, std::addressof(handler)); -#endif -} - -/// Deallocation function for handlers. -template -inline -void -deallocate(void* p, std::size_t s, Handler& handler) -{ -#if !defined(BOOST_ASIO_HAS_HANDLER_HOOKS) - ::operator delete(p); -#else - using boost::asio::asio_handler_deallocate; - asio_handler_deallocate(p, s, std::addressof(handler)); -#endif -} - -/// Invoke function for handlers. -template -inline -void -invoke(Function& function, Handler& handler) -{ -#if !defined(BOOST_ASIO_HAS_HANDLER_HOOKS) - Function tmp(function); - tmp(); -#else - using boost::asio::asio_handler_invoke; - asio_handler_invoke(function, std::addressof(handler)); -#endif -} - -/// Invoke function for handlers. -template -inline -void -invoke(Function const& function, Handler& handler) -{ -#if !defined(BOOST_ASIO_HAS_HANDLER_HOOKS) - Function tmp(function); - tmp(); -#else - using boost::asio::asio_handler_invoke; - asio_handler_invoke(function, std::addressof(handler)); -#endif -} - -/// Returns true if handler represents a continuation of the asynchronous operation -template -inline -bool -is_continuation(Handler& handler) -{ -#if !defined(BOOST_ASIO_HAS_HANDLER_HOOKS) - return false; -#else - using boost::asio::asio_handler_is_continuation; - return asio_handler_is_continuation(std::addressof(handler)); -#endif -} - -} // beast_asio_helpers - -#endif diff --git a/src/beast/include/beast/core/handler_ptr.hpp b/src/beast/include/beast/core/handler_ptr.hpp index 52650656e4..c288c4d1f9 100644 --- a/src/beast/include/beast/core/handler_ptr.hpp +++ b/src/beast/include/beast/core/handler_ptr.hpp @@ -191,6 +191,11 @@ public: deallocation-before-invocation Asio guarantee. All instances of @ref handler_ptr which refer to the same owned object will be reset, including this instance. + + @note Care must be taken when the arguments are themselves + stored in the owned object. Such arguments must first be + moved to the stack or elsewhere, and then passed, or else + undefined behavior will result. */ template void diff --git a/src/beast/include/beast/core/detail/buffer_cat.hpp b/src/beast/include/beast/core/impl/buffer_cat.ipp similarity index 73% rename from src/beast/include/beast/core/detail/buffer_cat.hpp rename to src/beast/include/beast/core/impl/buffer_cat.ipp index 60d840c220..f0f9d45eaf 100644 --- a/src/beast/include/beast/core/detail/buffer_cat.hpp +++ b/src/beast/include/beast/core/impl/buffer_cat.ipp @@ -5,12 +5,13 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef BEAST_DETAIL_BUFFER_CAT_HPP -#define BEAST_DETAIL_BUFFER_CAT_HPP +#ifndef BEAST_IMPL_BUFFER_CAT_IPP +#define BEAST_IMPL_BUFFER_CAT_IPP -#include #include #include +#include +#include #include #include #include @@ -19,57 +20,22 @@ #include namespace beast { -namespace detail { template -struct common_buffers_type +class buffer_cat_view::const_iterator { - using type = typename std::conditional< - std::is_convertible, - typename repeat_tuple::type>::value, - boost::asio::mutable_buffer, - boost::asio::const_buffer>::type; -}; +#if 0 + static_assert( + detail::is_all_const_buffer_sequence::value, + "BufferSequence requirements not met"); +#endif -template -class buffer_cat_helper -{ - std::tuple bn_; - -public: - using value_type = typename - common_buffers_type::type; - - class const_iterator; - - buffer_cat_helper(buffer_cat_helper&&) = default; - buffer_cat_helper(buffer_cat_helper const&) = default; - buffer_cat_helper& operator=(buffer_cat_helper&&) = delete; - buffer_cat_helper& operator=(buffer_cat_helper const&) = delete; - - explicit - buffer_cat_helper(Bn const&... bn) - : bn_(bn...) - { - } - - const_iterator - begin() const; - - const_iterator - end() const; -}; - -template -class buffer_cat_helper::const_iterator -{ std::size_t n_; std::tuple const* bn_; - std::array()> buf_; + std::array()> buf_; - friend class buffer_cat_helper; + friend class buffer_cat_view; template using C = std::integral_constant; @@ -84,8 +50,7 @@ class buffer_cat_helper::const_iterator { // type-pun return *reinterpret_cast< - iter_t*>(static_cast( - buf_.data())); + iter_t*>(static_cast(buf_.data())); } template @@ -100,7 +65,7 @@ class buffer_cat_helper::const_iterator public: using value_type = typename - common_buffers_type::type; + detail::common_buffers_type::type; using pointer = value_type const*; using reference = value_type; using difference_type = std::ptrdiff_t; @@ -120,7 +85,7 @@ public: bool operator!=(const_iterator const& other) const { - return !(*this == other); + return ! (*this == other); } reference @@ -133,23 +98,13 @@ public: operator++(); const_iterator - operator++(int) - { - auto temp = *this; - ++(*this); - return temp; - } + operator++(int); const_iterator& operator--(); const_iterator - operator--(int) - { - auto temp = *this; - --(*this); - return temp; - } + operator--(int); private: const_iterator( @@ -166,17 +121,48 @@ private: void construct(C const&) { - if(std::get(*bn_).begin() != - std::get(*bn_).end()) + if(boost::asio::buffer_size( + std::get(*bn_)) != 0) { n_ = I; - new(buf_.data()) iter_t{ + new(&buf_[0]) iter_t{ std::get(*bn_).begin()}; return; } construct(C{}); } + void + rconstruct(C<0> const&) + { + auto constexpr I = 0; + if(boost::asio::buffer_size( + std::get(*bn_)) != 0) + { + n_ = I; + new(&buf_[0]) iter_t{ + std::get(*bn_).end()}; + return; + } + BOOST_THROW_EXCEPTION(std::logic_error{ + "invalid iterator"}); + } + + template + void + rconstruct(C const&) + { + if(boost::asio::buffer_size( + std::get(*bn_)) != 0) + { + n_ = I; + new(&buf_[0]) iter_t{ + std::get(*bn_).end()}; + return; + } + rconstruct(C{}); + } + void destroy(C const&) { @@ -209,7 +195,7 @@ private: { if(n_ == I) { - new(buf_.data()) iter_t{ + new(&buf_[0]) iter_t{ std::move(other.iter())}; return; } @@ -229,7 +215,7 @@ private: { if(n_ == I) { - new(buf_.data()) iter_t{ + new(&buf_[0]) iter_t{ other.iter()}; return; } @@ -257,8 +243,8 @@ private: reference dereference(C const&) const { - throw detail::make_exception( - "invalid iterator", __FILE__, __LINE__); + BOOST_THROW_EXCEPTION(std::logic_error{ + "invalid iterator"}); } template @@ -274,8 +260,8 @@ private: void increment(C const&) { - throw detail::make_exception( - "invalid iterator", __FILE__, __LINE__); + BOOST_THROW_EXCEPTION(std::logic_error{ + "invalid iterator"}); } template @@ -299,27 +285,10 @@ private: { auto constexpr I = sizeof...(Bn); if(n_ == I) - { - --n_; - new(buf_.data()) iter_t{ - std::get(*bn_).end()}; - } + rconstruct(C{}); decrement(C{}); } - void - decrement(C<0> const&) - { - auto constexpr I = 0; - if(iter() != std::get(*bn_).begin()) - { - --iter(); - return; - } - throw detail::make_exception( - "invalid iterator", __FILE__, __LINE__); - } - template void decrement(C const&) @@ -334,24 +303,36 @@ private: --n_; using Iter = iter_t; iter().~Iter(); - new(buf_.data()) iter_t{ - std::get(*bn_).end()}; + rconstruct(C{}); } decrement(C{}); } + + void + decrement(C<0> const&) + { + auto constexpr I = 0; + if(iter() != std::get(*bn_).begin()) + { + --iter(); + return; + } + BOOST_THROW_EXCEPTION(std::logic_error{ + "invalid iterator"}); + } }; //------------------------------------------------------------------------------ template -buffer_cat_helper:: +buffer_cat_view:: const_iterator::~const_iterator() { destroy(C<0>{}); } template -buffer_cat_helper:: +buffer_cat_view:: const_iterator::const_iterator() : n_(sizeof...(Bn)) , bn_(nullptr) @@ -359,7 +340,7 @@ const_iterator::const_iterator() } template -buffer_cat_helper:: +buffer_cat_view:: const_iterator::const_iterator( std::tuple const& bn, bool at_end) : bn_(&bn) @@ -371,7 +352,7 @@ const_iterator::const_iterator( } template -buffer_cat_helper:: +buffer_cat_view:: const_iterator::const_iterator(const_iterator&& other) : n_(other.n_) , bn_(other.bn_) @@ -380,7 +361,7 @@ const_iterator::const_iterator(const_iterator&& other) } template -buffer_cat_helper:: +buffer_cat_view:: const_iterator::const_iterator(const_iterator const& other) : n_(other.n_) , bn_(other.bn_) @@ -390,7 +371,7 @@ const_iterator::const_iterator(const_iterator const& other) template auto -buffer_cat_helper:: +buffer_cat_view:: const_iterator::operator=(const_iterator&& other) -> const_iterator& { @@ -399,13 +380,14 @@ const_iterator::operator=(const_iterator&& other) -> destroy(C<0>{}); n_ = other.n_; bn_ = other.bn_; + // VFALCO What about exceptions? move(std::move(other), C<0>{}); return *this; } template auto -buffer_cat_helper:: +buffer_cat_view:: const_iterator::operator=(const_iterator const& other) -> const_iterator& { @@ -414,13 +396,14 @@ const_iterator& destroy(C<0>{}); n_ = other.n_; bn_ = other.bn_; + // VFALCO What about exceptions? copy(other, C<0>{}); return *this; } template bool -buffer_cat_helper:: +buffer_cat_view:: const_iterator::operator==(const_iterator const& other) const { if(bn_ != other.bn_) @@ -432,7 +415,7 @@ const_iterator::operator==(const_iterator const& other) const template auto -buffer_cat_helper:: +buffer_cat_view:: const_iterator::operator*() const -> reference { @@ -441,7 +424,7 @@ const_iterator::operator*() const -> template auto -buffer_cat_helper:: +buffer_cat_view:: const_iterator::operator++() -> const_iterator& { @@ -451,7 +434,18 @@ const_iterator::operator++() -> template auto -buffer_cat_helper:: +buffer_cat_view:: +const_iterator::operator++(int) -> + const_iterator +{ + auto temp = *this; + ++(*this); + return temp; +} + +template +auto +buffer_cat_view:: const_iterator::operator--() -> const_iterator& { @@ -459,10 +453,31 @@ const_iterator::operator--() -> return *this; } +template +auto +buffer_cat_view:: +const_iterator::operator--(int) -> + const_iterator +{ + auto temp = *this; + --(*this); + return temp; +} + +//------------------------------------------------------------------------------ + +template +buffer_cat_view:: +buffer_cat_view(Bn const&... bn) + : bn_(bn...) +{ +} + + template inline auto -buffer_cat_helper::begin() const -> +buffer_cat_view::begin() const -> const_iterator { return const_iterator{bn_, false}; @@ -471,13 +486,12 @@ buffer_cat_helper::begin() const -> template inline auto -buffer_cat_helper::end() const -> +buffer_cat_view::end() const -> const_iterator { return const_iterator{bn_, true}; } -} // detail } // beast #endif diff --git a/src/beast/include/beast/core/detail/prepare_buffers.hpp b/src/beast/include/beast/core/impl/buffer_prefix.ipp similarity index 54% rename from src/beast/include/beast/core/detail/prepare_buffers.hpp rename to src/beast/include/beast/core/impl/buffer_prefix.ipp index 4f9441c9b3..b3aa55782a 100644 --- a/src/beast/include/beast/core/detail/prepare_buffers.hpp +++ b/src/beast/include/beast/core/impl/buffer_prefix.ipp @@ -5,11 +5,9 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef BEAST_DETAIL_PREPARED_BUFFERS_HPP -#define BEAST_DETAIL_PREPARED_BUFFERS_HPP +#ifndef BEAST_IMPL_BUFFER_PREFIX_IPP +#define BEAST_IMPL_BUFFER_PREFIX_IPP -#include -#include #include #include #include @@ -18,102 +16,40 @@ #include namespace beast { + namespace detail { -/** A buffer sequence adapter that shortens the sequence size. - - The class adapts a buffer sequence to efficiently represent - a shorter subset of the original list of buffers starting - with the first byte of the original sequence. - - @tparam BufferSequence The buffer sequence to adapt. -*/ -template -class prepared_buffers +inline +boost::asio::const_buffer +buffer_prefix(std::size_t n, + boost::asio::const_buffer buffer) { - using iter_type = - typename BufferSequence::const_iterator; + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + return { buffer_cast(buffer), + (std::min)(n, buffer_size(buffer)) }; +} - BufferSequence bs_; - iter_type back_; - iter_type end_; - std::size_t size_; +inline +boost::asio::mutable_buffer +buffer_prefix(std::size_t n, + boost::asio::mutable_buffer buffer) +{ + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + return { buffer_cast(buffer), + (std::min)(n, buffer_size(buffer)) }; +} - template - prepared_buffers(Deduced&& other, - std::size_t nback, std::size_t nend) - : bs_(std::forward(other).bs_) - , back_(std::next(bs_.begin(), nback)) - , end_(std::next(bs_.begin(), nend)) - , size_(other.size_) - { - } - - void - setup(std::size_t n); - -public: - /// The type for each element in the list of buffers. - using value_type = typename std::conditional< - std::is_convertible::value_type, - boost::asio::mutable_buffer>::value, - boost::asio::mutable_buffer, - boost::asio::const_buffer>::type; - -#if GENERATING_DOCS - /// A bidirectional iterator type that may be used to read elements. - using const_iterator = implementation_defined; - -#else - class const_iterator; - -#endif - - /// Move constructor. - prepared_buffers(prepared_buffers&&); - - /// Copy constructor. - prepared_buffers(prepared_buffers const&); - - /// Move assignment. - prepared_buffers& operator=(prepared_buffers&&); - - /// Copy assignment. - prepared_buffers& operator=(prepared_buffers const&); - - /** Construct a shortened buffer sequence. - - @param n The maximum number of bytes in the wrapped - sequence. If this is larger than the size of passed, - buffers, the resulting sequence will represent the - entire input sequence. - - @param buffers The buffer sequence to adapt. A copy of - the sequence will be made, but ownership of the underlying - memory is not transferred. - */ - prepared_buffers(std::size_t n, BufferSequence const& buffers); - - /// Get a bidirectional iterator to the first element. - const_iterator - begin() const; - - /// Get a bidirectional iterator to one past the last element. - const_iterator - end() const; -}; +} // detail template -class prepared_buffers::const_iterator +class buffer_prefix_view::const_iterator { - friend class prepared_buffers; + friend class buffer_prefix_view; - using iter_type = - typename BufferSequence::const_iterator; - - prepared_buffers const* b_ = nullptr; - typename BufferSequence::const_iterator it_; + buffer_prefix_view const* b_ = nullptr; + iter_type it_; public: using value_type = typename std::conditional< @@ -150,7 +86,7 @@ public: operator*() const { if(it_ == b_->back_) - return prepare_buffer(b_->size_, *it_); + return detail::buffer_prefix(b_->size_, *it_); return *it_; } @@ -188,7 +124,7 @@ public: } private: - const_iterator(prepared_buffers const& b, + const_iterator(buffer_prefix_view const& b, bool at_end) : b_(&b) , it_(at_end ? b.end_ : b.bs_.begin()) @@ -198,7 +134,7 @@ private: template void -prepared_buffers:: +buffer_prefix_view:: setup(std::size_t n) { for(end_ = bs_.begin(); end_ != bs_.end(); ++end_) @@ -218,7 +154,8 @@ setup(std::size_t n) } template -prepared_buffers::const_iterator:: +buffer_prefix_view:: +const_iterator:: const_iterator(const_iterator&& other) : b_(other.b_) , it_(std::move(other.it_)) @@ -226,7 +163,8 @@ const_iterator(const_iterator&& other) } template -prepared_buffers::const_iterator:: +buffer_prefix_view:: +const_iterator:: const_iterator(const_iterator const& other) : b_(other.b_) , it_(other.it_) @@ -235,7 +173,8 @@ const_iterator(const_iterator const& other) template auto -prepared_buffers::const_iterator:: +buffer_prefix_view:: +const_iterator:: operator=(const_iterator&& other) -> const_iterator& { @@ -246,7 +185,8 @@ operator=(const_iterator&& other) -> template auto -prepared_buffers::const_iterator:: +buffer_prefix_view:: +const_iterator:: operator=(const_iterator const& other) -> const_iterator& { @@ -258,18 +198,18 @@ operator=(const_iterator const& other) -> } template -prepared_buffers:: -prepared_buffers(prepared_buffers&& other) - : prepared_buffers(std::move(other), +buffer_prefix_view:: +buffer_prefix_view(buffer_prefix_view&& other) + : buffer_prefix_view(std::move(other), std::distance(other.bs_.begin(), other.back_), std::distance(other.bs_.begin(), other.end_)) { } template -prepared_buffers:: -prepared_buffers(prepared_buffers const& other) - : prepared_buffers(other, +buffer_prefix_view:: +buffer_prefix_view(buffer_prefix_view const& other) + : buffer_prefix_view(other, std::distance(other.bs_.begin(), other.back_), std::distance(other.bs_.begin(), other.end_)) { @@ -277,9 +217,9 @@ prepared_buffers(prepared_buffers const& other) template auto -prepared_buffers:: -operator=(prepared_buffers&& other) -> - prepared_buffers& +buffer_prefix_view:: +operator=(buffer_prefix_view&& other) -> + buffer_prefix_view& { auto const nback = std::distance( other.bs_.begin(), other.back_); @@ -294,9 +234,9 @@ operator=(prepared_buffers&& other) -> template auto -prepared_buffers:: -operator=(prepared_buffers const& other) -> - prepared_buffers& +buffer_prefix_view:: +operator=(buffer_prefix_view const& other) -> + buffer_prefix_view& { auto const nback = std::distance( other.bs_.begin(), other.back_); @@ -310,17 +250,27 @@ operator=(prepared_buffers const& other) -> } template -prepared_buffers:: -prepared_buffers(std::size_t n, BufferSequence const& bs) +buffer_prefix_view:: +buffer_prefix_view(std::size_t n, BufferSequence const& bs) : bs_(bs) { setup(n); } +template +template +buffer_prefix_view:: +buffer_prefix_view(std::size_t n, + boost::in_place_init_t, Args&&... args) + : bs_(std::forward(args)...) +{ + setup(n); +} + template inline auto -prepared_buffers::begin() const -> +buffer_prefix_view::begin() const -> const_iterator { return const_iterator{*this, false}; @@ -329,13 +279,12 @@ prepared_buffers::begin() const -> template inline auto -prepared_buffers::end() const -> +buffer_prefix_view::end() const -> const_iterator { return const_iterator{*this, true}; } -} // detail } // beast #endif diff --git a/src/beast/include/beast/core/impl/dynabuf_readstream.ipp b/src/beast/include/beast/core/impl/buffered_read_stream.ipp similarity index 50% rename from src/beast/include/beast/core/impl/dynabuf_readstream.ipp rename to src/beast/include/beast/core/impl/buffered_read_stream.ipp index 5a0815baac..d78c136149 100644 --- a/src/beast/include/beast/core/impl/dynabuf_readstream.ipp +++ b/src/beast/include/beast/core/impl/buffered_read_stream.ipp @@ -5,38 +5,30 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef BEAST_IMPL_DYNABUF_READSTREAM_HPP -#define BEAST_IMPL_DYNABUF_READSTREAM_HPP +#ifndef BEAST_IMPL_BUFFERED_READ_STREAM_IPP +#define BEAST_IMPL_BUFFERED_READ_STREAM_IPP #include #include -#include -#include #include +#include +#include +#include +#include +#include +#include namespace beast { template template -class dynabuf_readstream< +class buffered_read_stream< Stream, DynamicBuffer>::read_some_op { - // VFALCO What about bool cont for is_continuation? - struct data - { - dynabuf_readstream& srs; - MutableBufferSequence bs; - int state = 0; - - data(Handler&, dynabuf_readstream& srs_, - MutableBufferSequence const& bs_) - : srs(srs_) - , bs(bs_) - { - } - }; - - handler_ptr d_; + int step_ = 0; + buffered_read_stream& s_; + MutableBufferSequence b_; + Handler h_; public: read_some_op(read_some_op&&) = default; @@ -44,11 +36,12 @@ public: template read_some_op(DeducedHandler&& h, - dynabuf_readstream& srs, Args&&... args) - : d_(std::forward(h), - srs, std::forward(args)...) + buffered_read_stream& s, + MutableBufferSequence const& b) + : s_(s) + , b_(b) + , h_(std::forward(h)) { - (*this)(error_code{}, 0); } void @@ -59,99 +52,92 @@ public: void* asio_handler_allocate( std::size_t size, read_some_op* op) { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); + using boost::asio::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(op->h_)); } friend void asio_handler_deallocate( void* p, std::size_t size, read_some_op* op) { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->h_)); } friend bool asio_handler_is_continuation(read_some_op* op) { - return beast_asio_helpers:: - is_continuation(op->d_.handler()); + using boost::asio::asio_handler_is_continuation; + return asio_handler_is_continuation( + std::addressof(op->h_)); } template friend void asio_handler_invoke(Function&& f, read_some_op* op) { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); + using boost::asio::asio_handler_invoke; + asio_handler_invoke(f, std::addressof(op->h_)); } }; template template void -dynabuf_readstream:: +buffered_read_stream:: read_some_op::operator()( error_code const& ec, std::size_t bytes_transferred) { - auto& d = *d_; - while(! ec && d.state != 99) + switch(step_) { - switch(d.state) + case 0: + if(s_.sb_.size() == 0) { - case 0: - if(d.srs.sb_.size() == 0) + if(s_.capacity_ == 0) { - d.state = - d.srs.capacity_ > 0 ? 2 : 1; - break; + // read (unbuffered) + step_ = 1; + return s_.next_layer_.async_read_some( + b_, std::move(*this)); } - d.state = 4; - d.srs.get_io_service().post( - bind_handler(std::move(*this), ec, 0)); - return; - case 1: - // read (unbuffered) - d.state = 99; - d.srs.next_layer_.async_read_some( - d.bs, std::move(*this)); - return; - - case 2: // read - d.state = 3; - d.srs.next_layer_.async_read_some( - d.srs.sb_.prepare(d.srs.capacity_), + step_ = 2; + return s_.next_layer_.async_read_some( + s_.sb_.prepare(s_.capacity_), std::move(*this)); - return; - // got data - case 3: - d.state = 4; - d.srs.sb_.commit(bytes_transferred); - break; - - // copy - case 4: - bytes_transferred = - boost::asio::buffer_copy( - d.bs, d.srs.sb_.data()); - d.srs.sb_.consume(bytes_transferred); - // call handler - d.state = 99; - break; } + step_ = 3; + s_.get_io_service().post( + bind_handler(std::move(*this), ec, 0)); + return; + + case 1: + // upcall + break; + + case 2: + s_.sb_.commit(bytes_transferred); + BEAST_FALLTHROUGH; + + case 3: + bytes_transferred = + boost::asio::buffer_copy(b_, s_.sb_.data()); + s_.sb_.consume(bytes_transferred); + break; } - d_.invoke(ec, bytes_transferred); + h_(ec, bytes_transferred); } //------------------------------------------------------------------------------ template template -dynabuf_readstream:: -dynabuf_readstream(Args&&... args) +buffered_read_stream:: +buffered_read_stream(Args&&... args) : next_layer_(std::forward(args)...) { } @@ -159,18 +145,17 @@ dynabuf_readstream(Args&&... args) template template auto -dynabuf_readstream:: +buffered_read_stream:: async_write_some(ConstBufferSequence const& buffers, - WriteHandler&& handler) -> - typename async_completion< - WriteHandler, void(error_code)>::result_type + WriteHandler&& handler) -> + async_return_type { - static_assert(is_AsyncWriteStream::value, + static_assert(is_async_write_stream::value, "AsyncWriteStream requirements not met"); - static_assert(is_ConstBufferSequence< + static_assert(is_const_buffer_sequence< ConstBufferSequence>::value, "ConstBufferSequence requirements not met"); - static_assert(is_CompletionHandler::value, "WriteHandler requirements not met"); return next_layer_.async_write_some(buffers, @@ -180,32 +165,32 @@ async_write_some(ConstBufferSequence const& buffers, template template std::size_t -dynabuf_readstream:: +buffered_read_stream:: read_some( MutableBufferSequence const& buffers) { - static_assert(is_SyncReadStream::value, + static_assert(is_sync_read_stream::value, "SyncReadStream requirements not met"); - static_assert(is_MutableBufferSequence< + static_assert(is_mutable_buffer_sequence< MutableBufferSequence>::value, "MutableBufferSequence requirements not met"); error_code ec; auto n = read_some(buffers, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); return n; } template template std::size_t -dynabuf_readstream:: +buffered_read_stream:: read_some(MutableBufferSequence const& buffers, error_code& ec) { - static_assert(is_SyncReadStream::value, + static_assert(is_sync_read_stream::value, "SyncReadStream requirements not met"); - static_assert(is_MutableBufferSequence< + static_assert(is_mutable_buffer_sequence< MutableBufferSequence>::value, "MutableBufferSequence requirements not met"); using boost::asio::buffer_size; @@ -219,6 +204,10 @@ read_some(MutableBufferSequence const& buffers, if(ec) return 0; } + else + { + ec.assign(0, ec.category()); + } auto bytes_transferred = buffer_copy(buffers, sb_.data()); sb_.consume(bytes_transferred); @@ -228,25 +217,23 @@ read_some(MutableBufferSequence const& buffers, template template auto -dynabuf_readstream:: -async_read_some( - MutableBufferSequence const& buffers, +buffered_read_stream:: +async_read_some(MutableBufferSequence const& buffers, ReadHandler&& handler) -> - typename async_completion< - ReadHandler, void(error_code)>::result_type + async_return_type { - static_assert(is_AsyncReadStream::value, + static_assert(is_async_read_stream::value, "Stream requirements not met"); - static_assert(is_MutableBufferSequence< + static_assert(is_mutable_buffer_sequence< MutableBufferSequence>::value, "MutableBufferSequence requirements not met"); - beast::async_completion< - ReadHandler, void(error_code, std::size_t) - > completion{handler}; - read_some_op{ - completion.handler, *this, buffers}; - return completion.result.get(); + async_completion init{handler}; + read_some_op>{ + init.completion_handler, *this, buffers}( + error_code{}, 0); + return init.result.get(); } } // beast diff --git a/src/beast/include/beast/core/impl/buffers_adapter.ipp b/src/beast/include/beast/core/impl/buffers_adapter.ipp index 286cfa86ff..3216667f1b 100644 --- a/src/beast/include/beast/core/impl/buffers_adapter.ipp +++ b/src/beast/include/beast/core/impl/buffers_adapter.ipp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -414,8 +415,8 @@ buffers_adapter::prepare(std::size_t n) -> } } if(n > 0) - throw detail::make_exception( - "no space", __FILE__, __LINE__); + BOOST_THROW_EXCEPTION(std::length_error{ + "buffer overflow"}); return mutable_buffers_type{*this}; } diff --git a/src/beast/include/beast/core/impl/consuming_buffers.ipp b/src/beast/include/beast/core/impl/consuming_buffers.ipp index cebf1195c4..fca247ef13 100644 --- a/src/beast/include/beast/core/impl/consuming_buffers.ipp +++ b/src/beast/include/beast/core/impl/consuming_buffers.ipp @@ -8,8 +8,7 @@ #ifndef BEAST_IMPL_CONSUMING_BUFFERS_IPP #define BEAST_IMPL_CONSUMING_BUFFERS_IPP -#include -#include +#include #include #include #include @@ -18,13 +17,13 @@ namespace beast { -template -class consuming_buffers::const_iterator +template +class consuming_buffers::const_iterator { - friend class consuming_buffers; + friend class consuming_buffers; using iter_type = - typename BufferSequence::const_iterator; + typename Buffers::const_iterator; iter_type it_; consuming_buffers const* b_ = nullptr; @@ -110,8 +109,17 @@ private: } }; -template -consuming_buffers:: +//------------------------------------------------------------------------------ + +template +consuming_buffers:: +consuming_buffers() + : begin_(bs_.begin()) +{ +} + +template +consuming_buffers:: consuming_buffers(consuming_buffers&& other) : consuming_buffers(std::move(other), std::distance( @@ -119,8 +127,8 @@ consuming_buffers(consuming_buffers&& other) { } -template -consuming_buffers:: +template +consuming_buffers:: consuming_buffers(consuming_buffers const& other) : consuming_buffers(other, std::distance( @@ -128,9 +136,35 @@ consuming_buffers(consuming_buffers const& other) { } -template +template +consuming_buffers:: +consuming_buffers(Buffers const& bs) + : bs_(bs) + , begin_(bs_.begin()) +{ + static_assert( + is_const_buffer_sequence::value|| + is_mutable_buffer_sequence::value, + "BufferSequence requirements not met"); +} + +template +template +consuming_buffers:: +consuming_buffers(boost::in_place_init_t, Args&&... args) + : bs_(std::forward(args)...) + , begin_(bs_.begin()) +{ + static_assert(sizeof...(Args) > 0, + "Missing constructor arguments"); + static_assert( + std::is_constructible::value, + "Buffers not constructible from arguments"); +} + +template auto -consuming_buffers:: +consuming_buffers:: operator=(consuming_buffers&& other) -> consuming_buffers& { @@ -142,9 +176,9 @@ operator=(consuming_buffers&& other) -> return *this; } -template +template auto -consuming_buffers:: +consuming_buffers:: operator=(consuming_buffers const& other) -> consuming_buffers& { @@ -156,40 +190,29 @@ operator=(consuming_buffers const& other) -> return *this; } -template -consuming_buffers:: -consuming_buffers(BufferSequence const& bs) - : bs_(bs) - , begin_(bs_.begin()) -{ - static_assert( - is_BufferSequence::value, - "BufferSequence requirements not met"); -} - -template +template inline auto -consuming_buffers:: +consuming_buffers:: begin() const -> const_iterator { return const_iterator{*this, begin_}; } -template +template inline auto -consuming_buffers:: +consuming_buffers:: end() const -> const_iterator { return const_iterator{*this, bs_.end()}; } -template +template void -consuming_buffers:: +consuming_buffers:: consume(std::size_t n) { using boost::asio::buffer_size; diff --git a/src/beast/include/beast/core/impl/file_posix.ipp b/src/beast/include/beast/core/impl/file_posix.ipp new file mode 100644 index 0000000000..f0a7279bb6 --- /dev/null +++ b/src/beast/include/beast/core/impl/file_posix.ipp @@ -0,0 +1,331 @@ +// +// Copyright (c) 2015-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_CORE_IMPL_FILE_POSIX_IPP +#define BEAST_CORE_IMPL_FILE_POSIX_IPP + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { + +namespace detail { + +inline +int +file_posix_close(int fd) +{ + for(;;) + { + if(! ::close(fd)) + break; + int const ev = errno; + if(errno != EINTR) + return ev; + } + return 0; +} + +} // detail + +inline +file_posix:: +~file_posix() +{ + if(fd_ != -1) + detail::file_posix_close(fd_); +} + +inline +file_posix:: +file_posix(file_posix&& other) + : fd_(other.fd_) +{ + other.fd_ = -1; +} + +inline +file_posix& +file_posix:: +operator=(file_posix&& other) +{ + if(&other == this) + return *this; + if(fd_ != -1) + detail::file_posix_close(fd_); + fd_ = other.fd_; + other.fd_ = -1; + return *this; +} + +inline +void +file_posix:: +native_handle(native_handle_type fd) +{ + if(fd_ != -1) + detail::file_posix_close(fd_); + fd_ = fd; +} + +inline +void +file_posix:: +close(error_code& ec) +{ + if(fd_ != -1) + { + auto const ev = + detail::file_posix_close(fd_); + if(ev) + ec.assign(ev, generic_category()); + else + ec.assign(0, ec.category()); + fd_ = -1; + } + else + { + ec.assign(0, ec.category()); + } +} + +inline +void +file_posix:: +open(char const* path, file_mode mode, error_code& ec) +{ + if(fd_ != -1) + { + auto const ev = + detail::file_posix_close(fd_); + if(ev) + ec.assign(ev, generic_category()); + else + ec.assign(0, ec.category()); + fd_ = -1; + } + int f = 0; +#ifndef __APPLE__ + int advise = 0; +#endif + switch(mode) + { + default: + case file_mode::read: + f = O_RDONLY; + #ifndef __APPLE__ + advise = POSIX_FADV_RANDOM; + #endif + break; + case file_mode::scan: + f = O_RDONLY; + #ifndef __APPLE__ + advise = POSIX_FADV_SEQUENTIAL; + #endif + break; + + case file_mode::write: + f = O_RDWR | O_CREAT | O_TRUNC; + #ifndef __APPLE__ + advise = POSIX_FADV_RANDOM; + #endif + break; + + case file_mode::write_new: + f = O_RDWR | O_CREAT | O_EXCL; + #ifndef __APPLE__ + advise = POSIX_FADV_RANDOM; + #endif + break; + + case file_mode::write_existing: + f = O_RDWR | O_EXCL; + #ifndef __APPLE__ + advise = POSIX_FADV_RANDOM; + #endif + break; + + case file_mode::append: + f = O_RDWR | O_CREAT | O_TRUNC; + #ifndef __APPLE__ + advise = POSIX_FADV_SEQUENTIAL; + #endif + break; + + case file_mode::append_new: + f = O_RDWR | O_CREAT | O_EXCL; + #ifndef __APPLE__ + advise = POSIX_FADV_SEQUENTIAL; + #endif + break; + + case file_mode::append_existing: + f = O_RDWR | O_EXCL; + #ifndef __APPLE__ + advise = POSIX_FADV_SEQUENTIAL; + #endif + break; + } + for(;;) + { + fd_ = ::open(path, f, 0644); + if(fd_ != -1) + break; + auto const ev = errno; + if(ev != EINTR) + { + ec.assign(ev, generic_category()); + return; + } + } +#ifndef __APPLE__ + if(::posix_fadvise(fd_, 0, 0, advise)) + { + auto const ev = errno; + detail::file_posix_close(fd_); + fd_ = -1; + ec.assign(ev, generic_category()); + return; + } +#endif + ec.assign(0, ec.category()); +} + +inline +std::uint64_t +file_posix:: +size(error_code& ec) const +{ + if(fd_ == -1) + { + ec.assign(errc::invalid_argument, generic_category()); + return 0; + } + struct stat st; + if(::fstat(fd_, &st) != 0) + { + ec.assign(errno, generic_category()); + return 0; + } + ec.assign(0, ec.category()); + return st.st_size; +} + +inline +std::uint64_t +file_posix:: +pos(error_code& ec) const +{ + if(fd_ == -1) + { + ec.assign(errc::invalid_argument, generic_category()); + return 0; + } + auto const result = ::lseek(fd_, 0, SEEK_CUR); + if(result == (off_t)-1) + { + ec.assign(errno, generic_category()); + return 0; + } + ec.assign(0, ec.category()); + return result; +} + +inline +void +file_posix:: +seek(std::uint64_t offset, error_code& ec) +{ + if(fd_ == -1) + { + ec.assign(errc::invalid_argument, generic_category()); + return; + } + auto const result = ::lseek(fd_, offset, SEEK_SET); + if(result == static_cast(-1)) + { + ec.assign(errno, generic_category()); + return; + } + ec.assign(0, ec.category()); +} + +inline +std::size_t +file_posix:: +read(void* buffer, std::size_t n, error_code& ec) const +{ + if(fd_ == -1) + { + ec.assign(errc::invalid_argument, generic_category()); + return 0; + } + std::size_t nread = 0; + while(n > 0) + { + auto const amount = static_cast((std::min)( + n, static_cast(SSIZE_MAX))); + auto const result = ::read(fd_, buffer, amount); + if(result == -1) + { + auto const ev = errno; + if(ev == EINTR) + continue; + ec.assign(ev, generic_category()); + return nread; + } + if(result == 0) + { + // short read + return nread; + } + n -= result; + nread += result; + buffer = reinterpret_cast(buffer) + result; + } + return nread; +} + +inline +std::size_t +file_posix:: +write(void const* buffer, std::size_t n, error_code& ec) +{ + if(fd_ == -1) + { + ec.assign(errc::invalid_argument, generic_category()); + return 0; + } + std::size_t nwritten = 0; + while(n > 0) + { + auto const amount = static_cast((std::min)( + n, static_cast(SSIZE_MAX))); + auto const result = ::write(fd_, buffer, amount); + if(result == -1) + { + auto const ev = errno; + if(ev == EINTR) + continue; + ec.assign(ev, generic_category()); + return nwritten; + } + n -= result; + nwritten += result; + buffer = reinterpret_cast(buffer) + result; + } + return nwritten; +} + +} // beast + +#endif diff --git a/src/beast/include/beast/core/impl/file_stdio.ipp b/src/beast/include/beast/core/impl/file_stdio.ipp new file mode 100644 index 0000000000..1738e48daa --- /dev/null +++ b/src/beast/include/beast/core/impl/file_stdio.ipp @@ -0,0 +1,225 @@ +// +// Copyright (c) 2015-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_CORE_IMPL_FILE_STDIO_IPP +#define BEAST_CORE_IMPL_FILE_STDIO_IPP + +#include + +namespace beast { + +inline +file_stdio:: +~file_stdio() +{ + if(f_) + fclose(f_); +} + +inline +file_stdio:: +file_stdio(file_stdio&& other) + : f_(other.f_) +{ + other.f_ = nullptr; +} + +inline +file_stdio& +file_stdio:: +operator=(file_stdio&& other) +{ + if(&other == this) + return *this; + if(f_) + fclose(f_); + f_ = other.f_; + other.f_ = nullptr; + return *this; +} + +inline +void +file_stdio:: +native_handle(FILE* f) +{ + if(f_) + fclose(f_); + f_ = f; +} + +inline +void +file_stdio:: +close(error_code& ec) +{ + if(f_) + { + int failed = fclose(f_); + f_ = nullptr; + if(failed) + { + ec.assign(errno, generic_category()); + return; + } + } + ec.assign(0, ec.category()); +} + +inline +void +file_stdio:: +open(char const* path, file_mode mode, error_code& ec) +{ + if(f_) + { + fclose(f_); + f_ = nullptr; + } + char const* s; + switch(mode) + { + default: + case file_mode::read: s = "rb"; break; + case file_mode::scan: s = "rb"; break; + case file_mode::write: s = "wb"; break; + case file_mode::write_new: s = "wbx"; break; + case file_mode::write_existing: s = "wb"; break; + case file_mode::append: s = "ab"; break; + case file_mode::append_new: s = "abx"; break; + case file_mode::append_existing: s = "ab"; break; + } + f_ = std::fopen(path, s); + if(! f_) + { + ec.assign(errno, generic_category()); + return; + } + ec.assign(0, ec.category()); +} + +inline +std::uint64_t +file_stdio:: +size(error_code& ec) const +{ + if(! f_) + { + ec.assign(errc::invalid_argument, generic_category()); + return 0; + } + long pos = std::ftell(f_); + if(pos == -1L) + { + ec.assign(errno, generic_category()); + return 0; + } + int result = std::fseek(f_, 0, SEEK_END); + if(result != 0) + { + ec.assign(errno, generic_category()); + return 0; + } + long size = std::ftell(f_); + if(size == -1L) + { + ec.assign(errno, generic_category()); + std::fseek(f_, pos, SEEK_SET); + return 0; + } + result = std::fseek(f_, pos, SEEK_SET); + if(result != 0) + ec.assign(errno, generic_category()); + else + ec.assign(0, ec.category()); + return size; +} + +inline +std::uint64_t +file_stdio:: +pos(error_code& ec) const +{ + if(! f_) + { + ec.assign(errc::invalid_argument, generic_category()); + return 0; + } + long pos = std::ftell(f_); + if(pos == -1L) + { + ec.assign(errno, generic_category()); + return 0; + } + ec.assign(0, ec.category()); + return pos; +} + +inline +void +file_stdio:: +seek(std::uint64_t offset, error_code& ec) +{ + if(! f_) + { + ec.assign(errc::invalid_argument, generic_category()); + return; + } + if(offset > (std::numeric_limits::max)()) + { + ec = make_error_code(errc::invalid_seek); + return; + } + int result = std::fseek(f_, + static_cast(offset), SEEK_SET); + if(result != 0) + ec.assign(errno, generic_category()); + else + ec.assign(0, ec.category()); +} + +inline +std::size_t +file_stdio:: +read(void* buffer, std::size_t n, error_code& ec) const +{ + if(! f_) + { + ec.assign(errc::invalid_argument, generic_category()); + return 0; + } + auto nread = std::fread(buffer, 1, n, f_); + if(std::ferror(f_)) + { + ec.assign(errno, generic_category()); + return 0; + } + return nread; +} + +inline +std::size_t +file_stdio:: +write(void const* buffer, std::size_t n, error_code& ec) +{ + if(! f_) + { + ec.assign(errc::invalid_argument, generic_category()); + return 0; + } + auto nwritten = std::fwrite(buffer, 1, n, f_); + if(std::ferror(f_)) + { + ec.assign(errno, generic_category()); + return 0; + } + return nwritten; +} + +} // beast + +#endif diff --git a/src/beast/include/beast/core/impl/file_win32.ipp b/src/beast/include/beast/core/impl/file_win32.ipp new file mode 100644 index 0000000000..5b7a16470f --- /dev/null +++ b/src/beast/include/beast/core/impl/file_win32.ipp @@ -0,0 +1,356 @@ +// +// Copyright (c) 2015-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_CORE_IMPL_FILE_WIN32_IPP +#define BEAST_CORE_IMPL_FILE_WIN32_IPP + +#include +#include +#include +#include +#include +#include + +namespace beast { + +namespace detail { + +// VFALCO Can't seem to get boost/detail/winapi to work with +// this so use the non-Ex version for now. +inline +boost::detail::winapi::BOOL_ +set_file_pointer_ex( + boost::detail::winapi::HANDLE_ hFile, + boost::detail::winapi::LARGE_INTEGER_ lpDistanceToMove, + boost::detail::winapi::PLARGE_INTEGER_ lpNewFilePointer, + boost::detail::winapi::DWORD_ dwMoveMethod) +{ + auto dwHighPart = lpDistanceToMove.u.HighPart; + auto dwLowPart = boost::detail::winapi::SetFilePointer( + hFile, + lpDistanceToMove.u.LowPart, + &dwHighPart, + dwMoveMethod); + if(dwLowPart == boost::detail::winapi::INVALID_SET_FILE_POINTER_) + return 0; + if(lpNewFilePointer) + { + lpNewFilePointer->u.LowPart = dwLowPart; + lpNewFilePointer->u.HighPart = dwHighPart; + } + return 1; +} + +} // detail + +inline +file_win32:: +~file_win32() +{ + if(h_ != boost::detail::winapi::INVALID_HANDLE_VALUE_) + boost::detail::winapi::CloseHandle(h_); +} + +inline +file_win32:: +file_win32(file_win32&& other) + : h_(other.h_) +{ + other.h_ = boost::detail::winapi::INVALID_HANDLE_VALUE_; +} + +inline +file_win32& +file_win32:: +operator=(file_win32&& other) +{ + if(&other == this) + return *this; + if(h_) + boost::detail::winapi::CloseHandle(h_); + h_ = other.h_; + other.h_ = boost::detail::winapi::INVALID_HANDLE_VALUE_; + return *this; +} + +inline +void +file_win32:: +native_handle(native_handle_type h) +{ + if(h_ != boost::detail::winapi::INVALID_HANDLE_VALUE_) + boost::detail::winapi::CloseHandle(h_); + h_ = h; +} + +inline +void +file_win32:: +close(error_code& ec) +{ + if(h_ != boost::detail::winapi::INVALID_HANDLE_VALUE_) + { + if(! boost::detail::winapi::CloseHandle(h_)) + ec.assign(boost::detail::winapi::GetLastError(), + system_category()); + else + ec.assign(0, ec.category()); + h_ = boost::detail::winapi::INVALID_HANDLE_VALUE_; + } + else + { + ec.assign(0, ec.category()); + } +} + +inline +void +file_win32:: +open(char const* path, file_mode mode, error_code& ec) +{ + if(h_ != boost::detail::winapi::INVALID_HANDLE_VALUE_) + { + boost::detail::winapi::CloseHandle(h_); + h_ = boost::detail::winapi::INVALID_HANDLE_VALUE_; + } + boost::detail::winapi::DWORD_ dw1 = 0; + boost::detail::winapi::DWORD_ dw2 = 0; + boost::detail::winapi::DWORD_ dw3 = 0; +/* + | When the file... + This argument: | Exists Does not exist + -------------------------+------------------------------------------------------ + CREATE_ALWAYS | Truncates Creates + CREATE_NEW +-----------+ Fails Creates + OPEN_ALWAYS ===| does this |===> Opens Creates + OPEN_EXISTING +-----------+ Opens Fails + TRUNCATE_EXISTING | Truncates Fails +*/ + switch(mode) + { + default: + case file_mode::read: + dw1 = boost::detail::winapi::GENERIC_READ_; + dw2 = boost::detail::winapi::OPEN_EXISTING_; + dw3 = 0x10000000; // FILE_FLAG_RANDOM_ACCESS + break; + + case file_mode::scan: + dw1 = boost::detail::winapi::GENERIC_READ_; + dw2 = boost::detail::winapi::OPEN_EXISTING_; + dw3 = 0x08000000; // FILE_FLAG_SEQUENTIAL_SCAN + break; + + case file_mode::write: + dw1 = boost::detail::winapi::GENERIC_READ_ | + boost::detail::winapi::GENERIC_WRITE_; + dw2 = boost::detail::winapi::CREATE_ALWAYS_; + dw3 = 0x10000000; // FILE_FLAG_RANDOM_ACCESS + break; + + case file_mode::write_new: + dw1 = boost::detail::winapi::GENERIC_READ_ | + boost::detail::winapi::GENERIC_WRITE_; + dw2 = boost::detail::winapi::CREATE_NEW_; + dw3 = 0x10000000; // FILE_FLAG_RANDOM_ACCESS + break; + + case file_mode::write_existing: + dw1 = boost::detail::winapi::GENERIC_READ_ | + boost::detail::winapi::GENERIC_WRITE_; + dw2 = boost::detail::winapi::OPEN_EXISTING_; + dw3 = 0x10000000; // FILE_FLAG_RANDOM_ACCESS + break; + + case file_mode::append: + dw1 = boost::detail::winapi::GENERIC_READ_ | + boost::detail::winapi::GENERIC_WRITE_; + dw2 = boost::detail::winapi::CREATE_ALWAYS_; + dw3 = 0x08000000; // FILE_FLAG_SEQUENTIAL_SCAN + break; + + case file_mode::append_new: + dw1 = boost::detail::winapi::GENERIC_READ_ | + boost::detail::winapi::GENERIC_WRITE_; + dw2 = boost::detail::winapi::CREATE_NEW_; + dw3 = 0x08000000; // FILE_FLAG_SEQUENTIAL_SCAN + break; + + case file_mode::append_existing: + dw1 = boost::detail::winapi::GENERIC_READ_ | + boost::detail::winapi::GENERIC_WRITE_; + dw2 = boost::detail::winapi::OPEN_EXISTING_; + dw3 = 0x08000000; // FILE_FLAG_SEQUENTIAL_SCAN + break; + } + h_ = ::CreateFileA( + path, + dw1, + 0, + NULL, + dw2, + dw3, + NULL); + if(h_ == boost::detail::winapi::INVALID_HANDLE_VALUE_) + ec.assign(boost::detail::winapi::GetLastError(), + system_category()); + else + ec.assign(0, ec.category()); +} + +inline +std::uint64_t +file_win32:: +size(error_code& ec) const +{ + if(h_ == boost::detail::winapi::INVALID_HANDLE_VALUE_) + { + ec.assign(errc::invalid_argument, generic_category()); + return 0; + } + boost::detail::winapi::LARGE_INTEGER_ fileSize; + if(! boost::detail::winapi::GetFileSizeEx(h_, &fileSize)) + { + ec.assign(boost::detail::winapi::GetLastError(), + system_category()); + return 0; + } + ec.assign(0, ec.category()); + return fileSize.QuadPart; +} + +inline +std::uint64_t +file_win32:: +pos(error_code& ec) +{ + if(h_ == boost::detail::winapi::INVALID_HANDLE_VALUE_) + { + ec.assign(errc::invalid_argument, generic_category()); + return 0; + } + boost::detail::winapi::LARGE_INTEGER_ in; + boost::detail::winapi::LARGE_INTEGER_ out; + in.QuadPart = 0; + if(! detail::set_file_pointer_ex(h_, in, &out, + boost::detail::winapi::FILE_CURRENT_)) + { + ec.assign(boost::detail::winapi::GetLastError(), + system_category()); + return 0; + } + ec.assign(0, ec.category()); + return out.QuadPart; +} + +inline +void +file_win32:: +seek(std::uint64_t offset, error_code& ec) +{ + if(h_ == boost::detail::winapi::INVALID_HANDLE_VALUE_) + { + ec.assign(errc::invalid_argument, generic_category()); + return; + } + boost::detail::winapi::LARGE_INTEGER_ in; + in.QuadPart = offset; + if(! detail::set_file_pointer_ex(h_, in, 0, + boost::detail::winapi::FILE_BEGIN_)) + { + ec.assign(boost::detail::winapi::GetLastError(), + system_category()); + return; + } + ec.assign(0, ec.category()); +} + +inline +std::size_t +file_win32:: +read(void* buffer, std::size_t n, error_code& ec) +{ + if(h_ == boost::detail::winapi::INVALID_HANDLE_VALUE_) + { + ec.assign(errc::invalid_argument, generic_category()); + return 0; + } + std::size_t nread = 0; + while(n > 0) + { + boost::detail::winapi::DWORD_ amount; + if(n > (std::numeric_limits< + boost::detail::winapi::DWORD_>::max)()) + amount = (std::numeric_limits< + boost::detail::winapi::DWORD_>::max)(); + else + amount = static_cast< + boost::detail::winapi::DWORD_>(n); + boost::detail::winapi::DWORD_ bytesRead; + if(! ::ReadFile(h_, buffer, amount, &bytesRead, 0)) + { + auto const dwError = ::GetLastError(); + if(dwError != boost::detail::winapi::ERROR_HANDLE_EOF_) + ec.assign(::GetLastError(), system_category()); + else + ec.assign(0, ec.category()); + return nread; + } + if(bytesRead == 0) + return nread; + n -= bytesRead; + nread += bytesRead; + buffer = reinterpret_cast(buffer) + bytesRead; + } + ec.assign(0, ec.category()); + return nread; +} + +inline +std::size_t +file_win32:: +write(void const* buffer, std::size_t n, error_code& ec) +{ + if(h_ == boost::detail::winapi::INVALID_HANDLE_VALUE_) + { + ec.assign(errc::invalid_argument, generic_category()); + return 0; + } + std::size_t nwritten = 0; + while(n > 0) + { + boost::detail::winapi::DWORD_ amount; + if(n > (std::numeric_limits< + boost::detail::winapi::DWORD_>::max)()) + amount = (std::numeric_limits< + boost::detail::winapi::DWORD_>::max)(); + else + amount = static_cast< + boost::detail::winapi::DWORD_>(n); + boost::detail::winapi::DWORD_ bytesWritten; + if(! ::WriteFile(h_, buffer, amount, &bytesWritten, 0)) + { + auto const dwError = ::GetLastError(); + if(dwError != boost::detail::winapi::ERROR_HANDLE_EOF_) + ec.assign(::GetLastError(), system_category()); + else + ec.assign(0, ec.category()); + return nwritten; + } + if(bytesWritten == 0) + return nwritten; + n -= bytesWritten; + nwritten += bytesWritten; + buffer = reinterpret_cast(buffer) + bytesWritten; + } + ec.assign(0, ec.category()); + return nwritten; +} + +} // beast + +#endif diff --git a/src/beast/include/beast/core/impl/flat_buffer.ipp b/src/beast/include/beast/core/impl/flat_buffer.ipp new file mode 100644 index 0000000000..b7a938ed1d --- /dev/null +++ b/src/beast/include/beast/core/impl/flat_buffer.ipp @@ -0,0 +1,471 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_IMPL_FLAT_BUFFER_HPP +#define BEAST_IMPL_FLAT_BUFFER_HPP + +#include +#include +#include + +namespace beast { + +/* Memory is laid out thusly: + + begin_ ..|.. in_ ..|.. out_ ..|.. last_ ..|.. end_ +*/ + +template +basic_flat_buffer:: +~basic_flat_buffer() +{ + if(begin_) + alloc_traits::deallocate( + this->member(), begin_, dist(begin_, end_)); +} + +template +basic_flat_buffer:: +basic_flat_buffer() + : begin_(nullptr) + , in_(nullptr) + , out_(nullptr) + , last_(nullptr) + , end_(nullptr) + , max_((std::numeric_limits::max)()) +{ +} + +template +basic_flat_buffer:: +basic_flat_buffer(std::size_t limit) + : begin_(nullptr) + , in_(nullptr) + , out_(nullptr) + , last_(nullptr) + , end_(nullptr) + , max_(limit) +{ +} + +template +basic_flat_buffer:: +basic_flat_buffer(Allocator const& alloc) + : detail::empty_base_optimization(alloc) + , begin_(nullptr) + , in_(nullptr) + , out_(nullptr) + , last_(nullptr) + , end_(nullptr) + , max_((std::numeric_limits::max)()) +{ +} + +template +basic_flat_buffer:: +basic_flat_buffer(std::size_t limit, Allocator const& alloc) + : detail::empty_base_optimization(alloc) + , begin_(nullptr) + , in_(nullptr) + , out_(nullptr) + , last_(nullptr) + , end_(nullptr) + , max_(limit) +{ +} + +template +basic_flat_buffer:: +basic_flat_buffer(basic_flat_buffer&& other) + : detail::empty_base_optimization( + std::move(other.member())) + , begin_(other.begin_) + , in_(other.in_) + , out_(other.out_) + , last_(out_) + , end_(other.end_) + , max_(other.max_) +{ + other.begin_ = nullptr; + other.in_ = nullptr; + other.out_ = nullptr; + other.last_ = nullptr; + other.end_ = nullptr; +} + +template +basic_flat_buffer:: +basic_flat_buffer(basic_flat_buffer&& other, + Allocator const& alloc) + : detail::empty_base_optimization(alloc) +{ + if(this->member() != other.member()) + { + begin_ = nullptr; + in_ = nullptr; + out_ = nullptr; + last_ = nullptr; + end_ = nullptr; + max_ = other.max_; + copy_from(other); + other.reset(); + } + else + { + begin_ = other.begin_; + in_ = other.in_; + out_ = other.out_; + last_ = out_; + end_ = other.end_; + max_ = other.max_; + other.begin_ = nullptr; + other.in_ = nullptr; + other.out_ = nullptr; + other.last_ = nullptr; + other.end_ = nullptr; + } +} + +template +basic_flat_buffer:: +basic_flat_buffer(basic_flat_buffer const& other) + : detail::empty_base_optimization( + alloc_traits::select_on_container_copy_construction( + other.member())) + , begin_(nullptr) + , in_(nullptr) + , out_(nullptr) + , last_(nullptr) + , end_(nullptr) + , max_(other.max_) +{ + copy_from(other); +} + +template +basic_flat_buffer:: +basic_flat_buffer(basic_flat_buffer const& other, + Allocator const& alloc) + : detail::empty_base_optimization(alloc) + , begin_(nullptr) + , in_(nullptr) + , out_(nullptr) + , last_(nullptr) + , end_(nullptr) + , max_(other.max_) +{ + copy_from(other); +} + +template +template +basic_flat_buffer:: +basic_flat_buffer( + basic_flat_buffer const& other) + : begin_(nullptr) + , in_(nullptr) + , out_(nullptr) + , last_(nullptr) + , end_(nullptr) + , max_(other.max_) +{ + copy_from(other); +} + +template +template +basic_flat_buffer:: +basic_flat_buffer(basic_flat_buffer const& other, + Allocator const& alloc) + : detail::empty_base_optimization(alloc) + , begin_(nullptr) + , in_(nullptr) + , out_(nullptr) + , last_(nullptr) + , end_(nullptr) + , max_(other.max_) +{ + copy_from(other); +} + +template +auto +basic_flat_buffer:: +operator=(basic_flat_buffer&& other) -> + basic_flat_buffer& +{ + if(this != &other) + move_assign(other, + typename alloc_traits::propagate_on_container_move_assignment{}); + return *this; +} + +template +auto +basic_flat_buffer:: +operator=(basic_flat_buffer const& other) -> + basic_flat_buffer& +{ + if(this != &other) + copy_assign(other, + typename alloc_traits::propagate_on_container_copy_assignment{}); + return *this; +} + +template +template +auto +basic_flat_buffer:: +operator=(basic_flat_buffer const& other) -> + basic_flat_buffer& +{ + reset(); + max_ = other.max_; + copy_from(other); + return *this; +} + +//------------------------------------------------------------------------------ + +template +auto +basic_flat_buffer:: +prepare(std::size_t n) -> + mutable_buffers_type +{ + if(n <= dist(out_, end_)) + { + // existing capacity is sufficient + last_ = out_ + n; + return{out_, n}; + } + auto const len = size(); + if(n <= capacity() - len) + { + // after a memmove, + // existing capacity is sufficient + if(len > 0) + std::memmove(begin_, in_, len); + in_ = begin_; + out_ = in_ + len; + last_ = out_ + n; + return {out_, n}; + } + // enforce maximum capacity + if(n > max_ - len) + BOOST_THROW_EXCEPTION(std::length_error{ + "basic_flat_buffer overflow"}); + // allocate a new buffer + auto const new_size = std::min( + max_, + std::max(2 * len, len + n)); + auto const p = alloc_traits::allocate( + this->member(), new_size); + if(begin_) + { + BOOST_ASSERT(p); + BOOST_ASSERT(in_); + std::memcpy(p, in_, len); + alloc_traits::deallocate( + this->member(), begin_, capacity()); + } + begin_ = p; + in_ = begin_; + out_ = in_ + len; + last_ = out_ + n; + end_ = begin_ + new_size; + return {out_, n}; +} + +template +void +basic_flat_buffer:: +consume(std::size_t n) +{ + if(n >= dist(in_, out_)) + { + in_ = begin_; + out_ = begin_; + return; + } + in_ += n; +} + +template +void +basic_flat_buffer:: +shrink_to_fit() +{ + auto const len = size(); + if(len == capacity()) + return; + char* p; + if(len > 0) + { + BOOST_ASSERT(begin_); + BOOST_ASSERT(in_); + p = alloc_traits::allocate( + this->member(), len); + std::memcpy(p, in_, len); + } + else + { + p = nullptr; + } + alloc_traits::deallocate( + this->member(), begin_, dist(begin_, end_)); + begin_ = p; + in_ = begin_; + out_ = begin_ + len; + last_ = out_; + end_ = out_; +} + +//------------------------------------------------------------------------------ + +template +inline +void +basic_flat_buffer:: +reset() +{ + consume(size()); + shrink_to_fit(); +} + +template +template +inline +void +basic_flat_buffer:: +copy_from(DynamicBuffer const& buffer) +{ + if(buffer.size() == 0) + return; + using boost::asio::buffer_copy; + commit(buffer_copy( + prepare(buffer.size()), buffer.data())); +} + +template +inline +void +basic_flat_buffer:: +move_assign(basic_flat_buffer& other, std::true_type) +{ + reset(); + this->member() = std::move(other.member()); + begin_ = other.begin_; + in_ = other.in_; + out_ = other.out_; + last_ = out_; + end_ = other.end_; + max_ = other.max_; + other.begin_ = nullptr; + other.in_ = nullptr; + other.out_ = nullptr; + other.last_ = nullptr; + other.end_ = nullptr; +} + +template +inline +void +basic_flat_buffer:: +move_assign(basic_flat_buffer& other, std::false_type) +{ + reset(); + if(this->member() != other.member()) + { + copy_from(other); + other.reset(); + } + else + { + move_assign(other, std::true_type{}); + } +} + +template +inline +void +basic_flat_buffer:: +copy_assign(basic_flat_buffer const& other, std::true_type) +{ + reset(); + max_ = other.max_; + this->member() = other.member(); + copy_from(other); +} + +template +inline +void +basic_flat_buffer:: +copy_assign(basic_flat_buffer const& other, std::false_type) +{ + reset(); + max_ = other.max_; + copy_from(other); +} + +template +inline +void +basic_flat_buffer:: +swap(basic_flat_buffer& other) +{ + swap(other, typename + alloc_traits::propagate_on_container_swap{}); +} + +template +inline +void +basic_flat_buffer:: +swap(basic_flat_buffer& other, std::true_type) +{ + using std::swap; + swap(this->member(), other.member()); + swap(max_, other.max_); + swap(begin_, other.begin_); + swap(in_, other.in_); + swap(out_, other.out_); + last_ = this->out_; + other.last_ = other.out_; + swap(end_, other.end_); +} + +template +inline +void +basic_flat_buffer:: +swap(basic_flat_buffer& other, std::false_type) +{ + BOOST_ASSERT(this->member() == other.member()); + using std::swap; + swap(max_, other.max_); + swap(begin_, other.begin_); + swap(in_, other.in_); + swap(out_, other.out_); + last_ = this->out_; + other.last_ = other.out_; + swap(end_, other.end_); +} + +template +void +swap( + basic_flat_buffer& lhs, + basic_flat_buffer& rhs) +{ + lhs.swap(rhs); +} + +} // beast + +#endif diff --git a/src/beast/include/beast/core/impl/handler_ptr.ipp b/src/beast/include/beast/core/impl/handler_ptr.ipp index 2243d5df34..a882acf9db 100644 --- a/src/beast/include/beast/core/impl/handler_ptr.ipp +++ b/src/beast/include/beast/core/impl/handler_ptr.ipp @@ -8,8 +8,9 @@ #ifndef BEAST_IMPL_HANDLER_PTR_HPP #define BEAST_IMPL_HANDLER_PTR_HPP -#include -#include +#include +#include +#include #include #include @@ -23,9 +24,10 @@ P(DeducedHandler&& h, Args&&... args) : n(1) , handler(std::forward(h)) { + using boost::asio::asio_handler_allocate; t = reinterpret_cast( - beast_asio_helpers:: - allocate(sizeof(T), handler)); + asio_handler_allocate( + sizeof(T), std::addressof(handler))); try { t = new(t) T{handler, @@ -33,8 +35,9 @@ P(DeducedHandler&& h, Args&&... args) } catch(...) { - beast_asio_helpers:: - deallocate(t, sizeof(T), handler); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + t, sizeof(T), std::addressof(handler)); throw; } } @@ -50,8 +53,9 @@ handler_ptr:: if(p_->t) { p_->t->~T(); - beast_asio_helpers:: - deallocate(p_->t, sizeof(T), p_->handler); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p_->t, sizeof(T), std::addressof(p_->handler)); } delete p_; } @@ -80,8 +84,7 @@ handler_ptr(Handler&& handler, Args&&... args) : p_(new P{std::move(handler), std::forward(args)...}) { - static_assert(! std::is_array::value, - "T must not be an array type"); + BOOST_STATIC_ASSERT(! std::is_array::value); } template @@ -90,8 +93,7 @@ handler_ptr:: handler_ptr(Handler const& handler, Args&&... args) : p_(new P{handler, std::forward(args)...}) { - static_assert(! std::is_array::value, - "T must not be an array type"); + BOOST_STATIC_ASSERT(! std::is_array::value); } template @@ -103,8 +105,9 @@ release_handler() -> BOOST_ASSERT(p_); BOOST_ASSERT(p_->t); p_->t->~T(); - beast_asio_helpers:: - deallocate(p_->t, sizeof(T), p_->handler); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p_->t, sizeof(T), std::addressof(p_->handler)); p_->t = nullptr; return std::move(p_->handler); } @@ -118,8 +121,9 @@ invoke(Args&&... args) BOOST_ASSERT(p_); BOOST_ASSERT(p_->t); p_->t->~T(); - beast_asio_helpers:: - deallocate(p_->t, sizeof(T), p_->handler); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p_->t, sizeof(T), std::addressof(p_->handler)); p_->t = nullptr; p_->handler(std::forward(args)...); } diff --git a/src/beast/include/beast/core/impl/streambuf.ipp b/src/beast/include/beast/core/impl/multi_buffer.ipp similarity index 60% rename from src/beast/include/beast/core/impl/streambuf.ipp rename to src/beast/include/beast/core/impl/multi_buffer.ipp index ea39d76389..aed523a43c 100644 --- a/src/beast/include/beast/core/impl/streambuf.ipp +++ b/src/beast/include/beast/core/impl/multi_buffer.ipp @@ -5,12 +5,12 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef BEAST_IMPL_STREAMBUF_IPP -#define BEAST_IMPL_STREAMBUF_IPP +#ifndef BEAST_IMPL_MULTI_BUFFER_IPP +#define BEAST_IMPL_MULTI_BUFFER_IPP #include -#include #include +#include #include #include #include @@ -84,7 +84,7 @@ namespace beast { */ template -class basic_streambuf::element +class basic_multi_buffer::element : public boost::intrusive::list_base_hook< boost::intrusive::link_mode< boost::intrusive::normal_link>> @@ -118,14 +118,14 @@ public: }; template -class basic_streambuf::const_buffers_type +class basic_multi_buffer::const_buffers_type { - basic_streambuf const* sb_; + basic_multi_buffer const* b_; - friend class basic_streambuf; + friend class basic_multi_buffer; explicit - const_buffers_type(basic_streambuf const& sb); + const_buffers_type(basic_multi_buffer const& b); public: // Why? @@ -142,17 +142,24 @@ public: const_iterator end() const; + + friend + std::size_t + buffer_size(const_buffers_type const& buffers) + { + return buffers.b_->size(); + } }; template -class basic_streambuf::mutable_buffers_type +class basic_multi_buffer::mutable_buffers_type { - basic_streambuf const* sb_; + basic_multi_buffer const* b_; - friend class basic_streambuf; + friend class basic_multi_buffer; explicit - mutable_buffers_type(basic_streambuf const& sb); + mutable_buffers_type(basic_multi_buffer const& b); public: using value_type = mutable_buffer; @@ -173,9 +180,9 @@ public: //------------------------------------------------------------------------------ template -class basic_streambuf::const_buffers_type::const_iterator +class basic_multi_buffer::const_buffers_type::const_iterator { - basic_streambuf const* sb_ = nullptr; + basic_multi_buffer const* b_ = nullptr; typename list_type::const_iterator it_; public: @@ -193,9 +200,9 @@ public: const_iterator& operator=(const_iterator&& other) = default; const_iterator& operator=(const_iterator const& other) = default; - const_iterator(basic_streambuf const& sb, + const_iterator(basic_multi_buffer const& b, typename list_type::const_iterator const& it) - : sb_(&sb) + : b_(&b) , it_(it) { } @@ -203,7 +210,7 @@ public: bool operator==(const_iterator const& other) const { - return sb_ == other.sb_ && it_ == other.it_; + return b_ == other.b_ && it_ == other.it_; } bool @@ -217,9 +224,9 @@ public: { auto const& e = *it_; return value_type{e.data(), - (sb_->out_ == sb_->list_.end() || - &e != &*sb_->out_) ? e.size() : sb_->out_pos_} + - (&e == &*sb_->list_.begin() ? sb_->in_pos_ : 0); + (b_->out_ == b_->list_.end() || + &e != &*b_->out_) ? e.size() : b_->out_pos_} + + (&e == &*b_->list_.begin() ? b_->in_pos_ : 0); } pointer @@ -257,36 +264,42 @@ public: }; template -basic_streambuf::const_buffers_type::const_buffers_type( - basic_streambuf const& sb) - : sb_(&sb) +basic_multi_buffer:: +const_buffers_type:: +const_buffers_type( + basic_multi_buffer const& b) + : b_(&b) { } template auto -basic_streambuf::const_buffers_type::begin() const -> +basic_multi_buffer:: +const_buffers_type:: +begin() const -> const_iterator { - return const_iterator{*sb_, sb_->list_.begin()}; + return const_iterator{*b_, b_->list_.begin()}; } template auto -basic_streambuf::const_buffers_type::end() const -> +basic_multi_buffer:: +const_buffers_type:: +end() const -> const_iterator { - return const_iterator{*sb_, sb_->out_ == - sb_->list_.end() ? sb_->list_.end() : - std::next(sb_->out_)}; + return const_iterator{*b_, b_->out_ == + b_->list_.end() ? b_->list_.end() : + std::next(b_->out_)}; } //------------------------------------------------------------------------------ template -class basic_streambuf::mutable_buffers_type::const_iterator +class basic_multi_buffer::mutable_buffers_type::const_iterator { - basic_streambuf const* sb_ = nullptr; + basic_multi_buffer const* b_ = nullptr; typename list_type::const_iterator it_; public: @@ -304,9 +317,9 @@ public: const_iterator& operator=(const_iterator&& other) = default; const_iterator& operator=(const_iterator const& other) = default; - const_iterator(basic_streambuf const& sb, + const_iterator(basic_multi_buffer const& b, typename list_type::const_iterator const& it) - : sb_(&sb) + : b_(&b) , it_(it) { } @@ -314,7 +327,7 @@ public: bool operator==(const_iterator const& other) const { - return sb_ == other.sb_ && it_ == other.it_; + return b_ == other.b_ && it_ == other.it_; } bool @@ -328,9 +341,9 @@ public: { auto const& e = *it_; return value_type{e.data(), - &e == &*std::prev(sb_->list_.end()) ? - sb_->out_end_ : e.size()} + - (&e == &*sb_->out_ ? sb_->out_pos_ : 0); + &e == &*std::prev(b_->list_.end()) ? + b_->out_end_ : e.size()} + + (&e == &*b_->out_ ? b_->out_pos_ : 0); } pointer @@ -368,42 +381,84 @@ public: }; template -basic_streambuf::mutable_buffers_type::mutable_buffers_type( - basic_streambuf const& sb) - : sb_(&sb) +basic_multi_buffer:: +mutable_buffers_type:: +mutable_buffers_type( + basic_multi_buffer const& b) + : b_(&b) { } template auto -basic_streambuf::mutable_buffers_type::begin() const -> +basic_multi_buffer:: +mutable_buffers_type:: +begin() const -> const_iterator { - return const_iterator{*sb_, sb_->out_}; + return const_iterator{*b_, b_->out_}; } template auto -basic_streambuf::mutable_buffers_type::end() const -> +basic_multi_buffer:: +mutable_buffers_type:: +end() const -> const_iterator { - return const_iterator{*sb_, sb_->list_.end()}; + return const_iterator{*b_, b_->list_.end()}; } //------------------------------------------------------------------------------ template -basic_streambuf::~basic_streambuf() +basic_multi_buffer:: +~basic_multi_buffer() { delete_list(); } template -basic_streambuf:: -basic_streambuf(basic_streambuf&& other) +basic_multi_buffer:: +basic_multi_buffer() + : out_(list_.end()) +{ +} + +template +basic_multi_buffer:: +basic_multi_buffer(std::size_t limit) + : max_(limit) + , out_(list_.end()) +{ +} + +template +basic_multi_buffer:: +basic_multi_buffer(Allocator const& alloc) + : detail::empty_base_optimization< + allocator_type>(alloc) + , out_(list_.end()) +{ +} + +template +basic_multi_buffer:: +basic_multi_buffer(std::size_t limit, + Allocator const& alloc) + : detail::empty_base_optimization< + allocator_type>(alloc) + , max_(limit) + , out_(list_.end()) +{ +} + +template +basic_multi_buffer:: +basic_multi_buffer(basic_multi_buffer&& other) : detail::empty_base_optimization( std::move(other.member())) - , alloc_size_(other.alloc_size_) + , max_(other.max_) , in_size_(other.in_size_) , in_pos_(other.in_pos_) , out_pos_(other.out_pos_) @@ -421,116 +476,127 @@ basic_streambuf(basic_streambuf&& other) } template -basic_streambuf:: -basic_streambuf(basic_streambuf&& other, - allocator_type const& alloc) - : basic_streambuf(other.alloc_size_, alloc) -{ - using boost::asio::buffer_copy; - if(this->member() != other.member()) - commit(buffer_copy(prepare(other.size()), other.data())); - else - move_assign(other, std::true_type{}); -} - -template -auto -basic_streambuf::operator=( - basic_streambuf&& other) -> basic_streambuf& -{ - if(this == &other) - return *this; - // VFALCO If any memory allocated we could use it first? - clear(); - alloc_size_ = other.alloc_size_; - move_assign(other, std::integral_constant{}); - return *this; -} - -template -basic_streambuf:: -basic_streambuf(basic_streambuf const& other) - : basic_streambuf(other.alloc_size_, - alloc_traits::select_on_container_copy_construction(other.member())) -{ - commit(boost::asio::buffer_copy(prepare(other.size()), other.data())); -} - -template -basic_streambuf:: -basic_streambuf(basic_streambuf const& other, - allocator_type const& alloc) - : basic_streambuf(other.alloc_size_, alloc) -{ - commit(boost::asio::buffer_copy(prepare(other.size()), other.data())); -} - -template -auto -basic_streambuf::operator=( - basic_streambuf const& other) -> - basic_streambuf& -{ - if(this == &other) - return *this; - using boost::asio::buffer_copy; - clear(); - copy_assign(other, std::integral_constant{}); - commit(buffer_copy(prepare(other.size()), other.data())); - return *this; -} - -template -template -basic_streambuf::basic_streambuf( - basic_streambuf const& other) - : basic_streambuf(other.alloc_size_) -{ - using boost::asio::buffer_copy; - commit(buffer_copy(prepare(other.size()), other.data())); -} - -template -template -basic_streambuf::basic_streambuf( - basic_streambuf const& other, - allocator_type const& alloc) - : basic_streambuf(other.alloc_size_, alloc) -{ - using boost::asio::buffer_copy; - commit(buffer_copy(prepare(other.size()), other.data())); -} - -template -template -auto -basic_streambuf::operator=( - basic_streambuf const& other) -> - basic_streambuf& -{ - using boost::asio::buffer_copy; - clear(); - commit(buffer_copy(prepare(other.size()), other.data())); - return *this; -} - -template -basic_streambuf::basic_streambuf( - std::size_t alloc_size, Allocator const& alloc) +basic_multi_buffer:: +basic_multi_buffer(basic_multi_buffer&& other, + Allocator const& alloc) : detail::empty_base_optimization(alloc) - , out_(list_.end()) - , alloc_size_(alloc_size) + , max_(other.max_) { - if(alloc_size <= 0) - throw detail::make_exception( - "invalid alloc_size", __FILE__, __LINE__); + if(this->member() != other.member()) + { + out_ = list_.end(); + copy_from(other); + other.reset(); + } + else + { + auto const at_end = + other.out_ == other.list_.end(); + list_ = std::move(other.list_); + out_ = at_end ? list_.end() : other.out_; + in_size_ = other.in_size_; + in_pos_ = other.in_pos_; + out_pos_ = other.out_pos_; + out_end_ = other.out_end_; + other.in_size_ = 0; + other.out_ = other.list_.end(); + other.in_pos_ = 0; + other.out_pos_ = 0; + other.out_end_ = 0; + } +} + +template +basic_multi_buffer:: +basic_multi_buffer(basic_multi_buffer const& other) + : detail::empty_base_optimization( + alloc_traits::select_on_container_copy_construction(other.member())) + , max_(other.max_) + , out_(list_.end()) +{ + copy_from(other); +} + +template +basic_multi_buffer:: +basic_multi_buffer(basic_multi_buffer const& other, + Allocator const& alloc) + : detail::empty_base_optimization(alloc) + , max_(other.max_) + , out_(list_.end()) +{ + copy_from(other); +} + +template +template +basic_multi_buffer:: +basic_multi_buffer( + basic_multi_buffer const& other) + : out_(list_.end()) +{ + copy_from(other); +} + +template +template +basic_multi_buffer:: +basic_multi_buffer( + basic_multi_buffer const& other, + allocator_type const& alloc) + : detail::empty_base_optimization(alloc) + , max_(other.max_) + , out_(list_.end()) +{ + copy_from(other); +} + +template +auto +basic_multi_buffer:: +operator=(basic_multi_buffer&& other) -> + basic_multi_buffer& +{ + if(this == &other) + return *this; + reset(); + max_ = other.max_; + move_assign(other, typename + alloc_traits::propagate_on_container_move_assignment{}); + return *this; +} + +template +auto +basic_multi_buffer:: +operator=(basic_multi_buffer const& other) -> +basic_multi_buffer& +{ + if(this == &other) + return *this; + copy_assign(other, typename + alloc_traits::propagate_on_container_copy_assignment{}); + return *this; +} + +template +template +auto +basic_multi_buffer:: +operator=( + basic_multi_buffer const& other) -> + basic_multi_buffer& +{ + reset(); + max_ = other.max_; + copy_from(other); + return *this; } template std::size_t -basic_streambuf::capacity() const +basic_multi_buffer:: +capacity() const { auto pos = out_; if(pos == list_.end()) @@ -543,7 +609,7 @@ basic_streambuf::capacity() const template auto -basic_streambuf:: +basic_multi_buffer:: data() const -> const_buffers_type { @@ -552,18 +618,27 @@ data() const -> template auto -basic_streambuf::prepare(size_type n) -> +basic_multi_buffer:: +prepare(size_type n) -> mutable_buffers_type { + if(in_size_ + n > max_) + BOOST_THROW_EXCEPTION(std::length_error{ + "dynamic buffer overflow"}); list_type reuse; + std::size_t total = in_size_; + // put all empty buffers on reuse list if(out_ != list_.end()) { + total += out_->size() - out_pos_; if(out_ != list_.iterator_to(list_.back())) { out_end_ = out_->size(); reuse.splice(reuse.end(), list_, std::next(out_), list_.end()); + #if BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); + #endif } auto const avail = out_->size() - out_pos_; if(n > avail) @@ -576,13 +651,17 @@ basic_streambuf::prepare(size_type n) -> out_end_ = out_pos_ + n; n = 0; } + #if BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); + #endif } + // get space from reuse buffers while(n > 0 && ! reuse.empty()) { auto& e = reuse.front(); reuse.erase(reuse.iterator_to(e)); list_.push_back(e); + total += e.size(); if(n > e.size()) { out_end_ = e.size(); @@ -593,11 +672,28 @@ basic_streambuf::prepare(size_type n) -> out_end_ = n; n = 0; } + #if BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); + #endif } - while(n > 0) + BOOST_ASSERT(total <= max_); + for(auto it = reuse.begin(); it != reuse.end();) { - auto const size = std::max(alloc_size_, n); + auto& e = *it++; + reuse.erase(list_.iterator_to(e)); + delete_element(e); + } + if(n > 0) + { + static auto const growth_factor = 2.0f; + auto const size = + std::min( + max_ - total, + std::max({ + static_cast( + in_size_ * growth_factor - in_size_), + 512, + n})); auto& e = *reinterpret_cast(static_cast< void*>(alloc_traits::allocate(this->member(), sizeof(element) + size))); @@ -605,33 +701,18 @@ basic_streambuf::prepare(size_type n) -> list_.push_back(e); if(out_ == list_.end()) out_ = list_.iterator_to(e); - if(n >= e.size()) - { - out_end_ = e.size(); - n -= e.size(); - } - else - { - out_end_ = n; - n = 0; - } + out_end_ = n; + #if BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); - } - for(auto it = reuse.begin(); it != reuse.end();) - { - auto& e = *it++; - reuse.erase(list_.iterator_to(e)); - auto const len = e.size() + sizeof(e); - alloc_traits::destroy(this->member(), &e); - alloc_traits::deallocate(this->member(), - reinterpret_cast(&e), len); + #endif } return mutable_buffers_type(*this); } template void -basic_streambuf::commit(size_type n) +basic_multi_buffer:: +commit(size_type n) { if(list_.empty()) return; @@ -647,14 +728,18 @@ basic_streambuf::commit(size_type n) { out_pos_ += n; in_size_ += n; + #if BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); + #endif return; } ++out_; n -= avail; out_pos_ = 0; in_size_ += avail; + #if BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); + #endif } n = (std::min)(n, out_end_ - out_pos_); @@ -666,26 +751,31 @@ basic_streambuf::commit(size_type n) out_pos_ = 0; out_end_ = 0; } +#if BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); +#endif } template void -basic_streambuf::consume(size_type n) +basic_multi_buffer:: +consume(size_type n) { if(list_.empty()) return; - for(;;) { if(list_.begin() != out_) { - auto const avail = list_.front().size() - in_pos_; + auto const avail = + list_.front().size() - in_pos_; if(n < avail) { in_size_ -= n; in_pos_ += n; + #if BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); + #endif break; } n -= avail; @@ -693,11 +783,10 @@ basic_streambuf::consume(size_type n) in_pos_ = 0; auto& e = list_.front(); list_.erase(list_.iterator_to(e)); - auto const len = e.size() + sizeof(e); - alloc_traits::destroy(this->member(), &e); - alloc_traits::deallocate(this->member(), - reinterpret_cast(&e), len); + delete_element(e); + #if BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); + #endif } else { @@ -724,20 +813,45 @@ basic_streambuf::consume(size_type n) out_end_ = 0; } } + #if BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); + #endif break; } } } template +inline void -basic_streambuf:: -clear() +basic_multi_buffer:: +delete_element(element& e) +{ + auto const len = sizeof(e) + e.size(); + alloc_traits::destroy(this->member(), &e); + alloc_traits::deallocate(this->member(), + reinterpret_cast(&e), len); +} + +template +inline +void +basic_multi_buffer:: +delete_list() +{ + for(auto iter = list_.begin(); iter != list_.end();) + delete_element(*iter++); +} + +template +inline +void +basic_multi_buffer:: +reset() { delete_list(); list_.clear(); - out_ = list_.begin(); + out_ = list_.end(); in_size_ = 0; in_pos_ = 0; out_pos_ = 0; @@ -745,24 +859,41 @@ clear() } template +template +inline void -basic_streambuf:: -move_assign(basic_streambuf& other, std::false_type) +basic_multi_buffer:: +copy_from(DynamicBuffer const& buffer) { + if(buffer.size() == 0) + return; using boost::asio::buffer_copy; - if(this->member() != other.member()) - { - commit(buffer_copy(prepare(other.size()), other.data())); - other.clear(); - } - else - move_assign(other, std::true_type{}); + commit(buffer_copy( + prepare(buffer.size()), buffer.data())); } template +inline void -basic_streambuf:: -move_assign(basic_streambuf& other, std::true_type) +basic_multi_buffer:: +move_assign(basic_multi_buffer& other, std::false_type) +{ + if(this->member() != other.member()) + { + copy_from(other); + other.reset(); + } + else + { + move_assign(other, std::true_type{}); + } +} + +template +inline +void +basic_multi_buffer:: +move_assign(basic_multi_buffer& other, std::true_type) { this->member() = std::move(other.member()); auto const at_end = @@ -783,38 +914,101 @@ move_assign(basic_streambuf& other, std::true_type) } template +inline void -basic_streambuf:: -copy_assign(basic_streambuf const& other, std::false_type) +basic_multi_buffer:: +copy_assign( + basic_multi_buffer const& other, std::false_type) { - beast::detail::ignore_unused(other); + reset(); + max_ = other.max_; + copy_from(other); } template +inline void -basic_streambuf:: -copy_assign(basic_streambuf const& other, std::true_type) +basic_multi_buffer:: +copy_assign( + basic_multi_buffer const& other, std::true_type) { + reset(); + max_ = other.max_; this->member() = other.member(); + copy_from(other); } template +inline void -basic_streambuf::delete_list() +basic_multi_buffer:: +swap(basic_multi_buffer& other) { - for(auto iter = list_.begin(); iter != list_.end();) - { - auto& e = *iter++; - auto const n = e.size() + sizeof(e); - alloc_traits::destroy(this->member(), &e); - alloc_traits::deallocate(this->member(), - reinterpret_cast(&e), n); - } + swap(other, typename + alloc_traits::propagate_on_container_swap{}); +} + +template +inline +void +basic_multi_buffer:: +swap(basic_multi_buffer& other, std::true_type) +{ + using std::swap; + auto const at_end0 = + out_ == list_.end(); + auto const at_end1 = + other.out_ == other.list_.end(); + swap(this->member(), other.member()); + swap(list_, other.list_); + swap(out_, other.out_); + if(at_end1) + out_ = list_.end(); + if(at_end0) + other.out_ = other.list_.end(); + swap(in_size_, other.in_size_); + swap(in_pos_, other.in_pos_); + swap(out_pos_, other.out_pos_); + swap(out_end_, other.out_end_); +} + +template +inline +void +basic_multi_buffer:: +swap(basic_multi_buffer& other, std::false_type) +{ + BOOST_ASSERT(this->member() == other.member()); + using std::swap; + auto const at_end0 = + out_ == list_.end(); + auto const at_end1 = + other.out_ == other.list_.end(); + swap(list_, other.list_); + swap(out_, other.out_); + if(at_end1) + out_ = list_.end(); + if(at_end0) + other.out_ = other.list_.end(); + swap(in_size_, other.in_size_); + swap(in_pos_, other.in_pos_); + swap(out_pos_, other.out_pos_); + swap(out_end_, other.out_end_); } template void -basic_streambuf::debug_check() const +swap( + basic_multi_buffer& lhs, + basic_multi_buffer& rhs) +{ + lhs.swap(rhs); +} + +template +void +basic_multi_buffer:: +debug_check() const { #ifndef NDEBUG using boost::asio::buffer_size; @@ -852,33 +1046,6 @@ basic_streambuf::debug_check() const #endif } -template -std::size_t -read_size_helper(basic_streambuf< - Allocator> const& streambuf, std::size_t max_size) -{ - BOOST_ASSERT(max_size >= 1); - // If we already have an allocated - // buffer, try to fill that up first - auto const avail = streambuf.capacity() - streambuf.size(); - if (avail > 0) - return (std::min)(avail, max_size); - // Try to have just one new block allocated - constexpr std::size_t low = 512; - if (streambuf.alloc_size_ > low) - return (std::min)(max_size, streambuf.alloc_size_); - // ...but enforce a 512 byte minimum. - return (std::min)(max_size, low); -} - -template -basic_streambuf& -operator<<(basic_streambuf& streambuf, T const& t) -{ - detail::write_dynabuf(streambuf, t); - return streambuf; -} - } // beast #endif diff --git a/src/beast/include/beast/core/impl/read_size.ipp b/src/beast/include/beast/core/impl/read_size.ipp new file mode 100644 index 0000000000..56bc81c758 --- /dev/null +++ b/src/beast/include/beast/core/impl/read_size.ipp @@ -0,0 +1,75 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_IMPL_READ_SIZE_IPP +#define BEAST_IMPL_READ_SIZE_IPP + +namespace beast { + +namespace detail { + +template +struct has_read_size_helper : std::false_type {}; + +template +struct has_read_size_helper(), 512), + (void)0)> : std::true_type +{ +}; + +template +std::size_t +read_size(DynamicBuffer& buffer, + std::size_t max_size, std::true_type) +{ + return read_size_helper(buffer, max_size); +} + +template +std::size_t +read_size(DynamicBuffer& buffer, + std::size_t max_size, std::false_type) +{ + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + BOOST_ASSERT(max_size >= 1); + auto const size = buffer.size(); + auto const limit = buffer.max_size() - size; + BOOST_ASSERT(size <= buffer.max_size()); + return std::min( + std::max(512, buffer.capacity() - size), + std::min(max_size, limit)); +} + +} // detail + +template +inline +std::size_t +read_size( + DynamicBuffer& buffer, std::size_t max_size) +{ + return detail::read_size(buffer, max_size, + detail::has_read_size_helper{}); +} + +template +std::size_t +read_size_or_throw( + DynamicBuffer& buffer, std::size_t max_size) +{ + auto const n = read_size(buffer, max_size); + if(n == 0) + BOOST_THROW_EXCEPTION(std::length_error{ + "buffer overflow"}); + return n; +} + +} // beast + +#endif diff --git a/src/beast/include/beast/core/impl/static_buffer.ipp b/src/beast/include/beast/core/impl/static_buffer.ipp new file mode 100644 index 0000000000..13652ca56f --- /dev/null +++ b/src/beast/include/beast/core/impl/static_buffer.ipp @@ -0,0 +1,129 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_IMPL_STATIC_BUFFER_IPP +#define BEAST_IMPL_STATIC_BUFFER_IPP + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { + +/* Memory is laid out thusly: + + begin_ ..|.. in_ ..|.. out_ ..|.. last_ ..|.. end_ +*/ + +inline +auto +static_buffer:: +data() const -> + const_buffers_type +{ + return {in_, dist(in_, out_)}; +} + +inline +auto +static_buffer:: +prepare(std::size_t n) -> + mutable_buffers_type +{ + return prepare_impl(n); +} + +inline +void +static_buffer:: +reset(void* p, std::size_t n) +{ + reset_impl(p, n); +} + +template +void +static_buffer:: +reset_impl(void* p, std::size_t n) +{ + begin_ = + reinterpret_cast(p); + in_ = begin_; + out_ = begin_; + last_ = begin_; + end_ = begin_ + n; +} + +template +auto +static_buffer:: +prepare_impl(std::size_t n) -> + mutable_buffers_type +{ + if(n <= dist(out_, end_)) + { + last_ = out_ + n; + return {out_, n}; + } + auto const len = size(); + if(n > capacity() - len) + BOOST_THROW_EXCEPTION(std::length_error{ + "buffer overflow"}); + if(len > 0) + std::memmove(begin_, in_, len); + in_ = begin_; + out_ = in_ + len; + last_ = out_ + n; + return {out_, n}; +} + +template +void +static_buffer:: +consume_impl(std::size_t n) +{ + if(n >= size()) + { + in_ = begin_; + out_ = in_; + return; + } + in_ += n; +} + +//------------------------------------------------------------------------------ + +template +static_buffer_n:: +static_buffer_n(static_buffer_n const& other) + : static_buffer(buf_, N) +{ + using boost::asio::buffer_copy; + this->commit(buffer_copy( + this->prepare(other.size()), other.data())); +} + +template +auto +static_buffer_n:: +operator=(static_buffer_n const& other) -> + static_buffer_n& +{ + using boost::asio::buffer_copy; + this->consume(this->size()); + this->commit(buffer_copy( + this->prepare(other.size()), other.data())); + return *this; +} + +} // beast + +#endif diff --git a/src/beast/include/beast/core/impl/static_streambuf.ipp b/src/beast/include/beast/core/impl/static_streambuf.ipp deleted file mode 100644 index 90a4834625..0000000000 --- a/src/beast/include/beast/core/impl/static_streambuf.ipp +++ /dev/null @@ -1,307 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_IMPL_STATIC_STREAMBUF_IPP -#define BEAST_IMPL_STATIC_STREAMBUF_IPP - -#include -#include -#include -#include -#include -#include - -namespace beast { - -class static_streambuf::const_buffers_type -{ - std::size_t n_; - std::uint8_t const* p_; - -public: - using value_type = boost::asio::const_buffer; - - class const_iterator; - - const_buffers_type() = delete; - const_buffers_type( - const_buffers_type const&) = default; - const_buffers_type& operator=( - const_buffers_type const&) = default; - - const_iterator - begin() const; - - const_iterator - end() const; - -private: - friend class static_streambuf; - - const_buffers_type( - std::uint8_t const* p, std::size_t n) - : n_(n) - , p_(p) - { - } -}; - -class static_streambuf::const_buffers_type::const_iterator -{ - std::size_t n_ = 0; - std::uint8_t const* p_ = nullptr; - -public: - using value_type = boost::asio::const_buffer; - using pointer = value_type const*; - using reference = value_type; - using difference_type = std::ptrdiff_t; - using iterator_category = - std::bidirectional_iterator_tag; - - const_iterator() = default; - const_iterator(const_iterator&& other) = default; - const_iterator(const_iterator const& other) = default; - const_iterator& operator=(const_iterator&& other) = default; - const_iterator& operator=(const_iterator const& other) = default; - - bool - operator==(const_iterator const& other) const - { - return p_ == other.p_; - } - - bool - operator!=(const_iterator const& other) const - { - return !(*this == other); - } - - reference - operator*() const - { - return value_type{p_, n_}; - } - - pointer - operator->() const = delete; - - const_iterator& - operator++() - { - p_ += n_; - return *this; - } - - const_iterator - operator++(int) - { - auto temp = *this; - ++(*this); - return temp; - } - - const_iterator& - operator--() - { - p_ -= n_; - return *this; - } - - const_iterator - operator--(int) - { - auto temp = *this; - --(*this); - return temp; - } - -private: - friend class const_buffers_type; - - const_iterator( - std::uint8_t const* p, std::size_t n) - : n_(n) - , p_(p) - { - } -}; - -inline -auto -static_streambuf::const_buffers_type::begin() const -> - const_iterator -{ - return const_iterator{p_, n_}; -} - -inline -auto -static_streambuf::const_buffers_type::end() const -> - const_iterator -{ - return const_iterator{p_ + n_, n_}; -} - -//------------------------------------------------------------------------------ - -class static_streambuf::mutable_buffers_type -{ - std::size_t n_; - std::uint8_t* p_; - -public: - using value_type = boost::asio::mutable_buffer; - - class const_iterator; - - mutable_buffers_type() = delete; - mutable_buffers_type( - mutable_buffers_type const&) = default; - mutable_buffers_type& operator=( - mutable_buffers_type const&) = default; - - const_iterator - begin() const; - - const_iterator - end() const; - -private: - friend class static_streambuf; - - mutable_buffers_type( - std::uint8_t* p, std::size_t n) - : n_(n) - , p_(p) - { - } -}; - -class static_streambuf::mutable_buffers_type::const_iterator -{ - std::size_t n_ = 0; - std::uint8_t* p_ = nullptr; - -public: - using value_type = boost::asio::mutable_buffer; - using pointer = value_type const*; - using reference = value_type; - using difference_type = std::ptrdiff_t; - using iterator_category = - std::bidirectional_iterator_tag; - - const_iterator() = default; - const_iterator(const_iterator&& other) = default; - const_iterator(const_iterator const& other) = default; - const_iterator& operator=(const_iterator&& other) = default; - const_iterator& operator=(const_iterator const& other) = default; - - bool - operator==(const_iterator const& other) const - { - return p_ == other.p_; - } - - bool - operator!=(const_iterator const& other) const - { - return !(*this == other); - } - - reference - operator*() const - { - return value_type{p_, n_}; - } - - pointer - operator->() const = delete; - - const_iterator& - operator++() - { - p_ += n_; - return *this; - } - - const_iterator - operator++(int) - { - auto temp = *this; - ++(*this); - return temp; - } - - const_iterator& - operator--() - { - p_ -= n_; - return *this; - } - - const_iterator - operator--(int) - { - auto temp = *this; - --(*this); - return temp; - } - -private: - friend class mutable_buffers_type; - - const_iterator(std::uint8_t* p, std::size_t n) - : n_(n) - , p_(p) - { - } -}; - -inline -auto -static_streambuf::mutable_buffers_type::begin() const -> - const_iterator -{ - return const_iterator{p_, n_}; -} - -inline -auto -static_streambuf::mutable_buffers_type::end() const -> - const_iterator -{ - return const_iterator{p_ + n_, n_}; -} - -//------------------------------------------------------------------------------ - - -inline -auto -static_streambuf::data() const -> - const_buffers_type -{ - return const_buffers_type{in_, - static_cast(out_ - in_)}; -} - -inline -auto -static_streambuf::prepare(std::size_t n) -> - mutable_buffers_type -{ - if(n > static_cast(end_ - out_)) - throw detail::make_exception( - "no space in streambuf", __FILE__, __LINE__); - last_ = out_ + n; - return mutable_buffers_type{out_, n}; -} - -} // beast - -#endif diff --git a/src/beast/include/beast/core/impl/static_string.ipp b/src/beast/include/beast/core/impl/static_string.ipp new file mode 100644 index 0000000000..6bab53b5d2 --- /dev/null +++ b/src/beast/include/beast/core/impl/static_string.ipp @@ -0,0 +1,614 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_IMPL_STATIC_STRING_IPP +#define BEAST_IMPL_STATIC_STRING_IPP + +#include +#include +#include + +namespace beast { + +// +// (constructor) +// + +template +static_string:: +static_string() +{ + n_ = 0; + term(); +} + +template +static_string:: +static_string(size_type count, CharT ch) +{ + assign(count, ch); +} + +template +template +static_string:: +static_string(static_string const& other, + size_type pos) +{ + assign(other, pos); +} + +template +template +static_string:: +static_string(static_string const& other, + size_type pos, size_type count) +{ + assign(other, pos, count); +} + +template +static_string:: +static_string(CharT const* s, size_type count) +{ + assign(s, count); +} + +template +static_string:: +static_string(CharT const* s) +{ + assign(s); +} + +template +template +static_string:: +static_string(InputIt first, InputIt last) +{ + assign(first, last); +} + +template +static_string:: +static_string(static_string const& s) +{ + assign(s); +} + +template +template +static_string:: +static_string(static_string const& s) +{ + assign(s); +} + +template +static_string:: +static_string(std::initializer_list init) +{ + assign(init.begin(), init.end()); +} + +template +static_string:: +static_string(string_view_type sv) +{ + assign(sv); +} + +template +template +static_string:: +static_string(T const& t, size_type pos, size_type n) +{ + assign(t, pos, n); +} + +// +// (assignment) +// + +template +auto +static_string:: +assign(size_type count, CharT ch) -> + static_string& +{ + if(count > max_size()) + BOOST_THROW_EXCEPTION(std::length_error{ + "count > max_size()"}); + n_ = count; + Traits::assign(&s_[0], n_, ch); + term(); + return *this; +} + +template +auto +static_string:: +assign(static_string const& str) -> + static_string& +{ + n_ = str.n_; + Traits::copy(&s_[0], &str.s_[0], n_ + 1); + return *this; +} + +template +template +auto +static_string:: +assign(static_string const& str, + size_type pos, size_type count) -> + static_string& +{ + auto const ss = str.substr(pos, count); + return assign(ss.data(), ss.size()); +} + +template +auto +static_string:: +assign(CharT const* s, size_type count) -> + static_string& +{ + if(count > max_size()) + BOOST_THROW_EXCEPTION(std::length_error{ + "count > max_size()"}); + n_ = count; + Traits::copy(&s_[0], s, n_); + term(); + return *this; +} + +template +template +auto +static_string:: +assign(InputIt first, InputIt last) -> + static_string& +{ + std::size_t const n = std::distance(first, last); + if(n > max_size()) + BOOST_THROW_EXCEPTION(std::length_error{ + "n > max_size()"}); + n_ = n; + for(auto it = &s_[0]; first != last; ++it, ++first) + Traits::assign(*it, *first); + term(); + return *this; +} + +template +template +auto +static_string:: +assign(T const& t, size_type pos, size_type count) -> + typename std::enable_if::value, static_string&>::type +{ + auto const sv = string_view_type(t).substr(pos, count); + if(sv.size() > max_size()) + BOOST_THROW_EXCEPTION(std::length_error{ + "sv.size() > max_size()"}); + n_ = sv.size(); + Traits::copy(&s_[0], &sv[0], n_); + term(); + return *this; +} + +// +// Element access +// + +template +auto +static_string:: +at(size_type pos) -> + reference +{ + if(pos >= size()) + BOOST_THROW_EXCEPTION(std::out_of_range{ + "pos >= size()"}); + return s_[pos]; +} + +template +auto +static_string:: +at(size_type pos) const -> + const_reference +{ + if(pos >= size()) + BOOST_THROW_EXCEPTION(std::out_of_range{ + "pos >= size()"}); + return s_[pos]; +} + +// +// Capacity +// + +template +void +static_string:: +reserve(std::size_t n) +{ + if(n > max_size()) + BOOST_THROW_EXCEPTION(std::length_error{ + "n > max_size()"}); +} + +// +// Operations +// + +template +void +static_string:: +clear() +{ + n_ = 0; + term(); +} + +template +auto +static_string:: +insert(size_type index, size_type count, CharT ch) -> + static_string& +{ + if(index > size()) + BOOST_THROW_EXCEPTION(std::out_of_range{ + "index > size()"}); + insert(begin() + index, count, ch); + return *this; +} + +template +auto +static_string:: +insert(size_type index, CharT const* s, size_type count) -> + static_string& +{ + if(index > size()) + BOOST_THROW_EXCEPTION(std::out_of_range{ + "index > size()"}); + if(size() + count > max_size()) + BOOST_THROW_EXCEPTION(std::length_error{ + "size() + count > max_size()"}); + Traits::move( + &s_[index + count], &s_[index], size() - index); + n_ += count; + Traits::copy(&s_[index], s, count); + term(); + return *this; +} + +template +template +auto +static_string:: +insert(size_type index, + static_string const& str, + size_type index_str, size_type count) -> + static_string& +{ + auto const ss = str.substr(index_str, count); + return insert(index, ss.data(), ss.size()); +} + +template +auto +static_string:: +insert(const_iterator pos, size_type count, CharT ch) -> + iterator +{ + if(size() + count > max_size()) + BOOST_THROW_EXCEPTION(std::length_error{ + "size() + count() > max_size()"}); + auto const index = pos - &s_[0]; + Traits::move( + &s_[index + count], &s_[index], size() - index); + n_ += count; + Traits::assign(&s_[index], count, ch); + term(); + return &s_[index]; +} + +template +template +auto +static_string:: +insert(const_iterator pos, InputIt first, InputIt last) -> + typename std::enable_if< + detail::is_input_iterator::value, + iterator>::type +{ + std::size_t const count = std::distance(first, last); + if(size() + count > max_size()) + BOOST_THROW_EXCEPTION(std::length_error{ + "size() + count > max_size()"}); + std::size_t const index = pos - begin(); + Traits::move( + &s_[index + count], &s_[index], size() - index); + n_ += count; + for(auto it = begin() + index; + first != last; ++it, ++first) + Traits::assign(*it, *first); + term(); + return begin() + index; +} + +template +template +auto +static_string:: +insert(size_type index, const T& t, + size_type index_str, size_type count) -> + typename std::enable_if::value && + ! std::is_convertible::value, + static_string&>::type +{ + auto const str = + string_view_type(t).substr(index_str, count); + return insert(index, str.data(), str.size()); +} + +template +auto +static_string:: +erase(size_type index, size_type count) -> + static_string& +{ + if(index > size()) + BOOST_THROW_EXCEPTION(std::out_of_range{ + "index > size()"}); + auto const n = (std::min)(count, size() - index); + Traits::move( + &s_[index], &s_[index + n], size() - (index + n) + 1); + n_ -= n; + return *this; +} + +template +auto +static_string:: +erase(const_iterator pos) -> + iterator +{ + erase(pos - begin(), 1); + return begin() + (pos - begin()); +} + +template +auto +static_string:: +erase(const_iterator first, const_iterator last) -> + iterator +{ + erase(first - begin(), + std::distance(first, last)); + return begin() + (first - begin()); +} + +template +void +static_string:: +push_back(CharT ch) +{ + if(size() >= max_size()) + BOOST_THROW_EXCEPTION(std::length_error{ + "size() >= max_size()"}); + Traits::assign(s_[n_++], ch); + term(); +} + +template +template +auto +static_string:: +append(static_string const& str, + size_type pos, size_type count) -> + static_string& +{ + // Valid range is [0, size) + if(pos >= str.size()) + BOOST_THROW_EXCEPTION(std::out_of_range{ + "pos > str.size()"}); + string_view_type const ss{&str.s_[pos], + (std::min)(count, str.size() - pos)}; + insert(size(), ss.data(), ss.size()); + return *this; +} + +template +auto +static_string:: +substr(size_type pos, size_type count) const -> + string_view_type +{ + if(pos > size()) + BOOST_THROW_EXCEPTION(std::out_of_range{ + "pos > size()"}); + return{&s_[pos], (std::min)(count, size() - pos)}; +} + +template +auto +static_string:: +copy(CharT* dest, size_type count, size_type pos) const -> + size_type +{ + auto const str = substr(pos, count); + Traits::copy(dest, str.data(), str.size()); + return str.size(); +} + +template +void +static_string:: +resize(std::size_t n) +{ + if(n > max_size()) + BOOST_THROW_EXCEPTION(std::length_error{ + "n > max_size()"}); + n_ = n; + term(); +} + +template +void +static_string:: +resize(std::size_t n, CharT c) +{ + if(n > max_size()) + BOOST_THROW_EXCEPTION(std::length_error{ + "n > max_size()"}); + if(n > n_) + Traits::assign(&s_[n_], n - n_, c); + n_ = n; + term(); +} + +template +void +static_string:: +swap(static_string& str) +{ + static_string tmp(str); + str.n_ = n_; + Traits::copy(&str.s_[0], &s_[0], n_ + 1); + n_ = tmp.n_; + Traits::copy(&s_[0], &tmp.s_[0], n_ + 1); +} + +template +template +void +static_string:: +swap(static_string& str) +{ + if(size() > str.max_size()) + BOOST_THROW_EXCEPTION(std::length_error{ + "size() > str.max_size()"}); + if(str.size() > max_size()) + BOOST_THROW_EXCEPTION(std::length_error{ + "str.size() > max_size()"}); + static_string tmp(str); + str.n_ = n_; + Traits::copy(&str.s_[0], &s_[0], n_ + 1); + n_ = tmp.n_; + Traits::copy(&s_[0], &tmp.s_[0], n_ + 1); +} + + +template +auto +static_string:: +assign_char(CharT ch, std::true_type) -> + static_string& +{ + n_ = 1; + Traits::assign(s_[0], ch); + term(); + return *this; +} + +template +auto +static_string:: +assign_char(CharT, std::false_type) -> + static_string& +{ + BOOST_THROW_EXCEPTION(std::length_error{ + "max_size() == 0"}); +} + +namespace detail { + +template +static_string +to_static_string(Integer x, std::true_type) +{ + if(x == 0) + return {'0'}; + static_string s; + if(x < 0) + { + x = -x; + char buf[max_digits(sizeof(x))]; + char* p = buf; + for(;x > 0; x /= 10) + *p++ = "0123456789"[x % 10]; + s.resize(1 + p - buf); + s[0] = '-'; + auto d = &s[1]; + while(p > buf) + *d++ = *--p; + } + else + { + char buf[max_digits(sizeof(x))]; + char* p = buf; + for(;x > 0; x /= 10) + *p++ = "0123456789"[x % 10]; + s.resize(p - buf); + auto d = &s[0]; + while(p > buf) + *d++ = *--p; + } + return s; +} + +template +static_string +to_static_string(Integer x, std::false_type) +{ + if(x == 0) + return {'0'}; + char buf[max_digits(sizeof(x))]; + char* p = buf; + for(;x > 0; x /= 10) + *p++ = "0123456789"[x % 10]; + static_string s; + s.resize(p - buf); + auto d = &s[0]; + while(p > buf) + *d++ = *--p; + return s; +} + +} // detail + +template +static_string +to_static_string(Integer x) +{ + using CharT = char; + using Traits = std::char_traits; + BOOST_STATIC_ASSERT(std::is_integral::value); + char buf[detail::max_digits(sizeof(Integer))]; + auto last = buf + sizeof(buf); + auto it = detail::raw_to_string< + CharT, Integer, Traits>(last, sizeof(buf), x); + static_string s; + s.resize(static_cast(last - it)); + auto p = s.data(); + while(it < last) + Traits::assign(*p++, *it++); + return s; +} + +} // beast + +#endif diff --git a/src/beast/include/beast/core/impl/string_param.ipp b/src/beast/include/beast/core/impl/string_param.ipp new file mode 100644 index 0000000000..8bcccca4c6 --- /dev/null +++ b/src/beast/include/beast/core/impl/string_param.ipp @@ -0,0 +1,104 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_IMPL_STRING_PARAM_IPP +#define BEAST_IMPL_STRING_PARAM_IPP + +namespace beast { + +template +typename std::enable_if< + std::is_integral::value>::type +string_param:: +print(T const& t) +{ + auto const last = buf_ + sizeof(buf_); + auto const it = detail::raw_to_string< + char, T, std::char_traits>( + last, sizeof(buf_), t); + sv_ = {it, static_cast( + last - it)}; +} + +template +typename std::enable_if< + ! std::is_integral::value && + ! std::is_convertible::value +>::type +string_param:: +print(T const& t) +{ + os_.emplace(buf_, sizeof(buf_)); + *os_ << t; + os_->flush(); + sv_ = os_->str(); +} + +inline +void +string_param:: +print(string_view const& sv) +{ + sv_ = sv; +} + +template +typename std::enable_if< + std::is_integral::value>::type +string_param:: +print_1(T const& t) +{ + char buf[detail::max_digits(sizeof(T))]; + auto const last = buf + sizeof(buf); + auto const it = detail::raw_to_string< + char, T, std::char_traits>( + last, sizeof(buf), t); + *os_ << string_view{it, + static_cast(last - it)}; +} + +template +typename std::enable_if< + ! std::is_integral::value>::type +string_param:: +print_1(T const& t) +{ + *os_ << t; +} + +template +void +string_param:: +print_n(T0 const& t0, TN const&... tn) +{ + print_1(t0); + print_n(tn...); +} + +template +void +string_param:: +print(T0 const& t0, T1 const& t1, TN const&... tn) +{ + os_.emplace(buf_, sizeof(buf_)); + print_1(t0); + print_1(t1); + print_n(tn...); + os_->flush(); + sv_ = os_->str(); +} + +template +string_param:: +string_param(Args const&... args) +{ + print(args...); +} + +} // beast + +#endif diff --git a/src/beast/include/beast/core/multi_buffer.hpp b/src/beast/include/beast/core/multi_buffer.hpp new file mode 100644 index 0000000000..4b961756fc --- /dev/null +++ b/src/beast/include/beast/core/multi_buffer.hpp @@ -0,0 +1,323 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_MULTI_BUFFER_HPP +#define BEAST_MULTI_BUFFER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { + +/** A @b DynamicBuffer that uses multiple buffers internally. + + The implementation uses a sequence of one or more character arrays + of varying sizes. Additional character array objects are appended to + the sequence to accommodate changes in the size of the character + sequence. + + @note Meets the requirements of @b DynamicBuffer. + + @tparam Allocator The allocator to use for managing memory. +*/ +template +class basic_multi_buffer +#if ! BEAST_DOXYGEN + : private detail::empty_base_optimization< + typename std::allocator_traits:: + template rebind_alloc> +#endif +{ +public: +#if BEAST_DOXYGEN + /// The type of allocator used. + using allocator_type = Allocator; +#else + using allocator_type = typename + std::allocator_traits:: + template rebind_alloc; +#endif + +private: + // Storage for the list of buffers representing the input + // and output sequences. The allocation for each element + // contains `element` followed by raw storage bytes. + class element; + + using alloc_traits = std::allocator_traits; + using list_type = typename boost::intrusive::make_list>::type; + using iterator = typename list_type::iterator; + using const_iterator = typename list_type::const_iterator; + + using size_type = typename std::allocator_traits::size_type; + using const_buffer = boost::asio::const_buffer; + using mutable_buffer = boost::asio::mutable_buffer; + + static_assert(std::is_base_of::iterator_category>::value, + "BidirectionalIterator requirements not met"); + + static_assert(std::is_base_of::iterator_category>::value, + "BidirectionalIterator requirements not met"); + + std::size_t max_ = + (std::numeric_limits::max)(); + list_type list_; // list of allocated buffers + iterator out_; // element that contains out_pos_ + size_type in_size_ = 0; // size of the input sequence + size_type in_pos_ = 0; // input offset in list_.front() + size_type out_pos_ = 0; // output offset in *out_ + size_type out_end_ = 0; // output end offset in list_.back() + +public: +#if BEAST_DOXYGEN + /// The type used to represent the input sequence as a list of buffers. + using const_buffers_type = implementation_defined; + + /// The type used to represent the output sequence as a list of buffers. + using mutable_buffers_type = implementation_defined; + +#else + class const_buffers_type; + + class mutable_buffers_type; + +#endif + + /// Destructor + ~basic_multi_buffer(); + + /** Constructor + + Upon construction, capacity will be zero. + */ + basic_multi_buffer(); + + /** Constructor. + + @param limit The setting for @ref max_size. + */ + explicit + basic_multi_buffer(std::size_t limit); + + /** Constructor. + + @param alloc The allocator to use. + */ + basic_multi_buffer(Allocator const& alloc); + + /** Constructor. + + @param limit The setting for @ref max_size. + + @param alloc The allocator to use. + */ + basic_multi_buffer( + std::size_t limit, Allocator const& alloc); + + /** Move constructor + + After the move, `*this` will have an empty output sequence. + + @param other The object to move from. After the move, + The object's state will be as if constructed using + its current allocator and limit. + */ + basic_multi_buffer(basic_multi_buffer&& other); + + /** Move constructor + + After the move, `*this` will have an empty output sequence. + + @param other The object to move from. After the move, + The object's state will be as if constructed using + its current allocator and limit. + + @param alloc The allocator to use. + */ + basic_multi_buffer(basic_multi_buffer&& other, + Allocator const& alloc); + + /** Copy constructor. + + @param other The object to copy from. + */ + basic_multi_buffer(basic_multi_buffer const& other); + + /** Copy constructor + + @param other The object to copy from. + + @param alloc The allocator to use. + */ + basic_multi_buffer(basic_multi_buffer const& other, + Allocator const& alloc); + + /** Copy constructor. + + @param other The object to copy from. + */ + template + basic_multi_buffer(basic_multi_buffer< + OtherAlloc> const& other); + + /** Copy constructor. + + @param other The object to copy from. + + @param alloc The allocator to use. + */ + template + basic_multi_buffer(basic_multi_buffer< + OtherAlloc> const& other, allocator_type const& alloc); + + /** Move assignment + + After the move, `*this` will have an empty output sequence. + + @param other The object to move from. After the move, + The object's state will be as if constructed using + its current allocator and limit. + */ + basic_multi_buffer& + operator=(basic_multi_buffer&& other); + + /** Copy assignment + + After the copy, `*this` will have an empty output sequence. + + @param other The object to copy from. + */ + basic_multi_buffer& operator=(basic_multi_buffer const& other); + + /** Copy assignment + + After the copy, `*this` will have an empty output sequence. + + @param other The object to copy from. + */ + template + basic_multi_buffer& operator=( + basic_multi_buffer const& other); + + /// Returns a copy of the associated allocator. + allocator_type + get_allocator() const + { + return this->member(); + } + + /// Returns the size of the input sequence. + size_type + size() const + { + return in_size_; + } + + /// Returns the permitted maximum sum of the sizes of the input and output sequence. + size_type + max_size() const + { + return max_; + } + + /// Returns the maximum sum of the sizes of the input sequence and output sequence the buffer can hold without requiring reallocation. + std::size_t + capacity() const; + + /** Get a list of buffers that represents the input sequence. + + @note These buffers remain valid across subsequent calls to `prepare`. + */ + const_buffers_type + data() const; + + /** Get a list of buffers that represents the output sequence, with the given size. + + @note Buffers representing the input sequence acquired prior to + this call remain valid. + */ + mutable_buffers_type + prepare(size_type n); + + /** Move bytes from the output sequence to the input sequence. + + @note Buffers representing the input sequence acquired prior to + this call remain valid. + */ + void + commit(size_type n); + + /// Remove bytes from the input sequence. + void + consume(size_type n); + + template + friend + void + swap( + basic_multi_buffer& lhs, + basic_multi_buffer& rhs); + +private: + template + friend class basic_multi_buffer; + + void + delete_element(element& e); + + void + delete_list(); + + void + reset(); + + template + void + copy_from(DynamicBuffer const& other); + + void + move_assign(basic_multi_buffer& other, std::false_type); + + void + move_assign(basic_multi_buffer& other, std::true_type); + + void + copy_assign(basic_multi_buffer const& other, std::false_type); + + void + copy_assign(basic_multi_buffer const& other, std::true_type); + + void + swap(basic_multi_buffer&); + + void + swap(basic_multi_buffer&, std::true_type); + + void + swap(basic_multi_buffer&, std::false_type); + + void + debug_check() const; +}; + +/// A typical multi buffer +using multi_buffer = basic_multi_buffer>; + +} // beast + +#include + +#endif diff --git a/src/beast/include/beast/core/ostream.hpp b/src/beast/include/beast/core/ostream.hpp new file mode 100644 index 0000000000..49cbd69aae --- /dev/null +++ b/src/beast/include/beast/core/ostream.hpp @@ -0,0 +1,99 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_WRITE_OSTREAM_HPP +#define BEAST_WRITE_OSTREAM_HPP + +#include +#include +#include +#include +#include +#include + +namespace beast { + +/** Return an object representing a @b ConstBufferSequence. + + This function wraps a reference to a buffer sequence and permits + the following operation: + + @li `operator<<` to `std::ostream`. No character translation is + performed; unprintable and null characters will be transferred + as-is to the output stream. + + @par Example + @code + multi_buffer b; + ... + std::cout << buffers(b.data()) << std::endl; + @endcode + + @param b An object meeting the requirements of @b ConstBufferSequence + to be streamed. The implementation will make a copy of this object. + Ownership of the underlying memory is not transferred, the application + is still responsible for managing its lifetime. +*/ +template +#if BEAST_DOXYGEN +implementation_defined +#else +detail::buffers_helper +#endif +buffers(ConstBufferSequence const& b) +{ + static_assert(is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + return detail::buffers_helper< + ConstBufferSequence>{b}; +} + +/** Return an output stream that formats values into a @b DynamicBuffer. + + This function wraps the caller provided @b DynamicBuffer into + a `std::ostream` derived class, to allow `operator<<` stream style + formatting operations. + + @par Example + @code + ostream(buffer) << "Hello, world!" << std::endl; + @endcode + + @note Calling members of the underlying buffer before the output + stream is destroyed results in undefined behavior. + + @param buffer An object meeting the requirements of @b DynamicBuffer + into which the formatted output will be placed. + + @return An object derived from `std::ostream` which redirects output + The wrapped dynamic buffer is not modified, a copy is made instead. + Ownership of the underlying memory is not transferred, the application + is still responsible for managing its lifetime. The caller is + responsible for ensuring the dynamic buffer is not destroyed for the + lifetime of the output stream. +*/ +template +#if BEAST_DOXYGEN +implementation_defined +#else +detail::ostream_helper< + DynamicBuffer, char, std::char_traits, + detail::basic_streambuf_movable::value> +#endif +ostream(DynamicBuffer& buffer) +{ + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + return detail::ostream_helper< + DynamicBuffer, char, std::char_traits, + detail::basic_streambuf_movable::value>{buffer}; +} + +} // beast + +#endif diff --git a/src/beast/include/beast/core/placeholders.hpp b/src/beast/include/beast/core/placeholders.hpp deleted file mode 100644 index c112778936..0000000000 --- a/src/beast/include/beast/core/placeholders.hpp +++ /dev/null @@ -1,30 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_PLACEHOLDERS_HPP -#define BEAST_PLACEHOLDERS_HPP - -#include -#include - -namespace beast { -namespace asio { - -namespace placeholders { -// asio placeholders that work with std::bind -namespace { -static auto const error (std::placeholders::_1); -static auto const bytes_transferred (std::placeholders::_2); -static auto const iterator (std::placeholders::_2); -static auto const signal_number (std::placeholders::_2); -} -} // placeholders - -} // asio -} // beast - -#endif diff --git a/src/beast/include/beast/core/prepare_buffer.hpp b/src/beast/include/beast/core/prepare_buffer.hpp deleted file mode 100644 index ab0fae645b..0000000000 --- a/src/beast/include/beast/core/prepare_buffer.hpp +++ /dev/null @@ -1,69 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_PREPARE_BUFFER_HPP -#define BEAST_PREPARE_BUFFER_HPP - -#include -#include -#include - -namespace beast { - -/** Return a shortened buffer. - - The returned buffer points to the same memory as the - passed buffer, but with a size that is equal to or less - than the size of the original buffer. - - @param n The size of the returned buffer. - - @param buffer The buffer to shorten. Ownership of the - underlying memory is not transferred. - - @return A new buffer that points to the first `n` bytes - of the original buffer. -*/ -inline -boost::asio::const_buffer -prepare_buffer(std::size_t n, - boost::asio::const_buffer buffer) -{ - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - return { buffer_cast(buffer), - (std::min)(n, buffer_size(buffer)) }; -} - -/** Return a shortened buffer. - - The returned buffer points to the same memory as the - passed buffer, but with a size that is equal to or less - than the size of the original buffer. - - @param n The size of the returned buffer. - - @param buffer The buffer to shorten. Ownership of the - underlying memory is not transferred. - - @return A new buffer that points to the first `n` bytes - of the original buffer. -*/ -inline -boost::asio::mutable_buffer -prepare_buffer(std::size_t n, - boost::asio::mutable_buffer buffer) -{ - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - return { buffer_cast(buffer), - (std::min)(n, buffer_size(buffer)) }; -} - -} // beast - -#endif diff --git a/src/beast/include/beast/core/prepare_buffers.hpp b/src/beast/include/beast/core/prepare_buffers.hpp deleted file mode 100644 index 7db31b5b80..0000000000 --- a/src/beast/include/beast/core/prepare_buffers.hpp +++ /dev/null @@ -1,53 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_PREPARE_BUFFERS_HPP -#define BEAST_PREPARE_BUFFERS_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { - -/** Return a shortened buffer sequence. - - This function returns a new buffer sequence which adapts the - passed buffer sequence and efficiently presents a shorter subset - of the original list of buffers starting with the first byte of - the original sequence. - - @param n The maximum number of bytes in the wrapped - sequence. If this is larger than the size of passed, - buffers, the resulting sequence will represent the - entire input sequence. - - @param buffers The buffer sequence to adapt. A copy of - the sequence will be made, but ownership of the underlying - memory is not transferred. -*/ -template -#if GENERATING_DOCS -implementation_defined -#else -inline -detail::prepared_buffers -#endif -prepare_buffers(std::size_t n, BufferSequence const& buffers) -{ - return detail::prepared_buffers(n, buffers); -} - -} // beast - -#endif diff --git a/src/beast/include/beast/core/read_size.hpp b/src/beast/include/beast/core/read_size.hpp new file mode 100644 index 0000000000..326877f4a2 --- /dev/null +++ b/src/beast/include/beast/core/read_size.hpp @@ -0,0 +1,60 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_READ_SIZE_HELPER_HPP +#define BEAST_READ_SIZE_HELPER_HPP + +#include +#include +#include + +namespace beast { + +/** Returns a natural read size. + + This function inspects the capacity, size, and maximum + size of the dynamic buffer. Then it computes a natural + read size given the passed-in upper limit. It favors + a read size that does not require a reallocation, subject + to a reasonable minimum to avoid tiny reads. + + @param buffer The dynamic buffer to inspect. + + @param max_size An upper limit on the returned value. + + @note If the buffer is already at its maximum size, zero + is returned. +*/ +template +std::size_t +read_size(DynamicBuffer& buffer, std::size_t max_size); + +/** Returns a natural read size or throw if the buffer is full. + + This function inspects the capacity, size, and maximum + size of the dynamic buffer. Then it computes a natural + read size given the passed-in upper limit. It favors + a read size that does not require a reallocation, subject + to a reasonable minimum to avoid tiny reads. + + @param buffer The dynamic buffer to inspect. + + @param max_size An upper limit on the returned value. + + @throws std::length_error if `max_size > 0` and the buffer + is full. +*/ +template +std::size_t +read_size_or_throw(DynamicBuffer& buffer, + std::size_t max_size); + +} // beast + +#include + +#endif diff --git a/src/beast/include/beast/core/span.hpp b/src/beast/include/beast/core/span.hpp new file mode 100644 index 0000000000..1d465ce42f --- /dev/null +++ b/src/beast/include/beast/core/span.hpp @@ -0,0 +1,211 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_CORE_SPAN_HPP +#define BEAST_CORE_SPAN_HPP + +#include +#include +#include +#include +#include +#include + +namespace beast { + +/** A range of bytes expressed as a ContiguousContainer + + This class implements a non-owning reference to a storage + area of a certain size and having an underlying integral + type with size of 1. + + @tparam T The type pointed to by span iterators +*/ +template +class span +{ + T* data_ = nullptr; + std::size_t size_ = 0; + +public: + /// The type of value, including cv qualifiers + using element_type = T; + + /// The type of value of each span element + using value_type = typename std::remove_const::type; + + /// The type of integer used to index the span + using index_type = std::ptrdiff_t; + + /// A pointer to a span element + using pointer = T*; + + /// A reference to a span element + using reference = T&; + + /// The iterator used by the container + using iterator = pointer; + + /// The const pointer used by the container + using const_pointer = T const*; + + /// The const reference used by the container + using const_reference = T const&; + + /// The const iterator used by the container + using const_iterator = const_pointer; + + /// Constructor + span() = default; + + /// Constructor + span(span const&) = default; + + /// Assignment + span& operator=(span const&) = default; + + /** Constructor + + @param data A pointer to the beginning of the range of elements + + @param size The number of elements pointed to by `data` + */ + span(T* data, std::size_t size) + : data_(data), size_(size) + { + } + + /** Constructor + + @param container The container to construct from + */ + template::value>::type +#endif + > + explicit + span(ContiguousContainer&& container) + : data_(container.data()) + , size_(container.size()) + { + } + +#if ! BEAST_DOXYGEN + template + explicit + span(std::basic_string& s) + : data_(&s[0]) + , size_(s.size()) + { + } + + template + explicit + span(std::basic_string const& s) + : data_(s.data()) + , size_(s.size()) + { + } +#endif + + /** Assignment + + @param container The container to assign from + */ + template +#if BEAST_DOXYGEN + span& +#else + typename std::enable_if::value, + span&>::type +#endif + operator=(ContiguousContainer&& container) + { + data_ = container.data(); + size_ = container.size(); + return *this; + } + +#if ! BEAST_DOXYGEN + template + span& + operator=(std::basic_string< + CharT, Traits, Allocator>& s) + { + data_ = &s[0]; + size_ = s.size(); + return *this; + } + + template + span& + operator=(std::basic_string< + CharT, Traits, Allocator> const& s) + { + data_ = s.data(); + size_ = s.size(); + return *this; + } +#endif + + /// Returns `true` if the span is empty + bool + empty() const + { + return size_ == 0; + } + + /// Returns a pointer to the beginning of the span + T* + data() const + { + return data_; + } + + /// Returns the number of elements in the span + std::size_t + size() const + { + return size_; + } + + /// Returns an iterator to the beginning of the span + const_iterator + begin() const + { + return data_; + } + + /// Returns an iterator to the beginning of the span + const_iterator + cbegin() const + { + return data_; + } + + /// Returns an iterator to one past the end of the span + const_iterator + end() const + { + return data_ + size_; + } + + /// Returns an iterator to one past the end of the span + const_iterator + cend() const + { + return data_ + size_; + } +}; + +} // beast + +#endif diff --git a/src/beast/include/beast/core/static_buffer.hpp b/src/beast/include/beast/core/static_buffer.hpp new file mode 100644 index 0000000000..1f86c3364e --- /dev/null +++ b/src/beast/include/beast/core/static_buffer.hpp @@ -0,0 +1,218 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_STATIC_BUFFER_HPP +#define BEAST_STATIC_BUFFER_HPP + +#include +#include +#include +#include +#include + +namespace beast { + +/** A @b DynamicBuffer with a fixed size internal buffer. + + Ownership of the underlying storage belongs to the derived class. + + @note Variables are usually declared using the template class + @ref static_buffer_n; however, to reduce the number of instantiations + of template functions receiving static stream buffer arguments in a + deduced context, the signature of the receiving function should use + @ref static_buffer. + + When used with @ref static_buffer_n this implements a dynamic + buffer using no memory allocations. + + @see @ref static_buffer_n +*/ +class static_buffer +{ + char* begin_; + char* in_; + char* out_; + char* last_; + char* end_; + + static_buffer(static_buffer const& other) = delete; + static_buffer& operator=(static_buffer const&) = delete; + +public: + /// The type used to represent the input sequence as a list of buffers. + using const_buffers_type = boost::asio::const_buffers_1; + + /// The type used to represent the output sequence as a list of buffers. + using mutable_buffers_type = boost::asio::mutable_buffers_1; + + /** Constructor. + + This creates a dynamic buffer using the provided storage area. + + @param p A pointer to valid storage of at least `n` bytes. + + @param n The number of valid bytes pointed to by `p`. + */ + static_buffer(void* p, std::size_t n) + { + reset_impl(p, n); + } + + /// Return the size of the input sequence. + std::size_t + size() const + { + return out_ - in_; + } + + /// Return the maximum sum of the input and output sequence sizes. + std::size_t + max_size() const + { + return dist(begin_, end_); + } + + /// Return the maximum sum of input and output sizes that can be held without an allocation. + std::size_t + capacity() const + { + return max_size(); + } + + /** Get a list of buffers that represent the input sequence. + + @note These buffers remain valid across subsequent calls to `prepare`. + */ + const_buffers_type + data() const; + + /** Get a list of buffers that represent the output sequence, with the given size. + + @throws std::length_error if the size would exceed the limit + imposed by the underlying mutable buffer sequence. + + @note Buffers representing the input sequence acquired prior to + this call remain valid. + */ + mutable_buffers_type + prepare(std::size_t n); + + /** Move bytes from the output sequence to the input sequence. + + @note Buffers representing the input sequence acquired prior to + this call remain valid. + */ + void + commit(std::size_t n) + { + out_ += std::min(n, last_ - out_); + } + + /// Remove bytes from the input sequence. + void + consume(std::size_t n) + { + consume_impl(n); + } + +protected: + /** Default constructor. + + The buffer will be in an undefined state. It is necessary + for the derived class to call @ref reset in order to + initialize the object. + */ + static_buffer(); + + /** Reset the pointed-to buffer. + + This function resets the internal state to the buffer provided. + All input and output sequences are invalidated. This function + allows the derived class to construct its members before + initializing the static buffer. + + @param p A pointer to valid storage of at least `n` bytes. + + @param n The number of valid bytes pointed to by `p`. + */ + void + reset(void* p, std::size_t n); + +private: + static + inline + std::size_t + dist(char const* first, char const* last) + { + return static_cast(last - first); + } + + template + void + reset_impl(void* p, std::size_t n); + + template + mutable_buffers_type + prepare_impl(std::size_t n); + + template + void + consume_impl(std::size_t n); +}; + +//------------------------------------------------------------------------------ + +/** A @b DynamicBuffer with a fixed size internal buffer. + + This implements a dynamic buffer using no memory allocations. + + @tparam N The number of bytes in the internal buffer. + + @note To reduce the number of template instantiations when passing + objects of this type in a deduced context, the signature of the + receiving function should use @ref static_buffer instead. + + @see @ref static_buffer +*/ +template +class static_buffer_n : public static_buffer +{ + char buf_[N]; + +public: + /// Copy constructor + static_buffer_n(static_buffer_n const&); + + /// Copy assignment + static_buffer_n& operator=(static_buffer_n const&); + + /// Construct a static buffer. + static_buffer_n() + : static_buffer(buf_, N) + { + } + + /// Returns the @ref static_buffer portion of this object + static_buffer& + base() + { + return *this; + } + + /// Returns the @ref static_buffer portion of this object + static_buffer const& + base() const + { + return *this; + } +}; + +} // beast + +#include + +#endif diff --git a/src/beast/include/beast/core/static_streambuf.hpp b/src/beast/include/beast/core/static_streambuf.hpp deleted file mode 100644 index 75ced50a37..0000000000 --- a/src/beast/include/beast/core/static_streambuf.hpp +++ /dev/null @@ -1,199 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_STATIC_STREAMBUF_HPP -#define BEAST_STATIC_STREAMBUF_HPP - -#include -#include -#include -#include -#include - -namespace beast { - -/** A @b `DynamicBuffer` with a fixed size internal buffer. - - Ownership of the underlying storage belongs to the derived class. - - @note Variables are usually declared using the template class - @ref static_streambuf_n; however, to reduce the number of instantiations - of template functions receiving static stream buffer arguments in a - deduced context, the signature of the receiving function should use - @ref static_streambuf. -*/ -class static_streambuf -{ -#if GENERATING_DOCS -private: -#else -protected: -#endif - std::uint8_t* begin_; - std::uint8_t* in_; - std::uint8_t* out_; - std::uint8_t* last_; - std::uint8_t* end_; - -public: -#if GENERATING_DOCS - /// The type used to represent the input sequence as a list of buffers. - using const_buffers_type = implementation_defined; - - /// The type used to represent the output sequence as a list of buffers. - using mutable_buffers_type = implementation_defined; - -#else - class const_buffers_type; - class mutable_buffers_type; - - static_streambuf( - static_streambuf const& other) noexcept = delete; - - static_streambuf& operator=( - static_streambuf const&) noexcept = delete; - -#endif - - /// Return the size of the input sequence. - std::size_t - size() const - { - return out_ - in_; - } - - /// Return the maximum sum of the input and output sequence sizes. - std::size_t - max_size() const - { - return end_ - begin_; - } - - /// Return the maximum sum of input and output sizes that can be held without an allocation. - std::size_t - capacity() const - { - return end_ - in_; - } - - /** Get a list of buffers that represent the input sequence. - - @note These buffers remain valid across subsequent calls to `prepare`. - */ - const_buffers_type - data() const; - - /** Get a list of buffers that represent the output sequence, with the given size. - - @throws std::length_error if the size would exceed the limit - imposed by the underlying mutable buffer sequence. - - @note Buffers representing the input sequence acquired prior to - this call remain valid. - */ - mutable_buffers_type - prepare(std::size_t n); - - /** Move bytes from the output sequence to the input sequence. - - @note Buffers representing the input sequence acquired prior to - this call remain valid. - */ - void - commit(std::size_t n) - { - out_ += std::min(n, last_ - out_); - } - - /// Remove bytes from the input sequence. - void - consume(std::size_t n) - { - in_ += std::min(n, out_ - in_); - } - -#if GENERATING_DOCS -private: -#else -protected: -#endif - static_streambuf(std::uint8_t* p, std::size_t n) - { - reset(p, n); - } - - void - reset(std::uint8_t* p, std::size_t n) - { - begin_ = p; - in_ = p; - out_ = p; - last_ = p; - end_ = p + n; - } -}; - -//------------------------------------------------------------------------------ - -/** A `DynamicBuffer` with a fixed size internal buffer. - - @tparam N The number of bytes in the internal buffer. - - @note To reduce the number of template instantiations when passing - objects of this type in a deduced context, the signature of the - receiving function should use `static_streambuf` instead. -*/ -template -class static_streambuf_n - : public static_streambuf -#if ! GENERATING_DOCS - , private boost::base_from_member< - std::array> -#endif -{ - using member_type = boost::base_from_member< - std::array>; -public: -#if GENERATING_DOCS -private: -#endif - static_streambuf_n( - static_streambuf_n const&) = delete; - static_streambuf_n& operator=( - static_streambuf_n const&) = delete; -#if GENERATING_DOCS -public: -#endif - - /// Construct a static stream buffer. - static_streambuf_n() - : static_streambuf( - member_type::member.data(), - member_type::member.size()) - { - } - - /** Reset the stream buffer. - - Postconditions: - The input sequence and output sequence are empty, - `max_size()` returns `N`. - */ - void - reset() - { - static_streambuf::reset( - member_type::member.data(), - member_type::member.size()); - } -}; - -} // beast - -#include - -#endif diff --git a/src/beast/include/beast/core/static_string.hpp b/src/beast/include/beast/core/static_string.hpp index 6ba5332cac..0d0dc8f201 100644 --- a/src/beast/include/beast/core/static_string.hpp +++ b/src/beast/include/beast/core/static_string.hpp @@ -5,20 +5,24 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef BEAST_WEBSOCKET_STATIC_STRING_HPP -#define BEAST_WEBSOCKET_STATIC_STRING_HPP +#ifndef BEAST_STATIC_STRING_HPP +#define BEAST_STATIC_STRING_HPP #include -#include -#include +#include +#include +#include #include +#include #include +#include #include #include +#include namespace beast { -/** A string with a fixed-size storage area. +/** A modifiable string with a fixed-size storage area. These objects behave like `std::string` except that the storage is not dynamically allocated but rather fixed in size. @@ -27,6 +31,8 @@ namespace beast { imposes a natural small upper limit on the size of a value. @note The stored string is always null-terminated. + + @see @ref to_static_string */ template< std::size_t N, @@ -37,10 +43,20 @@ class static_string template friend class static_string; + void + term() + { + Traits::assign(s_[n_], 0); + } + std::size_t n_; - std::array s_; + CharT s_[N+1]; public: + // + // Member types + // + using traits_type = Traits; using value_type = typename Traits::char_type; using size_type = std::size_t; @@ -56,35 +72,204 @@ public: using const_reverse_iterator = std::reverse_iterator; - /** Default constructor. + /// The type of `string_view` returned by the interface + using string_view_type = + beast::basic_string_view; - The string is initially empty, and null terminated. - */ + // + // Constants + // + + /// Maximum size of the string excluding the null terminator + static std::size_t constexpr max_size_n = N; + + /// A special index + static constexpr size_type npos = size_type(-1); + + // + // (constructor) + // + + /// Default constructor (empty string). static_string(); + /** Construct with count copies of character `ch`. + + The behavior is undefined if `count >= npos` + */ + static_string(size_type count, CharT ch); + + /// Construct with a substring (pos, other.size()) of `other`. + template + static_string(static_string const& other, + size_type pos); + + /// Construct with a substring (pos, count) of `other`. + template + static_string(static_string const& other, + size_type pos, size_type count); + + /// Construct with the first `count` characters of `s`, including nulls. + static_string(CharT const* s, size_type count); + + /// Construct from a null terminated string. + static_string(CharT const* s); + + /// Construct from a range of characters + template + static_string(InputIt first, InputIt last); + /// Copy constructor. - static_string(static_string const& s); + static_string(static_string const& other); /// Copy constructor. template - static_string(static_string const& s); + static_string(static_string const& other); + + /// Construct from an initializer list + static_string(std::initializer_list init); + + /// Construct from a `string_view` + explicit + static_string(string_view_type sv); + + /** Construct from any object convertible to `string_view_type`. + + The range (pos, n) is extracted from the value + obtained by converting `t` to `string_view_type`, + and used to construct the string. + */ +#if BEAST_DOXYGEN + template +#else + template::value>::type> +#endif + static_string(T const& t, size_type pos, size_type n); + + // + // (assignment) + // /// Copy assignment. static_string& - operator=(static_string const& s); + operator=(static_string const& str) + { + return assign(str); + } /// Copy assignment. template static_string& - operator=(static_string const& s); + operator=(static_string const& str) + { + return assign(str); + } - /// Construct from string literal. - template - static_string(const CharT (&s)[M]); + /// Assign from null-terminated string. + static_string& + operator=(CharT const* s) + { + return assign(s); + } - /// Assign from string literal. + /// Assign from single character. + static_string& + operator=(CharT ch) + { + return assign_char(ch, + std::integral_constant0)>{}); + } + + /// Assign from initializer list. + static_string& + operator=(std::initializer_list init) + { + return assign(init); + } + + /// Assign from `string_view_type`. + static_string& + operator=(string_view_type sv) + { + return assign(sv); + } + + /// Assign `count` copies of `ch`. + static_string& + assign(size_type count, CharT ch); + + /// Assign from another `static_string` + static_string& + assign(static_string const& str); + + // VFALCO NOTE this could come in two flavors, + // N>M and NM + + /// Assign from another `static_string` template - static_string& operator=(const CharT (&s)[M]); + static_string& + assign(static_string const& str) + { + return assign(str.data(), str.size()); + } + + /// Assign `count` characterss starting at `npos` from `other`. + template + static_string& + assign(static_string const& str, + size_type pos, size_type count = npos); + + /// Assign the first `count` characters of `s`, including nulls. + static_string& + assign(CharT const* s, size_type count); + + /// Assign a null terminated string. + static_string& + assign(CharT const* s) + { + return assign(s, Traits::length(s)); + } + + /// Assign from an iterator range of characters. + template + static_string& + assign(InputIt first, InputIt last); + + /// Assign from initializer list. + static_string& + assign(std::initializer_list init) + { + return assign(init.begin(), init.end()); + } + + /// Assign from `string_view_type`. + static_string& + assign(string_view_type str) + { + return assign(str.data(), str.size()); + } + + /** Assign from any object convertible to `string_view_type`. + + The range (pos, n) is extracted from the value + obtained by converting `t` to `string_view_type`, + and used to assign the string. + */ + template +#if BEAST_DOXYGEN + static_string& +#else + typename std::enable_if::value, static_string&>::type +#endif + assign(T const& t, + size_type pos, size_type count = npos); + + // + // Element access + // /// Access specified character with bounds checking. reference @@ -154,9 +339,20 @@ public: CharT const* c_str() const { - return &s_[0]; + return data(); } + /// Convert a static string to a `string_view_type` + operator string_view_type() const + { + return basic_string_view< + CharT, Traits>{data(), size()}; + } + + // + // Iterators + // + /// Returns an iterator to the beginning. iterator begin() @@ -241,6 +437,10 @@ public: return const_reverse_iterator{cbegin()}; } + // + // Capacity + // + /// Returns `true` if the string is empty. bool empty() const @@ -255,6 +455,13 @@ public: return n_; } + /// Returns the number of characters, excluding the null terminator. + size_type + length() const + { + return size(); + } + /// Returns the maximum number of characters that can be stored, excluding the null terminator. size_type constexpr max_size() const @@ -262,23 +469,323 @@ public: return N; } + /** Reserves storage. + + This actually just throws an exception if `n > N`, + otherwise does nothing since the storage is fixed. + */ + void + reserve(std::size_t n); + /// Returns the number of characters that can be held in currently allocated storage. - size_type + size_type constexpr capacity() const { - return N; + return max_size(); } + + /** Reduces memory usage by freeing unused memory. + + This actually does nothing, since the storage is fixed. + */ + void + shrink_to_fit() + { + } + + // + // Operations + // /// Clears the contents. void - clear() + clear(); + + static_string& + insert(size_type index, size_type count, CharT ch); + + static_string& + insert(size_type index, CharT const* s) { - resize(0); + return insert(index, s, Traits::length(s)); } + static_string& + insert(size_type index, CharT const* s, size_type count); + + template + static_string& + insert(size_type index, + static_string const& str) + { + return insert(index, str.data(), str.size()); + } + + template + static_string& + insert(size_type index, + static_string const& str, + size_type index_str, size_type count = npos); + + iterator + insert(const_iterator pos, CharT ch) + { + return insert(pos, 1, ch); + } + + iterator + insert(const_iterator pos, size_type count, CharT ch); + + template +#if BEAST_DOXYGEN + iterator +#else + typename std::enable_if< + detail::is_input_iterator::value, + iterator>::type +#endif + insert(const_iterator pos, InputIt first, InputIt last); + + iterator + insert(const_iterator pos, std::initializer_list init) + { + return insert(pos, init.begin(), init.end()); + } + + static_string& + insert(size_type index, string_view_type str) + { + return insert(index, str.data(), str.size()); + } + + template +#if BEAST_DOXYGEN + static_string& +#else + typename std::enable_if< + std::is_convertible::value && + ! std::is_convertible::value, + static_string&>::type +#endif + insert(size_type index, T const& t, + size_type index_str, size_type count = npos); + + static_string& + erase(size_type index = 0, size_type count = npos); + + iterator + erase(const_iterator pos); + + iterator + erase(const_iterator first, const_iterator last); + + void + push_back(CharT ch); + + void + pop_back() + { + Traits::assign(s_[--n_], 0); + } + + static_string& + append(size_type count, CharT ch) + { + insert(end(), count, ch); + return *this; + } + + template + static_string& + append(static_string const& str) + { + insert(size(), str); + return *this; + } + + template + static_string& + append(static_string const& str, + size_type pos, size_type count = npos); + + static_string& + append(CharT const* s, size_type count) + { + insert(size(), s, count); + return *this; + } + + static_string& + append(CharT const* s) + { + insert(size(), s); + return *this; + } + + template +#if BEAST_DOXYGEN + static_string& +#else + typename std::enable_if< + detail::is_input_iterator::value, + static_string&>::type +#endif + append(InputIt first, InputIt last) + { + insert(end(), first, last); + return *this; + } + + static_string& + append(std::initializer_list init) + { + insert(end(), init); + return *this; + } + + static_string& + append(string_view_type sv) + { + insert(size(), sv); + return *this; + } + + template + typename std::enable_if< + std::is_convertible::value && + ! std::is_convertible::value, + static_string&>::type + append(T const& t, size_type pos, size_type count = npos) + { + insert(size(), t, pos, count); + return *this; + } + + template + static_string& + operator+=(static_string const& str) + { + return append(str.data(), str.size()); + } + + static_string& + operator+=(CharT ch) + { + push_back(ch); + return *this; + } + + static_string& + operator+=(CharT const* s) + { + return append(s); + } + + static_string& + operator+=(std::initializer_list init) + { + return append(init); + } + + static_string& + operator+=(string_view_type const& str) + { + return append(str); + } + + template + int + compare(static_string const& str) const + { + return detail::lexicographical_compare( + &s_[0], n_, &str.s_[0], str.n_); + } + + template + int + compare(size_type pos1, size_type count1, + static_string const& str) const + { + return detail::lexicographical_compare( + substr(pos1, count1), str.data(), str.size()); + } + + template + int + compare(size_type pos1, size_type count1, + static_string const& str, + size_type pos2, size_type count2 = npos) const + { + return detail::lexicographical_compare( + substr(pos1, count1), str.substr(pos2, count2)); + } + + int + compare(CharT const* s) const + { + return detail::lexicographical_compare( + &s_[0], n_, s, Traits::length(s)); + } + + int + compare(size_type pos1, size_type count1, + CharT const* s) const + { + return detail::lexicographical_compare( + substr(pos1, count1), s, Traits::length(s)); + } + + int + compare(size_type pos1, size_type count1, + CharT const*s, size_type count2) const + { + return detail::lexicographical_compare( + substr(pos1, count1), s, count2); + } + + int + compare(string_view_type str) const + { + return detail::lexicographical_compare( + &s_[0], n_, str.data(), str.size()); + } + + int + compare(size_type pos1, size_type count1, + string_view_type str) const + { + return detail::lexicographical_compare( + substr(pos1, count1), str); + } + + template +#if BEAST_DOXYGEN + int +#else + typename std::enable_if< + std::is_convertible::value && + ! std::is_convertible::value, + int>::type +#endif + compare(size_type pos1, size_type count1, + T const& t, size_type pos2, + size_type count2 = npos) const + { + return compare(pos1, count1, + string_view_type(t).substr(pos2, count2)); + } + + string_view_type + substr(size_type pos = 0, size_type count = npos) const; + + /// Copy a substring (pos, pos+count) to character string pointed to by `dest`. + size_type + copy(CharT* dest, size_type count, size_type pos = 0) const; + /** Changes the number of characters stored. - @note No value-initialization is performed. + If the resulting string is larger, the new + characters are uninitialized. */ void resize(std::size_t n); @@ -291,231 +798,66 @@ public: void resize(std::size_t n, CharT c); - /// Compare two character sequences. - template - int - compare(static_string const& rhs) const; + /// Exchange the contents of this string with another. + void + swap(static_string& str); - /// Return the characters as a `basic_string`. - std::basic_string - to_string() const - { - return std::basic_string< - CharT, Traits>{&s_[0], n_}; - } + /// Exchange the contents of this string with another. + template + void + swap(static_string& str); + + // + // Search + // private: - void - assign(CharT const* s); + static_string& + assign_char(CharT ch, std::true_type); + + static_string& + assign_char(CharT ch, std::false_type); }; -template -static_string:: -static_string() - : n_(0) -{ - s_[0] = 0; -} +// +// Disallowed operations +// -template -static_string:: -static_string(static_string const& s) - : n_(s.n_) -{ - Traits::copy(&s_[0], &s.s_[0], n_ + 1); -} +// These operations are explicitly deleted since +// there is no reasonable implementation possible. -template -template -static_string:: -static_string(static_string const& s) -{ - if(s.size() > N) - throw detail::make_exception( - "static_string overflow", __FILE__, __LINE__); - n_ = s.size(); - Traits::copy(&s_[0], &s.s_[0], n_ + 1); -} - -template -auto -static_string:: -operator=(static_string const& s) -> - static_string& -{ - n_ = s.n_; - Traits::copy(&s_[0], &s.s_[0], n_ + 1); - return *this; -} - -template -template -auto -static_string:: -operator=(static_string const& s) -> - static_string& -{ - if(s.size() > N) - throw detail::make_exception( - "static_string overflow", __FILE__, __LINE__); - n_ = s.size(); - Traits::copy(&s_[0], &s.s_[0], n_ + 1); - return *this; -} - -template -template -static_string:: -static_string(const CharT (&s)[M]) - : n_(M-1) -{ - static_assert(M-1 <= N, - "static_string overflow"); - Traits::copy(&s_[0], &s[0], M); -} - -template -template -auto -static_string:: -operator=(const CharT (&s)[M]) -> - static_string& -{ - static_assert(M-1 <= N, - "static_string overflow"); - n_ = M-1; - Traits::copy(&s_[0], &s[0], M); - return *this; -} - -template -auto -static_string:: -at(size_type pos) -> - reference -{ - if(pos >= n_) - throw detail::make_exception( - "invalid pos", __FILE__, __LINE__); - return s_[pos]; -} - -template -auto -static_string:: -at(size_type pos) const -> - const_reference -{ - if(pos >= n_) - throw detail::make_exception( - "static_string::at", __FILE__, __LINE__); - return s_[pos]; -} +template +void +operator+( + static_stringconst& lhs, + static_stringconst& rhs) = delete; template void -static_string:: -resize(std::size_t n) -{ - if(n > N) - throw detail::make_exception( - "static_string overflow", __FILE__, __LINE__); - n_ = n; - s_[n_] = 0; -} +operator+(CharT const* lhs, + static_stringconst& rhs) = delete; template void -static_string:: -resize(std::size_t n, CharT c) -{ - if(n > N) - throw detail::make_exception( - "static_string overflow", __FILE__, __LINE__); - if(n > n_) - Traits::assign(&s_[n_], n - n_, c); - n_ = n; - s_[n_] = 0; -} - -template -template -int -static_string:: -compare(static_string const& rhs) const -{ - if(size() < rhs.size()) - { - auto const v = Traits::compare( - data(), rhs.data(), size()); - if(v == 0) - return -1; - return v; - } - else if(size() > rhs.size()) - { - auto const v = Traits::compare( - data(), rhs.data(), rhs.size()); - if(v == 0) - return 1; - return v; - } - return Traits::compare(data(), rhs.data(), size()); -} +operator+(CharT lhs, + static_string const& rhs) = delete; template void -static_string:: -assign(CharT const* s) -{ - auto const n = Traits::length(s); - if(n > N) - throw detail::make_exception( - "too large", __FILE__, __LINE__); - n_ = n; - Traits::copy(&s_[0], s, n_ + 1); -} +operator+(static_string const& lhs, + CharT const* rhs) = delete; -namespace detail { +template +void +operator+(static_string const& lhs, + CharT rhs) = delete; -template -int -compare( - static_string const& lhs, - const CharT (&s)[M]) -{ - if(lhs.size() < M-1) - { - auto const v = Traits::compare( - lhs.data(), &s[0], lhs.size()); - if(v == 0) - return -1; - return v; - } - else if(lhs.size() > M-1) - { - auto const v = Traits::compare( - lhs.data(), &s[0], M-1); - if(v == 0) - return 1; - return v; - } - return Traits::compare(lhs.data(), &s[0], lhs.size()); -} +// +// Non-member functions +// -template -inline -int -compare( - const CharT (&s)[M], - static_string const& rhs) -{ - return -compare(rhs, s); -} - -} // detail - -template +template bool operator==( static_string const& lhs, @@ -524,7 +866,8 @@ operator==( return lhs.compare(rhs) == 0; } -template +template bool operator!=( static_string const& lhs, @@ -533,7 +876,8 @@ operator!=( return lhs.compare(rhs) != 0; } -template +template bool operator<( static_string const& lhs, @@ -542,7 +886,8 @@ operator<( return lhs.compare(rhs) < 0; } -template +template bool operator<=( static_string const& lhs, @@ -551,7 +896,8 @@ operator<=( return lhs.compare(rhs) <= 0; } -template +template bool operator>( static_string const& lhs, @@ -560,7 +906,8 @@ operator>( return lhs.compare(rhs) > 0; } -template +template bool operator>=( static_string const& lhs, @@ -569,116 +916,193 @@ operator>=( return lhs.compare(rhs) >= 0; } -//--- - -template +template bool operator==( - const CharT (&s)[N], - static_string const& rhs) + CharT const* lhs, + static_string const& rhs) { - return detail::compare(s, rhs) == 0; + return detail::lexicographical_compare( + lhs, Traits::length(lhs), + rhs.data(), rhs.size()) == 0; } -template +template bool operator==( static_string const& lhs, - const CharT (&s)[M]) + CharT const* rhs) { - return detail::compare(lhs, s) == 0; + return detail::lexicographical_compare( + lhs.data(), lhs.size(), + rhs, Traits::length(rhs)) == 0; } -template +template bool operator!=( - const CharT (&s)[N], - static_string const& rhs) + CharT const* lhs, + static_string const& rhs) { - return detail::compare(s, rhs) != 0; + return detail::lexicographical_compare( + lhs, Traits::length(lhs), + rhs.data(), rhs.size()) != 0; } -template +template bool operator!=( static_string const& lhs, - const CharT (&s)[M]) + CharT const* rhs) { - return detail::compare(lhs, s) != 0; + return detail::lexicographical_compare( + lhs.data(), lhs.size(), + rhs, Traits::length(rhs)) != 0; } -template +template bool operator<( - const CharT (&s)[N], - static_string const& rhs) + CharT const* lhs, + static_string const& rhs) { - return detail::compare(s, rhs) < 0; + return detail::lexicographical_compare( + lhs, Traits::length(lhs), + rhs.data(), rhs.size()) < 0; } -template +template bool operator<( static_string const& lhs, - const CharT (&s)[M]) + CharT const* rhs) { - return detail::compare(lhs, s) < 0; + return detail::lexicographical_compare( + lhs.data(), lhs.size(), + rhs, Traits::length(rhs)) < 0; } -template +template bool operator<=( - const CharT (&s)[N], - static_string const& rhs) + CharT const* lhs, + static_string const& rhs) { - return detail::compare(s, rhs) <= 0; + return detail::lexicographical_compare( + lhs, Traits::length(lhs), + rhs.data(), rhs.size()) <= 0; } -template +template bool operator<=( static_string const& lhs, - const CharT (&s)[M]) + CharT const* rhs) { - return detail::compare(lhs, s) <= 0; + return detail::lexicographical_compare( + lhs.data(), lhs.size(), + rhs, Traits::length(rhs)) <= 0; } -template +template bool operator>( - const CharT (&s)[N], - static_string const& rhs) + CharT const* lhs, + static_string const& rhs) { - return detail::compare(s, rhs) > 0; + return detail::lexicographical_compare( + lhs, Traits::length(lhs), + rhs.data(), rhs.size()) > 0; } -template +template bool operator>( static_string const& lhs, - const CharT (&s)[M]) + CharT const* rhs) { - return detail::compare(lhs, s) > 0; + return detail::lexicographical_compare( + lhs.data(), lhs.size(), + rhs, Traits::length(rhs)) > 0; } -template +template bool operator>=( - const CharT (&s)[N], - static_string const& rhs) + CharT const* lhs, + static_string const& rhs) { - return detail::compare(s, rhs) >= 0; + return detail::lexicographical_compare( + lhs, Traits::length(lhs), + rhs.data(), rhs.size()) >= 0; } -template +template bool operator>=( static_string const& lhs, - const CharT (&s)[M]) + CharT const* rhs) { - return detail::compare(lhs, s) >= 0; + return detail::lexicographical_compare( + lhs.data(), lhs.size(), + rhs, Traits::length(rhs)) >= 0; } +// +// swap +// + +template +void +swap( + static_string& lhs, + static_string& rhs) +{ + lhs.swap(rhs); +} + +template +void +swap( + static_string& lhs, + static_string& rhs) +{ + lhs.swap(rhs); +} + +// +// Input/Output +// + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, + static_string const& str) +{ + return os << static_cast< + beast::basic_string_view>(str); +} + +// +// Numeric conversions +// + +/** Returns a static string representing an integer as a decimal. + + @param x The signed or unsigned integer to convert. + This must be an integral type. + + @return A @ref static_string with an implementation defined + maximum size large enough to hold the longest possible decimal + representation of any integer of the given type. +*/ +template +static_string +to_static_string(Integer x); + } // beast +#include + #endif diff --git a/src/beast/include/beast/core/stream_concepts.hpp b/src/beast/include/beast/core/stream_concepts.hpp deleted file mode 100644 index 5f0ba3adfa..0000000000 --- a/src/beast/include/beast/core/stream_concepts.hpp +++ /dev/null @@ -1,77 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_STREAM_CONCEPTS_HPP -#define BEAST_STREAM_CONCEPTS_HPP - -#include -#include -#include - -namespace beast { - -/// Determine if `T` has the `get_io_service` member. -template -#if GENERATING_DOCS -struct has_get_io_service : std::integral_constant{}; -#else -using has_get_io_service = typename detail::has_get_io_service::type; -#endif - -/// Determine if `T` meets the requirements of @b `AsyncReadStream`. -template -#if GENERATING_DOCS -struct is_AsyncReadStream : std::integral_constant{}; -#else -using is_AsyncReadStream = typename detail::is_AsyncReadStream::type; -#endif - -/// Determine if `T` meets the requirements of @b `AsyncWriteStream`. -template -#if GENERATING_DOCS -struct is_AsyncWriteStream : std::integral_constant{}; -#else -using is_AsyncWriteStream = typename detail::is_AsyncWriteStream::type; -#endif - -/// Determine if `T` meets the requirements of @b `SyncReadStream`. -template -#if GENERATING_DOCS -struct is_SyncReadStream : std::integral_constant{}; -#else -using is_SyncReadStream = typename detail::is_SyncReadStream::type; -#endif - -/// Determine if `T` meets the requirements of @b `SyncWriterStream`. -template -#if GENERATING_DOCS -struct is_SyncWriteStream : std::integral_constant{}; -#else -using is_SyncWriteStream = typename detail::is_SyncWriteStream::type; -#endif - -/// Determine if `T` meets the requirements of @b `AsyncStream`. -template -#if GENERATING_DOCS -struct is_AsyncStream : std::integral_constant{}; -#else -using is_AsyncStream = std::integral_constant::value && is_AsyncWriteStream::value>; -#endif - -/// Determine if `T` meets the requirements of @b `SyncStream`. -template -#if GENERATING_DOCS -struct is_SyncStream : std::integral_constant{}; -#else -using is_SyncStream = std::integral_constant::value && is_SyncWriteStream::value>; -#endif - -} // beast - -#endif diff --git a/src/beast/include/beast/core/streambuf.hpp b/src/beast/include/beast/core/streambuf.hpp deleted file mode 100644 index 537094c1a4..0000000000 --- a/src/beast/include/beast/core/streambuf.hpp +++ /dev/null @@ -1,345 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_STREAMBUF_HPP -#define BEAST_STREAMBUF_HPP - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { - -/** A @b `DynamicBuffer` that uses multiple buffers internally. - - The implementation uses a sequence of one or more character arrays - of varying sizes. Additional character array objects are appended to - the sequence to accommodate changes in the size of the character - sequence. - - @note Meets the requirements of @b DynamicBuffer. - - @tparam Allocator The allocator to use for managing memory. -*/ -template -class basic_streambuf -#if ! GENERATING_DOCS - : private detail::empty_base_optimization< - typename std::allocator_traits:: - template rebind_alloc> -#endif -{ -public: -#if GENERATING_DOCS - /// The type of allocator used. - using allocator_type = Allocator; -#else - using allocator_type = typename - std::allocator_traits:: - template rebind_alloc; -#endif - -private: - // Storage for the list of buffers representing the input - // and output sequences. The allocation for each element - // contains `element` followed by raw storage bytes. - class element; - - using alloc_traits = std::allocator_traits; - using list_type = typename boost::intrusive::make_list>::type; - using iterator = typename list_type::iterator; - using const_iterator = typename list_type::const_iterator; - - using size_type = typename std::allocator_traits::size_type; - using const_buffer = boost::asio::const_buffer; - using mutable_buffer = boost::asio::mutable_buffer; - - static_assert(std::is_base_of::iterator_category>::value, - "BidirectionalIterator requirements not met"); - - static_assert(std::is_base_of::iterator_category>::value, - "BidirectionalIterator requirements not met"); - - list_type list_; // list of allocated buffers - iterator out_; // element that contains out_pos_ - size_type alloc_size_; // min amount to allocate - size_type in_size_ = 0; // size of the input sequence - size_type in_pos_ = 0; // input offset in list_.front() - size_type out_pos_ = 0; // output offset in *out_ - size_type out_end_ = 0; // output end offset in list_.back() - -public: -#if GENERATING_DOCS - /// The type used to represent the input sequence as a list of buffers. - using const_buffers_type = implementation_defined; - - /// The type used to represent the output sequence as a list of buffers. - using mutable_buffers_type = implementation_defined; - -#else - class const_buffers_type; - - class mutable_buffers_type; - -#endif - - /// Destructor. - ~basic_streambuf(); - - /** Move constructor. - - The new object will have the input sequence of - the other stream buffer, and an empty output sequence. - - @note After the move, the moved-from object will have - an empty input and output sequence, with no internal - buffers allocated. - */ - basic_streambuf(basic_streambuf&&); - - /** Move constructor. - - The new object will have the input sequence of - the other stream buffer, and an empty output sequence. - - @note After the move, the moved-from object will have - an empty input and output sequence, with no internal - buffers allocated. - - @param alloc The allocator to associate with the - stream buffer. - */ - basic_streambuf(basic_streambuf&&, - allocator_type const& alloc); - - /** Move assignment. - - This object will have the input sequence of - the other stream buffer, and an empty output sequence. - - @note After the move, the moved-from object will have - an empty input and output sequence, with no internal - buffers allocated. - */ - basic_streambuf& - operator=(basic_streambuf&&); - - /** Copy constructor. - - This object will have a copy of the other stream - buffer's input sequence, and an empty output sequence. - */ - basic_streambuf(basic_streambuf const&); - - /** Copy constructor. - - This object will have a copy of the other stream - buffer's input sequence, and an empty output sequence. - - @param alloc The allocator to associate with the - stream buffer. - */ - basic_streambuf(basic_streambuf const&, - allocator_type const& alloc); - - /** Copy assignment. - - This object will have a copy of the other stream - buffer's input sequence, and an empty output sequence. - */ - basic_streambuf& operator=(basic_streambuf const&); - - /** Copy constructor. - - This object will have a copy of the other stream - buffer's input sequence, and an empty output sequence. - */ - template - basic_streambuf(basic_streambuf const&); - - /** Copy constructor. - - This object will have a copy of the other stream - buffer's input sequence, and an empty output sequence. - - @param alloc The allocator to associate with the - stream buffer. - */ - template - basic_streambuf(basic_streambuf const&, - allocator_type const& alloc); - - /** Copy assignment. - - This object will have a copy of the other stream - buffer's input sequence, and an empty output sequence. - */ - template - basic_streambuf& operator=(basic_streambuf const&); - - /** Construct a stream buffer. - - @param alloc_size The size of buffer to allocate. This is a - soft limit, calls to prepare for buffers exceeding this size - will allocate the larger size. The default allocation size - is 1KB (1024 bytes). - - @param alloc The allocator to use. If this parameter is - unspecified, a default constructed allocator will be used. - */ - explicit - basic_streambuf(std::size_t alloc_size = 1024, - Allocator const& alloc = allocator_type{}); - - /// Returns a copy of the associated allocator. - allocator_type - get_allocator() const - { - return this->member(); - } - - /** Returns the default allocation size. - - This is the smallest size that the stream buffer will allocate. - The size of the allocation can influence capacity, which will - affect algorithms that use capacity to efficiently read from - streams. - */ - std::size_t - alloc_size() const - { - return alloc_size_; - } - - /** Set the default allocation size. - - This is the smallest size that the stream buffer will allocate. - The size of the allocation can influence capacity, which will - affect algorithms that use capacity to efficiently read from - streams. - - @note This will not affect any already-existing allocations. - - @param n The number of bytes. - */ - void - alloc_size(std::size_t n) - { - alloc_size_ = n; - } - - /// Returns the size of the input sequence. - size_type - size() const - { - return in_size_; - } - - /// Returns the permitted maximum sum of the sizes of the input and output sequence. - size_type - max_size() const - { - return (std::numeric_limits::max)(); - } - - /// Returns the maximum sum of the sizes of the input sequence and output sequence the buffer can hold without requiring reallocation. - std::size_t - capacity() const; - - /** Get a list of buffers that represents the input sequence. - - @note These buffers remain valid across subsequent calls to `prepare`. - */ - const_buffers_type - data() const; - - /** Get a list of buffers that represents the output sequence, with the given size. - - @note Buffers representing the input sequence acquired prior to - this call remain valid. - */ - mutable_buffers_type - prepare(size_type n); - - /** Move bytes from the output sequence to the input sequence. - - @note Buffers representing the input sequence acquired prior to - this call remain valid. - */ - void - commit(size_type n); - - /// Remove bytes from the input sequence. - void - consume(size_type n); - - // Helper for boost::asio::read_until - template - friend - std::size_t - read_size_helper(basic_streambuf< - OtherAllocator> const& streambuf, std::size_t max_size); - -private: - void - clear(); - - void - move_assign(basic_streambuf& other, std::false_type); - - void - move_assign(basic_streambuf& other, std::true_type); - - void - copy_assign(basic_streambuf const& other, std::false_type); - - void - copy_assign(basic_streambuf const& other, std::true_type); - - void - delete_list(); - - void - debug_check() const; -}; - -/** A @b `DynamicBuffer` that uses multiple buffers internally. - - The implementation uses a sequence of one or more character arrays - of varying sizes. Additional character array objects are appended to - the sequence to accommodate changes in the size of the character - sequence. - - @note Meets the requirements of @b `DynamicBuffer`. -*/ -using streambuf = basic_streambuf>; - -/** Format output to a @ref basic_streambuf. - - @param streambuf The @ref basic_streambuf to write to. - - @param t The object to write. - - @return A reference to the @ref basic_streambuf. -*/ -template -basic_streambuf& -operator<<(basic_streambuf& streambuf, T const& t); - -} // beast - -#include - -#endif diff --git a/src/beast/include/beast/core/string.hpp b/src/beast/include/beast/core/string.hpp new file mode 100644 index 0000000000..601160982b --- /dev/null +++ b/src/beast/include/beast/core/string.hpp @@ -0,0 +1,151 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_STRING_HPP +#define BEAST_STRING_HPP + +#include +#include +#ifndef BEAST_NO_BOOST_STRING_VIEW +# if BOOST_VERSION >= 106400 +# define BEAST_NO_BOOST_STRING_VIEW 0 +# else +# define BEAST_NO_BOOST_STRING_VIEW 1 +# endif +#endif + +#if BEAST_NO_BOOST_STRING_VIEW +#include +#else +#include +#endif + +#include + +namespace beast { + +#if BEAST_NO_BOOST_STRING_VIEW +/// The type of string view used by the library +using string_view = boost::string_ref; + +/// The type of basic string view used by the library +template +using basic_string_view = + boost::basic_string_ref; +#else +/// The type of string view used by the library +using string_view = boost::string_view; + +/// The type of basic string view used by the library +template +using basic_string_view = + boost::basic_string_view; +#endif + +namespace detail { + +inline +char +ascii_tolower(char c) +{ + if(c >= 'A' && c <= 'Z') + c += 'a' - 'A'; + return c; +} + +template +bool +iequals( + beast::string_view const& lhs, + beast::string_view const& rhs) +{ + auto n = lhs.size(); + if(rhs.size() != n) + return false; + auto p1 = lhs.data(); + auto p2 = rhs.data(); + char a, b; + while(n--) + { + a = *p1++; + b = *p2++; + if(a != b) + goto slow; + } + return true; + + while(n--) + { + slow: + if(ascii_tolower(a) != ascii_tolower(b)) + return false; + a = *p1++; + b = *p2++; + } + return true; +} + +} // detail + +/** Returns `true` if two strings are equal, using a case-insensitive comparison. + + The case-comparison operation is defined only for low-ASCII characters. + + @param lhs The string on the left side of the equality + + @param rhs The string on the right side of the equality +*/ +inline +bool +iequals( + beast::string_view const& lhs, + beast::string_view const& rhs) +{ + return detail::iequals(lhs, rhs); +} + +/** A case-insensitive less predicate for strings. + + The case-comparison operation is defined only for low-ASCII characters. +*/ +struct iless +{ + bool + operator()( + string_view const& lhs, + string_view const& rhs) const + { + using std::begin; + using std::end; + return std::lexicographical_compare( + begin(lhs), end(lhs), begin(rhs), end(rhs), + [](char c1, char c2) + { + return detail::ascii_tolower(c1) < detail::ascii_tolower(c2); + } + ); + } +}; + +/** A case-insensitive equality predicate for strings. + + The case-comparison operation is defined only for low-ASCII characters. +*/ +struct iequal +{ + bool + operator()( + string_view const& lhs, + string_view const& rhs) const + { + return iequals(lhs, rhs); + } +}; + +} // beast + +#endif diff --git a/src/beast/include/beast/core/string_param.hpp b/src/beast/include/beast/core/string_param.hpp new file mode 100644 index 0000000000..dedd060aae --- /dev/null +++ b/src/beast/include/beast/core/string_param.hpp @@ -0,0 +1,109 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_STRING_PARAM_HPP +#define BEAST_STRING_PARAM_HPP + +#include +#include +#include +#include +#include +#include + +namespace beast { + +/** A function parameter which efficiently converts to string. + + This is used as a function parameter type to allow callers + notational convenience: objects other than strings may be + passed in contexts where a string is expected. The conversion + to string is made using `operator<<` to a non-dynamically + allocated static buffer if possible, else to a `std::string` + on overflow. +*/ +class string_param +{ + string_view sv_; + char buf_[128]; + boost::optional os_; + + template + typename std::enable_if< + std::is_integral::value>::type + print(T const&); + + template + typename std::enable_if< + ! std::is_integral::value && + ! std::is_convertible::value + >::type + print(T const&); + + void + print(string_view const&); + + template + typename std::enable_if< + std::is_integral::value>::type + print_1(T const&); + + template + typename std::enable_if< + ! std::is_integral::value>::type + print_1(T const&); + + void + print_n() + { + } + + template + void + print_n(T0 const&, TN const&...); + + template + void + print(T0 const&, T1 const&, TN const&...); + +public: + /// Copy constructor (disallowed) + string_param(string_param const&) = delete; + + /// Copy assignment (disallowed) + string_param& operator=(string_param const&) = delete; + + /** Constructor + + This function constructs a string as if by concatenating + the result of streaming each argument in order into an + output stream. It is used as a notational convenience + at call sites which expect a parameter with the semantics + of a @ref string_view. + + The implementation uses a small, internal static buffer + to avoid memory allocations especially for the case where + the list of arguments to be converted consists of a single + integral type. + + @param args One or more arguments to convert + */ + template + string_param(Args const&... args); + + /// Implicit conversion to @ref string_view + operator string_view const() const + { + return sv_; + } +}; + +} // beast + +#include + +#endif diff --git a/src/beast/include/beast/core/to_string.hpp b/src/beast/include/beast/core/to_string.hpp deleted file mode 100644 index e391ea2d57..0000000000 --- a/src/beast/include/beast/core/to_string.hpp +++ /dev/null @@ -1,53 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_TO_STRING_HPP -#define BEAST_TO_STRING_HPP - -#include -#include -#include -#include - -namespace beast { - -/** Convert a @b `ConstBufferSequence` to a `std::string`. - - This function will convert the octets in a buffer sequence to a string. - All octets will be inserted into the resulting string, including null - or unprintable characters. - - @param buffers The buffer sequence to convert. - - @return A string representing the contents of the input area. - - @note This function participates in overload resolution only if - the buffers parameter meets the requirements of @b `ConstBufferSequence`. -*/ -template -#if GENERATING_DOCS -std::string -#else -typename std::enable_if< - is_ConstBufferSequence::value, - std::string>::type -#endif -to_string(ConstBufferSequence const& buffers) -{ - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - std::string s; - s.reserve(buffer_size(buffers)); - for(auto const& buffer : buffers) - s.append(buffer_cast(buffer), - buffer_size(buffer)); - return s; -} - -} // beast - -#endif diff --git a/src/beast/include/beast/core/type_traits.hpp b/src/beast/include/beast/core/type_traits.hpp new file mode 100644 index 0000000000..2624ee6680 --- /dev/null +++ b/src/beast/include/beast/core/type_traits.hpp @@ -0,0 +1,633 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_TYPE_TRAITS_HPP +#define BEAST_TYPE_TRAITS_HPP + +#include +#include +#include +#include +#include + +namespace beast { + +//------------------------------------------------------------------------------ +// +// Buffer concepts +// +//------------------------------------------------------------------------------ + +/** Determine if `T` meets the requirements of @b ConstBufferSequence. + + Metafunctions are used to perform compile time checking of template + types. This type will be `std::true_type` if `T` meets the requirements, + else the type will be `std::false_type`. + + @par Example + + Use with `static_assert`: + + @code + template + void f(ConstBufferSequence const& buffers) + { + static_assert(is_const_buffer_sequence::value, + "ConstBufferSequence requirements not met"); + ... + @endcode + + Use with `std::enable_if` (SFINAE): + + @code + template + typename std::enable_if::value>::type + f(ConstBufferSequence const& buffers); + @endcode +*/ +template +#if BEAST_DOXYGEN +struct is_const_buffer_sequence : std::integral_constant +#else +struct is_const_buffer_sequence : + detail::is_buffer_sequence +#endif +{ +}; + +/** Determine if `T` meets the requirements of @b MutableBufferSequence. + + Metafunctions are used to perform compile time checking of template + types. This type will be `std::true_type` if `T` meets the requirements, + else the type will be `std::false_type`. + + @par Example + + Use with `static_assert`: + + @code + template + void f(MutableBufferSequence const& buffers) + { + static_assert(is_const_buffer_sequence::value, + "MutableBufferSequence requirements not met"); + ... + @endcode + + Use with `std::enable_if` (SFINAE): + + @code + template + typename std::enable_if::value>::type + f(MutableBufferSequence const& buffers); + @endcode +*/ +template +#if BEAST_DOXYGEN +struct is_mutable_buffer_sequence : std::integral_constant +#else +struct is_mutable_buffer_sequence : + detail::is_buffer_sequence +#endif +{ +}; + +/** Determine if `T` meets the requirements of @b DynamicBuffer. + + Metafunctions are used to perform compile time checking of template + types. This type will be `std::true_type` if `T` meets the requirements, + else the type will be `std::false_type`. + + @par Example + + Use with `static_assert`: + + @code + template + void f(DynamicBuffer& buffer) + { + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + ... + @endcode + + Use with `std::enable_if` (SFINAE): + + @code + template + typename std::enable_if::value>::type + f(DynamicBuffer const& buffer); + @endcode +*/ +#if BEAST_DOXYGEN +template +struct is_dynamic_buffer : std::integral_constant {}; +#else +template +struct is_dynamic_buffer : std::false_type {}; + +template +struct is_dynamic_buffer() = + std::declval().size(), + std::declval() = + std::declval().max_size(), + std::declval() = + std::declval().capacity(), + std::declval().commit(std::declval()), + std::declval().consume(std::declval()), + (void)0)> > : std::integral_constant::value && + is_mutable_buffer_sequence< + typename T::mutable_buffers_type>::value && + std::is_same().data())>::value && + std::is_same().prepare( + std::declval()))>::value + > +{ +}; + +// Special case for Boost.Asio which doesn't adhere to +// net-ts but still provides a read_size_helper so things work +template +struct is_dynamic_buffer< + boost::asio::basic_streambuf> : std::true_type +{ +}; +#endif + +//------------------------------------------------------------------------------ +// +// Handler concepts +// +//------------------------------------------------------------------------------ + +/** Determine if `T` meets the requirements of @b CompletionHandler. + + This trait checks whether a type meets the requirements for a completion + handler, and is also callable with the specified signature. + Metafunctions are used to perform compile time checking of template + types. This type will be `std::true_type` if `T` meets the requirements, + else the type will be `std::false_type`. + + @par Example + + Use with `static_assert`: + + @code + struct handler + { + void operator()(error_code&); + }; + + static_assert(is_completion_handler::value, + "Not a completion handler"); + @endcode +*/ +template +#if BEAST_DOXYGEN +using is_completion_handler = std::integral_constant; +#else +using is_completion_handler = std::integral_constant::type>::value && + detail::is_invocable::value>; +#endif + +//------------------------------------------------------------------------------ +// +// Stream concepts +// +//------------------------------------------------------------------------------ + +/** Determine if `T` has the `get_io_service` member. + + Metafunctions are used to perform compile time checking of template + types. This type will be `std::true_type` if `T` has the member + function with the correct signature, else type will be `std::false_type`. + + @par Example + + Use with tag dispatching: + + @code + template + void maybe_hello(T& t, std::true_type) + { + t.get_io_service().post([]{ std::cout << "Hello, world!" << std::endl; }); + } + + template + void maybe_hello(T&, std::false_type) + { + // T does not have get_io_service + } + + template + void maybe_hello(T& t) + { + maybe_hello(t, has_get_io_service{}); + } + @endcode + + Use with `static_assert`: + + @code + struct stream + { + boost::asio::io_service& get_io_service(); + }; + + static_assert(has_get_io_service::value, + "Missing get_io_service member"); + @endcode +*/ +#if BEAST_DOXYGEN +template +struct has_get_io_service : std::integral_constant{}; +#else +template +struct has_get_io_service : std::false_type {}; + +template +struct has_get_io_service( + std::declval().get_io_service()), + (void)0)>> : std::true_type {}; +#endif + +/** Returns `T::lowest_layer_type` if it exists, else `T` + + This will contain a nested `type` equal to `T::lowest_layer_type` + if it exists, else `type` will be equal to `T`. + + @par Example + + Declaring a wrapper: + + @code + template + struct stream_wrapper + { + using next_layer_type = typename std::remove_reference::type; + using lowest_layer_type = typename get_lowest_layer::type; + }; + @endcode + + Defining a metafunction: + + @code + /// Alias for `std::true_type` if `T` wraps another stream + template + using is_stream_wrapper : std::integral_constant::type>::value> {}; + @endcode +*/ +#if BEAST_DOXYGEN +template +struct get_lowest_layer; +#else +template +struct get_lowest_layer +{ + using type = T; +}; + +template +struct get_lowest_layer> +{ + using type = typename T::lowest_layer_type; +}; +#endif + +/** Determine if `T` meets the requirements of @b AsyncReadStream. + + Metafunctions are used to perform compile time checking of template + types. This type will be `std::true_type` if `T` meets the requirements, + else the type will be `std::false_type`. + + @par Example + + Use with `static_assert`: + + @code + template + void f(AsyncReadStream& stream) + { + static_assert(is_async_read_stream::value, + "AsyncReadStream requirements not met"); + ... + @endcode + + Use with `std::enable_if` (SFINAE): + + @code + template + typename std::enable_if::value>::type + f(AsyncReadStream& stream); + @endcode +*/ +#if BEAST_DOXYGEN +template +struct is_async_read_stream : std::integral_constant{}; +#else +template +struct is_async_read_stream : std::false_type {}; + +template +struct is_async_read_stream().async_read_some( + std::declval(), + std::declval()), + (void)0)>> : std::integral_constant::value + > {}; +#endif + +/** Determine if `T` meets the requirements of @b AsyncWriteStream. + + Metafunctions are used to perform compile time checking of template + types. This type will be `std::true_type` if `T` meets the requirements, + else the type will be `std::false_type`. + + @par Example + + Use with `static_assert`: + + @code + template + void f(AsyncWriteStream& stream) + { + static_assert(is_async_write_stream::value, + "AsyncWriteStream requirements not met"); + ... + @endcode + + Use with `std::enable_if` (SFINAE): + + @code + template + typename std::enable_if::value>::type + f(AsyncWriteStream& stream); + @endcode +*/ +#if BEAST_DOXYGEN +template +struct is_async_write_stream : std::integral_constant{}; +#else +template +struct is_async_write_stream : std::false_type {}; + +template +struct is_async_write_stream().async_write_some( + std::declval(), + std::declval()), + (void)0)>> : std::integral_constant::value + > {}; +#endif + +/** Determine if `T` meets the requirements of @b SyncReadStream. + + Metafunctions are used to perform compile time checking of template + types. This type will be `std::true_type` if `T` meets the requirements, + else the type will be `std::false_type`. + + @par Example + + Use with `static_assert`: + + @code + template + void f(SyncReadStream& stream) + { + static_assert(is_sync_read_stream::value, + "SyncReadStream requirements not met"); + ... + @endcode + + Use with `std::enable_if` (SFINAE): + + @code + template + typename std::enable_if::value>::type + f(SyncReadStream& stream); + @endcode +*/ +#if BEAST_DOXYGEN +template +struct is_sync_read_stream : std::integral_constant{}; +#else +template +struct is_sync_read_stream : std::false_type {}; + +template +struct is_sync_read_stream() = std::declval().read_some( + std::declval()), + std::declval() = std::declval().read_some( + std::declval(), + std::declval()), + (void)0)>> : std::integral_constant::value + > {}; +#endif + +/** Determine if `T` meets the requirements of @b SyncWriterStream. + + Metafunctions are used to perform compile time checking of template + types. This type will be `std::true_type` if `T` meets the requirements, + else the type will be `std::false_type`. + + @par Example + + Use with `static_assert`: + + @code + template + void f(SyncReadStream& stream) + { + static_assert(is_sync_read_stream::value, + "SyncReadStream requirements not met"); + ... + @endcode + + Use with `std::enable_if` (SFINAE): + + @code + template + typename std::enable_if::value>::type + f(SyncReadStream& stream); + @endcode +*/ +#if BEAST_DOXYGEN +template +struct is_sync_write_stream : std::integral_constant{}; +#else +template +struct is_sync_write_stream : std::false_type {}; + +template +struct is_sync_write_stream() = std::declval().write_some( + std::declval()), + std::declval() = std::declval().write_some( + std::declval(), + std::declval()), + (void)0)>> : std::integral_constant::value + > {}; +#endif + +/** Determine if `T` meets the requirements of @b AsyncStream. + + Metafunctions are used to perform compile time checking of template + types. This type will be `std::true_type` if `T` meets the requirements, + else the type will be `std::false_type`. + + @par Example + + Use with `static_assert`: + + @code + template + void f(AsyncStream& stream) + { + static_assert(is_async_stream::value, + "AsyncStream requirements not met"); + ... + @endcode + + Use with `std::enable_if` (SFINAE): + + @code + template + typename std::enable_if::value>::type + f(AsyncStream& stream); + @endcode +*/ +#if BEAST_DOXYGEN +template +struct is_async_stream : std::integral_constant{}; +#else +template +using is_async_stream = std::integral_constant::value && is_async_write_stream::value>; +#endif + +/** Determine if `T` meets the requirements of @b SyncStream. + + Metafunctions are used to perform compile time checking of template + types. This type will be `std::true_type` if `T` meets the requirements, + else the type will be `std::false_type`. + + @par Example + + Use with `static_assert`: + + @code + template + void f(SyncStream& stream) + { + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + ... + @endcode + + Use with `std::enable_if` (SFINAE): + + @code + template + typename std::enable_if::value>::type + f(SyncStream& stream); + @endcode +*/ +#if BEAST_DOXYGEN +template +struct is_sync_stream : std::integral_constant{}; +#else +template +using is_sync_stream = std::integral_constant::value && is_sync_write_stream::value>; +#endif + +//------------------------------------------------------------------------------ +// +// File concepts +// +//------------------------------------------------------------------------------ + +/** Determine if `T` meets the requirements of @b File. + + Metafunctions are used to perform compile time checking of template + types. This type will be `std::true_type` if `T` meets the requirements, + else the type will be `std::false_type`. + + @par Example + + Use with `static_assert`: + + @code + template + void f(File& file) + { + static_assert(is_file::value, + "File requirements not met"); + ... + @endcode + + Use with `std::enable_if` (SFINAE): + + @code + template + typename std::enable_if::value>::type + f(File& file); + @endcode +*/ +#if BEAST_DOXYGEN +template +struct is_file : std::integral_constant{}; +#else +template +struct is_file : std::false_type {}; + +template +struct is_file() = std::declval().is_open(), + std::declval().close(std::declval()), + std::declval().open( + std::declval(), + std::declval(), + std::declval()), + std::declval() = std::declval().size( + std::declval()), + std::declval() = std::declval().pos( + std::declval()), + std::declval().seek( + std::declval(), + std::declval()), + std::declval() = std::declval().read( + std::declval(), + std::declval(), + std::declval()), + std::declval() = std::declval().write( + std::declval(), + std::declval(), + std::declval()), + (void)0)>> : std::integral_constant::value && + std::is_destructible::value + > {}; +#endif + +} // beast + +#endif diff --git a/src/beast/include/beast/core/write_dynabuf.hpp b/src/beast/include/beast/core/write_dynabuf.hpp deleted file mode 100644 index 646e787498..0000000000 --- a/src/beast/include/beast/core/write_dynabuf.hpp +++ /dev/null @@ -1,64 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_WRITE_DYNABUF_HPP -#define BEAST_WRITE_DYNABUF_HPP - -#include -#include -#include -#include -#include - -namespace beast { - -/** Write to a @b `DynamicBuffer`. - - This function appends the serialized representation of each provided - argument into the dynamic buffer. It is capable of converting the - following types of arguments: - - @li `boost::asio::const_buffer` - - @li `boost::asio::mutable_buffer` - - @li A type meeting the requirements of @b `ConvertibleToConstBuffer` - - @li A type meeting the requirements of @b `ConstBufferSequence` - - @li A type meeting the requirements of @b `MutableBufferSequence` - - For all types not listed above, the function will invoke - `boost::lexical_cast` on the argument in an attempt to convert to - a string, which is then appended to the dynamic buffer. - - When this function serializes numbers, it converts them to - their text representation as if by a call to `std::to_string`. - - @param dynabuf The dynamic buffer to write to. - - @param args A list of one or more arguments to write. - - @throws unspecified Any exceptions thrown by `boost::lexical_cast`. - - @note This function participates in overload resolution only if - the `dynabuf` parameter meets the requirements of @b `DynamicBuffer`. -*/ -template -#if GENERATING_DOCS -void -#else -typename std::enable_if::value>::type -#endif -write(DynamicBuffer& dynabuf, Args const&... args) -{ - detail::write_dynabuf(dynabuf, args...); -} - -} // beast - -#endif diff --git a/src/beast/include/beast/http.hpp b/src/beast/include/beast/http.hpp index b4c7734f17..4f13500e4f 100644 --- a/src/beast/include/beast/http.hpp +++ b/src/beast/include/beast/http.hpp @@ -10,20 +10,25 @@ #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 +#include #include #endif diff --git a/src/beast/include/beast/http/basic_dynabuf_body.hpp b/src/beast/include/beast/http/basic_dynabuf_body.hpp deleted file mode 100644 index a2c090149e..0000000000 --- a/src/beast/include/beast/http/basic_dynabuf_body.hpp +++ /dev/null @@ -1,101 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_HTTP_BASIC_DYNABUF_BODY_HPP -#define BEAST_HTTP_BASIC_DYNABUF_BODY_HPP - -#include -#include -#include -#include -#include - -namespace beast { -namespace http { - -/** A message body represented by a @b `DynamicBuffer` - - Meets the requirements of @b `Body`. -*/ -template -struct basic_dynabuf_body -{ - /// The type of the `message::body` member - using value_type = DynamicBuffer; - -#if GENERATING_DOCS -private: -#endif - - class reader - { - value_type& sb_; - - public: - template - explicit - reader(message& m) noexcept - : sb_(m.body) - { - } - - void - init(error_code&) noexcept - { - } - - void - write(void const* data, - std::size_t size, error_code&) noexcept - { - using boost::asio::buffer; - using boost::asio::buffer_copy; - sb_.commit(buffer_copy( - sb_.prepare(size), buffer(data, size))); - } - }; - - class writer - { - DynamicBuffer const& body_; - - public: - template - explicit - writer(message< - isRequest, basic_dynabuf_body, Fields> const& m) noexcept - : body_(m.body) - { - } - - void - init(error_code& ec) noexcept - { - beast::detail::ignore_unused(ec); - } - - std::uint64_t - content_length() const noexcept - { - return body_.size(); - } - - template - bool - write(error_code&, WriteFunction&& wf) noexcept - { - wf(body_.data()); - return true; - } - }; -}; - -} // http -} // beast - -#endif diff --git a/src/beast/include/beast/http/basic_fields.hpp b/src/beast/include/beast/http/basic_fields.hpp deleted file mode 100644 index 2589c827a9..0000000000 --- a/src/beast/include/beast/http/basic_fields.hpp +++ /dev/null @@ -1,307 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_HTTP_BASIC_FIELDS_HPP -#define BEAST_HTTP_BASIC_FIELDS_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace http { - -/** A container for storing HTTP header fields. - - This container is designed to store the field value pairs that make - up the fields and trailers in a HTTP message. Objects of this type - are iterable, with each element holding the field name and field - value. - - Field names are stored as-is, but comparisons are case-insensitive. - When the container is iterated, the fields are presented in the order - of insertion. For fields with the same name, the container behaves - as a `std::multiset`; there will be a separate value for each occurrence - of the field name. - - @note Meets the requirements of @b FieldSequence. -*/ -template -class basic_fields : -#if ! GENERATING_DOCS - private beast::detail::empty_base_optimization< - typename std::allocator_traits:: - template rebind_alloc< - detail::basic_fields_base::element>>, -#endif - public detail::basic_fields_base -{ - using alloc_type = typename - std::allocator_traits:: - template rebind_alloc< - detail::basic_fields_base::element>; - - using alloc_traits = - std::allocator_traits; - - using size_type = - typename std::allocator_traits::size_type; - - void - delete_all(); - - void - move_assign(basic_fields&, std::false_type); - - void - move_assign(basic_fields&, std::true_type); - - void - copy_assign(basic_fields const&, std::false_type); - - void - copy_assign(basic_fields const&, std::true_type); - - template - void - copy_from(FieldSequence const& fs) - { - for(auto const& e : fs) - insert(e.first, e.second); - } - -public: - /// The type of allocator used. - using allocator_type = Allocator; - - /** The value type of the field sequence. - - Meets the requirements of @b Field. - */ -#if GENERATING_DOCS - using value_type = implementation_defined; -#endif - - /// A const iterator to the field sequence -#if GENERATING_DOCS - using iterator = implementation_defined; -#endif - - /// A const iterator to the field sequence -#if GENERATING_DOCS - using const_iterator = implementation_defined; -#endif - - /// Default constructor. - basic_fields() = default; - - /// Destructor - ~basic_fields(); - - /** Construct the fields. - - @param alloc The allocator to use. - */ - explicit - basic_fields(Allocator const& alloc); - - /** Move constructor. - - The moved-from object becomes an empty field sequence. - - @param other The object to move from. - */ - basic_fields(basic_fields&& other); - - /** Move assignment. - - The moved-from object becomes an empty field sequence. - - @param other The object to move from. - */ - basic_fields& operator=(basic_fields&& other); - - /// Copy constructor. - basic_fields(basic_fields const&); - - /// Copy assignment. - basic_fields& operator=(basic_fields const&); - - /// Copy constructor. - template - basic_fields(basic_fields const&); - - /// Copy assignment. - template - basic_fields& operator=(basic_fields const&); - - /// Construct from a field sequence. - template - basic_fields(FwdIt first, FwdIt last); - - /// Returns `true` if the field sequence contains no elements. - bool - empty() const - { - return set_.empty(); - } - - /// Returns the number of elements in the field sequence. - std::size_t - size() const - { - return set_.size(); - } - - /// Returns a const iterator to the beginning of the field sequence. - const_iterator - begin() const - { - return list_.cbegin(); - } - - /// Returns a const iterator to the end of the field sequence. - const_iterator - end() const - { - return list_.cend(); - } - - /// Returns a const iterator to the beginning of the field sequence. - const_iterator - cbegin() const - { - return list_.cbegin(); - } - - /// Returns a const iterator to the end of the field sequence. - const_iterator - cend() const - { - return list_.cend(); - } - - /// Returns `true` if the specified field exists. - bool - exists(boost::string_ref const& name) const - { - return set_.find(name, less{}) != set_.end(); - } - - /// Returns the number of values for the specified field. - std::size_t - count(boost::string_ref const& name) const; - - /** Returns an iterator to the case-insensitive matching field name. - - If more than one field with the specified name exists, the - first field defined by insertion order is returned. - */ - iterator - find(boost::string_ref const& name) const; - - /** Returns the value for a case-insensitive matching header, or `""`. - - If more than one field with the specified name exists, the - first field defined by insertion order is returned. - */ - boost::string_ref - operator[](boost::string_ref const& name) const; - - /// Clear the contents of the basic_fields. - void - clear() noexcept; - - /** Remove a field. - - If more than one field with the specified name exists, all - matching fields will be removed. - - @param name The name of the field(s) to remove. - - @return The number of fields removed. - */ - std::size_t - erase(boost::string_ref const& name); - - /** Insert a field value. - - If a field with the same name already exists, the - existing field is untouched and a new field value pair - is inserted into the container. - - @param name The name of the field. - - @param value A string holding the value of the field. - */ - void - insert(boost::string_ref const& name, boost::string_ref value); - - /** Insert a field value. - - If a field with the same name already exists, the - existing field is untouched and a new field value pair - is inserted into the container. - - @param name The name of the field - - @param value The value of the field. The object will be - converted to a string using `boost::lexical_cast`. - */ - template - typename std::enable_if< - ! std::is_constructible::value>::type - insert(boost::string_ref name, T const& value) - { - insert(name, boost::lexical_cast(value)); - } - - /** Replace a field value. - - First removes any values with matching field names, then - inserts the new field value. - - @param name The name of the field. - - @param value A string holding the value of the field. - */ - void - replace(boost::string_ref const& name, boost::string_ref value); - - /** Replace a field value. - - First removes any values with matching field names, then - inserts the new field value. - - @param name The name of the field - - @param value The value of the field. The object will be - converted to a string using `boost::lexical_cast`. - */ - template - typename std::enable_if< - ! std::is_constructible::value>::type - replace(boost::string_ref const& name, T const& value) - { - replace(name, - boost::lexical_cast(value)); - } -}; - -} // http -} // beast - -#include - -#endif diff --git a/src/beast/include/beast/http/basic_parser.hpp b/src/beast/include/beast/http/basic_parser.hpp new file mode 100644 index 0000000000..16de78ef81 --- /dev/null +++ b/src/beast/include/beast/http/basic_parser.hpp @@ -0,0 +1,509 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_BASIC_PARSER_HPP +#define BEAST_HTTP_BASIC_PARSER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +/** A parser for decoding HTTP/1 wire format messages. + + This parser is designed to efficiently parse messages in the + HTTP/1 wire format. It allocates no memory when input is + presented as a single contiguous buffer, and uses minimal + state. It will handle chunked encoding and it understands + the semantics of the Connection, Content-Length, and Upgrade + fields. + The parser is optimized for the case where the input buffer + sequence consists of a single contiguous buffer. The + @ref beast::flat_buffer class is provided, which guarantees + that the input sequence of the stream buffer will be represented + by exactly one contiguous buffer. To ensure the optimum performance + of the parser, use @ref beast::flat_buffer with HTTP algorithms + such as @ref beast::http::read, @ref beast::http::read_some, + @ref beast::http::async_read, and @ref beast::http::async_read_some. + Alternatively, the caller may use custom techniques to ensure that + the structured portion of the HTTP message (header or chunk header) + is contained in a linear buffer. + + The interface uses CRTP (Curiously Recurring Template Pattern). + To use this class directly, derive from @ref basic_parser. When + bytes are presented, the implementation will make a series of zero + or more calls to derived class members functions (termed "callbacks" + in this context) matching a specific signature. + + Every callback must be provided by the derived class, or else + a compilation error will be generated. This exemplar shows + the signature and description of the callbacks required in + the derived class. + For each callback, the function will ensure that `!ec` is `true` + if there was no error or set to the appropriate error code if + there was one. If an error is set, the value is propagated to + the caller of the parser. + + @tparam isRequest A `bool` indicating whether the parser will be + presented with request or response message. + + @tparam Derived The derived class type. This is part of the + Curiously Recurring Template Pattern interface. + + @note If the parser encounters a field value with obs-fold + longer than 4 kilobytes in length, an error is generated. +*/ +template +class basic_parser + : private detail::basic_parser_base +{ + template + friend class basic_parser; + + // limit on the size of the stack flat buffer + static std::size_t constexpr max_stack_buffer = 8192; + + // Message will be complete after reading header + static unsigned constexpr flagSkipBody = 1<< 0; + + // Consume input buffers across semantic boundaries + static unsigned constexpr flagEager = 1<< 1; + + // The parser has read at least one byte + static unsigned constexpr flagGotSome = 1<< 2; + + // Message semantics indicate a body is expected. + // cleared if flagSkipBody set + // + static unsigned constexpr flagHasBody = 1<< 3; + + static unsigned constexpr flagHTTP11 = 1<< 4; + static unsigned constexpr flagNeedEOF = 1<< 5; + static unsigned constexpr flagExpectCRLF = 1<< 6; + static unsigned constexpr flagConnectionClose = 1<< 7; + static unsigned constexpr flagConnectionUpgrade = 1<< 8; + static unsigned constexpr flagConnectionKeepAlive = 1<< 9; + static unsigned constexpr flagContentLength = 1<< 10; + static unsigned constexpr flagChunked = 1<< 11; + static unsigned constexpr flagUpgrade = 1<< 12; + static unsigned constexpr flagFinalChunk = 1<< 13; + + static + std::uint64_t + default_body_limit(std::true_type) + { + // limit for requests + return 1 * 1024 * 1024; // 1MB + } + + static + std::uint64_t + default_body_limit(std::false_type) + { + // limit for responses + return 8 * 1024 * 1024; // 8MB + } + + std::uint64_t body_limit_; // max payload body + std::uint64_t len_; // size of chunk or body + std::unique_ptr buf_; // temp storage + std::size_t buf_len_ = 0; // size of buf_ + std::size_t skip_ = 0; // resume search here + std::uint32_t + header_limit_ = 8192; // max header size + unsigned short status_; // response status + state state_ = // initial state + state::nothing_yet; + unsigned f_ = 0; // flags + +public: + /// `true` if this parser parses requests, `false` for responses. + using is_request = + std::integral_constant; + + /// Copy constructor (disallowed) + basic_parser(basic_parser const&) = delete; + + /// Copy assignment (disallowed) + basic_parser& operator=(basic_parser const&) = delete; + + /// Destructor + ~basic_parser() = default; + + /// Default constructor + basic_parser(); + + /** Move constructor + + After the move, the only valid operation on the + moved-from object is destruction. + */ + template + basic_parser(basic_parser&&); + + /** Returns a reference to this object as a @ref basic_parser. + + This is used to pass a derived class where a base class is + expected, to choose a correct function overload when the + resolution would be ambiguous. + */ + basic_parser& + base() + { + return *this; + } + + /** Returns a constant reference to this object as a @ref basic_parser. + + This is used to pass a derived class where a base class is + expected, to choose a correct function overload when the + resolution would be ambiguous. + */ + basic_parser const& + base() const + { + return *this; + } + + /// Returns `true` if the parser has received at least one byte of input. + bool + got_some() const + { + return state_ != state::nothing_yet; + } + + /** Returns `true` if the message is complete. + + The message is complete after the full header is prduced + and one of the following is true: + + @li The skip body option was set. + + @li The semantics of the message indicate there is no body. + + @li The semantics of the message indicate a body is expected, + and the entire body was parsed. + */ + bool + is_done() const + { + return state_ == state::complete; + } + + /** Returns `true` if a the parser has produced the full header. + */ + bool + is_header_done() const + { + return state_ > state::fields; + } + + /** Returns `true` if the message is an upgrade message. + + @note The return value is undefined unless + @ref is_header_done would return `true`. + */ + bool + is_upgrade() const + { + return (f_ & flagConnectionUpgrade) != 0; + } + + /** Returns `true` if the last value for Transfer-Encoding is "chunked". + + @note The return value is undefined unless + @ref is_header_done would return `true`. + */ + bool + is_chunked() const + { + return (f_ & flagChunked) != 0; + } + + /** Returns `true` if the message has keep-alive connection semantics. + + @note The return value is undefined unless + @ref is_header_done would return `true`. + */ + bool + is_keep_alive() const; + + /** Returns the optional value of Content-Length if known. + + @note The return value is undefined unless + @ref is_header_done would return `true`. + */ + boost::optional + content_length() const; + + /** Returns `true` if the message semantics require an end of file. + + Depending on the contents of the header, the parser may + require and end of file notification to know where the end + of the body lies. If this function returns `true` it will be + necessary to call @ref put_eof when there will never be additional + data from the input. + */ + bool + need_eof() const + { + return (f_ & flagNeedEOF) != 0; + } + + /** Set the limit on the payload body. + + This function sets the maximum allowed size of the payload body, + before any encodings except chunked have been removed. Depending + on the message semantics, one of these cases will apply: + + @li The Content-Length is specified and exceeds the limit. In + this case the result @ref error::body_limit is returned + immediately after the header is parsed. + + @li The Content-Length is unspecified and the chunked encoding + is not specified as the last encoding. In this case the end of + message is determined by the end of file indicator on the + associated stream or input source. If a sufficient number of + body payload octets are presented to the parser to exceed the + configured limit, the parse fails with the result + @ref error::body_limit + + @li The Transfer-Encoding specifies the chunked encoding as the + last encoding. In this case, when the number of payload body + octets produced by removing the chunked encoding exceeds + the configured limit, the parse fails with the result + @ref error::body_limit. + + Setting the limit after any body octets have been parsed + results in undefined behavior. + + The default limit is 1MB for requests and 8MB for responses. + + @param v The payload body limit to set + */ + void + body_limit(std::uint64_t v) + { + body_limit_ = v; + } + + /** Set a limit on the total size of the header. + + This function sets the maximum allowed size of the header + including all field name, value, and delimiter characters + and also including the CRLF sequences in the serialized + input. If the end of the header is not found within the + limit of the header size, the error @ref error::header_limit + is returned by @ref put. + + Setting the limit after any header octets have been parsed + results in undefined behavior. + */ + void + header_limit(std::uint32_t v) + { + header_limit_ = v; + } + + /// Returns `true` if the eager parse option is set. + bool + eager() const + { + return (f_ & flagEager) != 0; + } + + /** Set the eager parse option. + + Normally the parser returns after successfully parsing a structured + element (header, chunk header, or chunk body) even if there are octets + remaining in the input. This is necessary when attempting to parse the + header first, or when the caller wants to inspect information which may + be invalidated by subsequent parsing, such as a chunk extension. The + `eager` option controls whether the parser keeps going after parsing + structured element if there are octets remaining in the buffer and no + error occurs. This option is automatically set or cleared during certain + stream operations to improve performance with no change in functionality. + + The default setting is `false`. + + @param v `true` to set the eager parse option or `false` to disable it. + */ + void + eager(bool v) + { + if(v) + f_ |= flagEager; + else + f_ &= ~flagEager; + } + + /// Returns `true` if the skip parse option is set. + bool + skip() + { + return (f_ & flagSkipBody) != 0; + } + + /** Set the skip parse option. + + This option controls whether or not the parser expects to see an HTTP + body, regardless of the presence or absence of certain fields such as + Content-Length or a chunked Transfer-Encoding. Depending on the request, + some responses do not carry a body. For example, a 200 response to a + CONNECT request from a tunneling proxy, or a response to a HEAD request. + In these cases, callers may use this function inform the parser that + no body is expected. The parser will consider the message complete + after the header has been received. + + @param v `true` to set the skip body option or `false` to disable it. + + @note This function must called before any bytes are processed. + */ + void + skip(bool v); + + /** Write a buffer sequence to the parser. + + This function attempts to incrementally parse the HTTP + message data stored in the caller provided buffers. Upon + success, a positive return value indicates that the parser + made forward progress, consuming that number of + bytes. + + In some cases there may be an insufficient number of octets + in the input buffer in order to make forward progress. This + is indicated by the code @ref error::need_more. When + this happens, the caller should place additional bytes into + the buffer sequence and call @ref put again. + + The error code @ref error::need_more is special. When this + error is returned, a subsequent call to @ref put may succeed + if the buffers have been updated. Otherwise, upon error + the parser may not be restarted. + + @param buffers An object meeting the requirements of + @b ConstBufferSequence that represents the next chunk of + message data. If the length of this buffer sequence is + one, the implementation will not allocate additional memory. + The class @ref flat_buffer is provided as one way to + meet this requirement + + @param ec Set to the error, if any occurred. + + @return The number of octets consumed in the buffer + sequence. The caller should remove these octets even if the + error is set. + */ + template + std::size_t + put(ConstBufferSequence const& buffers, error_code& ec); + +#if ! BEAST_DOXYGEN + std::size_t + put(boost::asio::const_buffers_1 const& buffer, + error_code& ec); +#endif + + /** Inform the parser that the end of stream was reached. + + In certain cases, HTTP needs to know where the end of + the stream is. For example, sometimes servers send + responses without Content-Length and expect the client + to consume input (for the body) until EOF. Callbacks + and errors will still be processed as usual. + + This is typically called when a read from the + underlying stream object sets the error code to + `boost::asio::error::eof`. + + @note Only valid after parsing a complete header. + + @param ec Set to the error, if any occurred. + */ + void + put_eof(error_code& ec); + +private: + inline + Derived& + impl() + { + return *static_cast(this); + } + + template + std::size_t + put_from_stack(std::size_t size, + ConstBufferSequence const& buffers, + error_code& ec); + + void + maybe_need_more( + char const* p, std::size_t n, + error_code& ec); + + void + parse_start_line( + char const*& p, char const* last, + error_code& ec, std::true_type); + + void + parse_start_line( + char const*& p, char const* last, + error_code& ec, std::false_type); + + void + parse_fields( + char const*& p, char const* last, + error_code& ec); + + void + finish_header( + error_code& ec, std::true_type); + + void + finish_header( + error_code& ec, std::false_type); + + void + parse_body(char const*& p, + std::size_t n, error_code& ec); + + void + parse_body_to_eof(char const*& p, + std::size_t n, error_code& ec); + + void + parse_chunk_header(char const*& p, + std::size_t n, error_code& ec); + + void + parse_chunk_body(char const*& p, + std::size_t n, error_code& ec); + + void + do_field(field f, + string_view value, error_code& ec); +}; + +} // http +} // beast + +#include + +#endif diff --git a/src/beast/include/beast/http/basic_parser_v1.hpp b/src/beast/include/beast/http/basic_parser_v1.hpp deleted file mode 100644 index cda14a0023..0000000000 --- a/src/beast/include/beast/http/basic_parser_v1.hpp +++ /dev/null @@ -1,856 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_HTTP_BASIC_PARSER_v1_HPP -#define BEAST_HTTP_BASIC_PARSER_v1_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace http { - -/** Parse flags - - The set of parser bit flags are returned by @ref basic_parser_v1::flags. -*/ -enum parse_flag -{ - chunked = 1, - connection_keep_alive = 2, - connection_close = 4, - connection_upgrade = 8, - trailing = 16, - upgrade = 32, - skipbody = 64, - contentlength = 128, - paused = 256 -}; - -/** Body maximum size option. - - Sets the maximum number of cumulative bytes allowed including - all body octets. Octets in chunk-encoded bodies are counted - after decoding. A value of zero indicates no limit on - the number of body octets. - - The default body maximum size for requests is 4MB (four - megabytes or 4,194,304 bytes) and unlimited for responses. - - @note Objects of this type are used with @ref basic_parser_v1::set_option. -*/ -struct body_max_size -{ - std::size_t value; - - explicit - body_max_size(std::size_t v) - : value(v) - { - } -}; - -/** Header maximum size option. - - Sets the maximum number of cumulative bytes allowed - including all header octets. A value of zero indicates - no limit on the number of header octets. - - The default header maximum size is 16KB (16,384 bytes). - - @note Objects of this type are used with @ref basic_parser_v1::set_option. -*/ -struct header_max_size -{ - std::size_t value; - - explicit - header_max_size(std::size_t v) - : value(v) - { - } -}; - -/** A value indicating how the parser should treat the body. - - This value is returned from the `on_header` callback in - the derived class. It controls what the parser does next - in terms of the message body. -*/ -enum class body_what -{ - /** The parser should expect a body, keep reading. - */ - normal, - - /** Skip parsing of the body. - - When returned by `on_header` this causes parsing to - complete and control to return to the caller. This - could be used when sending a response to a HEAD - request, for example. - */ - skip, - - /** The message represents an UPGRADE request. - - When returned by `on_body_prepare` this causes parsing - to complete and control to return to the caller. - */ - upgrade, - - /** Suspend parsing before reading the body. - - When returned by `on_body_prepare` this causes parsing - to pause. Control is returned to the caller, and the - parser state is preserved such that a subsequent call - to the parser will begin reading the message body. - - This could be used by callers to inspect the HTTP - header before committing to read the body. For example, - to choose the body type based on the fields. Or to - respond to an Expect: 100-continue request. - */ - pause -}; - -/// The value returned when no content length is known or applicable. -static std::uint64_t constexpr no_content_length = - (std::numeric_limits::max)(); - -/** A parser for decoding HTTP/1 wire format messages. - - This parser is designed to efficiently parse messages in the - HTTP/1 wire format. It allocates no memory and uses minimal - state. It will handle chunked encoding and it understands the - semantics of the Connection and Content-Length header fields. - - The interface uses CRTP (Curiously Recurring Template Pattern). - To use this class, derive from basic_parser. When bytes are - presented, the implementation will make a series of zero or - more calls to derived class members functions (referred to as - "callbacks" from here on) matching a specific signature. - - Every callback must be provided by the derived class, or else - a compilation error will be generated. This exemplar shows - the signature and description of the callbacks required in - the derived class. - - @code - template - struct exemplar : basic_parser_v1 - { - // Called when the first valid octet of a new message is received - // - void on_start(error_code&); - - // Called for each piece of the Request-Method - // - void on_method(boost::string_ref const&, error_code&); - - // Called for each piece of the Request-URI - // - void on_uri(boost::string_ref const&, error_code&); - - // Called for each piece of the reason-phrase - // - void on_reason(boost::string_ref const&, error_code&); - - // Called after the entire Request-Line has been parsed successfully. - // - void on_request(error_code&); - - // Called after the entire Response-Line has been parsed successfully. - // - void on_response(error_code&); - - // Called for each piece of the current header field. - // - void on_field(boost::string_ref const&, error_code&); - - // Called for each piece of the current header value. - // - void on_value(boost::string_ref const&, error_code&) - - // Called when the entire header has been parsed successfully. - // - void - on_header(std::uint64_t content_length, error_code&); - - // Called after on_header, before the body is parsed - // - body_what - on_body_what(std::uint64_t content_length, error_code&); - - // Called for each piece of the body. - // - // If the header indicates chunk encoding, the chunk - // encoding is removed from the buffer before being - // passed to the callback. - // - void on_body(boost::string_ref const&, error_code&); - - // Called when the entire message has been parsed successfully. - // At this point, @ref complete returns `true`, and the parser - // is ready to parse another message if `keep_alive` would - // return `true`. - // - void on_complete(error_code&) {} - }; - @endcode - - The return value of `on_body_what` is special, it controls - whether or not the parser should expect a body. See @ref body_what - for choices of the return value. - - If a callback sets an error, parsing stops at the current octet - and the error is returned to the caller. Callbacks must not throw - exceptions. - - @tparam isRequest A `bool` indicating whether the parser will be - presented with request or response message. - - @tparam Derived The derived class type. This is part of the - Curiously Recurring Template Pattern interface. -*/ -template -class basic_parser_v1 : public detail::parser_base -{ -private: - template - friend class basic_parser_v1; - - using self = basic_parser_v1; - typedef void(self::*pmf_t)(error_code&, boost::string_ref const&); - - enum field_state : std::uint8_t - { - h_general = 0, - h_C, - h_CO, - h_CON, - - h_matching_connection, - h_matching_proxy_connection, - h_matching_content_length, - h_matching_transfer_encoding, - h_matching_upgrade, - - h_connection, - h_content_length0, - h_content_length, - h_content_length_ows, - h_transfer_encoding, - h_upgrade, - - h_matching_transfer_encoding_chunked, - h_matching_transfer_encoding_general, - h_matching_connection_keep_alive, - h_matching_connection_close, - h_matching_connection_upgrade, - - h_transfer_encoding_chunked, - h_transfer_encoding_chunked_ows, - - h_connection_keep_alive, - h_connection_keep_alive_ows, - h_connection_close, - h_connection_close_ows, - h_connection_upgrade, - h_connection_upgrade_ows, - h_connection_token, - h_connection_token_ows - }; - - std::size_t h_max_; - std::size_t h_left_; - std::size_t b_max_; - std::size_t b_left_; - std::uint64_t content_length_; - pmf_t cb_; - state s_ : 8; - unsigned fs_ : 8; - unsigned pos_ : 8; // position in field state - unsigned http_major_ : 16; - unsigned http_minor_ : 16; - unsigned status_code_ : 16; - unsigned flags_ : 9; - bool upgrade_ : 1; // true if parser exited for upgrade - -public: - /// Default constructor - basic_parser_v1(); - - /// Copy constructor. - template - basic_parser_v1(basic_parser_v1< - isRequest, OtherDerived> const& other); - - /// Copy assignment. - template - basic_parser_v1& operator=(basic_parser_v1< - isRequest, OtherDerived> const& other); - - /** Set options on the parser. - - @param args One or more parser options to set. - */ -#if GENERATING_DOCS - template - void - set_option(Args&&... args) -#else - template - void - set_option(A1&& a1, A2&& a2, An&&... an) -#endif - { - set_option(std::forward(a1)); - set_option(std::forward(a2), - std::forward(an)...); - } - - /// Set the header maximum size option - void - set_option(header_max_size const& o) - { - h_max_ = o.value; - h_left_ = h_max_; - } - - /// Set the body maximum size option - void - set_option(body_max_size const& o) - { - b_max_ = o.value; - b_left_ = b_max_; - } - - /// Returns internal flags associated with the parser. - unsigned - flags() const - { - return flags_; - } - - /** Returns `true` if the message end is indicated by eof. - - This function returns true if the semantics of the message require - that the end of the message is signaled by an end of file. For - example, if the message is a HTTP/1.0 message and the Content-Length - is unspecified, the end of the message is indicated by an end of file. - - @return `true` if write_eof must be used to indicate the message end. - */ - bool - needs_eof() const - { - return needs_eof( - std::integral_constant{}); - } - - /** Returns the major HTTP version number. - - Examples: - * Returns 1 for HTTP/1.1 - * Returns 1 for HTTP/1.0 - - @return The HTTP major version number. - */ - unsigned - http_major() const - { - return http_major_; - } - - /** Returns the minor HTTP version number. - - Examples: - * Returns 1 for HTTP/1.1 - * Returns 0 for HTTP/1.0 - - @return The HTTP minor version number. - */ - unsigned - http_minor() const - { - return http_minor_; - } - - /** Returns `true` if the message is an upgrade message. - - A value of `true` indicates that the parser has successfully - completed parsing a HTTP upgrade message. - - @return `true` if the message is an upgrade message. - */ - bool - upgrade() const - { - return upgrade_; - } - - /** Returns the numeric HTTP Status-Code of a response. - - @return The Status-Code. - */ - unsigned - status_code() const - { - return status_code_; - } - - /** Returns `true` if the connection should be kept open. - - @note This function is only valid to call when the parser - is complete. - */ - bool - keep_alive() const; - - /** Returns `true` if the parse has completed succesfully. - - When the parse has completed successfully, and the semantics - of the parsed message indicate that the connection is still - active, a subsequent call to `write` will begin parsing a - new message. - - @return `true` If the parsing has completed successfully. - */ - bool - complete() const - { - return - s_ == s_restart || - s_ == s_closed_complete || - (flags_ & parse_flag::paused); - } - - /** Write a sequence of buffers to the parser. - - @param buffers An object meeting the requirements of - ConstBufferSequence that represents the input sequence. - - @param ec Set to the error, if any error occurred. - - @return The number of bytes consumed in the input sequence. - */ - template -#if GENERATING_DOCS - std::size_t -#else - typename std::enable_if< - ! std::is_convertible::value, - std::size_t>::type -#endif - write(ConstBufferSequence const& buffers, error_code& ec); - - /** Write a single buffer of data to the parser. - - @param buffer The buffer to write. - @param ec Set to the error, if any error occurred. - - @return The number of bytes consumed in the buffer. - */ - std::size_t - write(boost::asio::const_buffer const& buffer, error_code& ec); - - /** Called to indicate the end of file. - - HTTP needs to know where the end of the stream is. For example, - sometimes servers send responses without Content-Length and - expect the client to consume input (for the body) until EOF. - Callbacks and errors will still be processed as usual. - - @note This is typically called when a socket read returns eof. - */ - void - write_eof(error_code& ec); - -protected: - /** Reset the parsing state. - - The state of the parser is reset to expect the beginning of - a new request or response. The old state is discarded. - */ - void - reset(); - -private: - Derived& - impl() - { - return *static_cast(this); - } - - void - reset(std::true_type) - { - s_ = s_req_start; - } - - void - reset(std::false_type) - { - s_ = s_res_start; - } - - void - init(std::true_type) - { - // Request: 16KB max header, 4MB max body - h_max_ = 16 * 1024; - b_max_ = 4 * 1024 * 1024; - } - - void - init(std::false_type) - { - // Response: 16KB max header, unlimited body - h_max_ = 16 * 1024; - b_max_ = 0; - } - - void - init() - { - init(std::integral_constant{}); - reset(); - } - - bool - needs_eof(std::true_type) const; - - bool - needs_eof(std::false_type) const; - - template> - struct check_on_start : std::false_type {}; - - template - struct check_on_start().on_start( - std::declval()) - )>> : std::true_type { }; - - template> - struct check_on_method : std::false_type {}; - - template - struct check_on_method().on_method( - std::declval(), - std::declval()) - )>> : std::true_type {}; - - template> - struct check_on_uri : std::false_type {}; - - template - struct check_on_uri().on_uri( - std::declval(), - std::declval()) - )>> : std::true_type {}; - - template> - struct check_on_reason : std::false_type {}; - - template - struct check_on_reason().on_reason( - std::declval(), - std::declval()) - )>> : std::true_type {}; - - template> - struct check_on_request : std::false_type {}; - - template - struct check_on_request().on_request( - std::declval()) - )>> : std::true_type {}; - - template> - struct check_on_response : std::false_type {}; - - template - struct check_on_response().on_response( - std::declval()) - )>> : std::true_type {}; - - template> - struct check_on_field : std::false_type {}; - - template - struct check_on_field().on_field( - std::declval(), - std::declval()) - )>> : std::true_type {}; - - template> - struct check_on_value : std::false_type {}; - - template - struct check_on_value().on_value( - std::declval(), - std::declval()) - )>> : std::true_type {}; - - template> - struct check_on_headers : std::false_type {}; - - template - struct check_on_headers().on_header( - std::declval(), - std::declval()) - )>> : std::true_type {}; - - // VFALCO Can we use std::is_detected? Is C++11 capable? - template - class check_on_body_what_t - { - template().on_body_what( - std::declval(), - std::declval())), - body_what>> - static R check(int); - template - static std::false_type check(...); - using type = decltype(check(0)); - public: - static bool const value = type::value; - }; - template - using check_on_body_what = - std::integral_constant::value>; - - template> - struct check_on_body : std::false_type {}; - - template - struct check_on_body().on_body( - std::declval(), - std::declval()) - )>> : std::true_type {}; - - template> - struct check_on_complete : std::false_type {}; - - template - struct check_on_complete().on_complete( - std::declval()) - )>> : std::true_type {}; - - void call_on_start(error_code& ec) - { - static_assert(check_on_start::value, - "on_start requirements not met"); - impl().on_start(ec); - } - - void call_on_method(error_code& ec, - boost::string_ref const& s, std::true_type) - { - static_assert(check_on_method::value, - "on_method requirements not met"); - if(h_max_ && s.size() > h_left_) - { - ec = parse_error::header_too_big; - return; - } - h_left_ -= s.size(); - impl().on_method(s, ec); - } - - void call_on_method(error_code&, - boost::string_ref const&, std::false_type) - { - } - - void call_on_method(error_code& ec, - boost::string_ref const& s) - { - call_on_method(ec, s, - std::integral_constant{}); - } - - void call_on_uri(error_code& ec, - boost::string_ref const& s, std::true_type) - { - static_assert(check_on_uri::value, - "on_uri requirements not met"); - if(h_max_ && s.size() > h_left_) - { - ec = parse_error::header_too_big; - return; - } - h_left_ -= s.size(); - impl().on_uri(s, ec); - } - - void call_on_uri(error_code&, - boost::string_ref const&, std::false_type) - { - } - - void call_on_uri(error_code& ec, - boost::string_ref const& s) - { - call_on_uri(ec, s, - std::integral_constant{}); - } - - void call_on_reason(error_code& ec, - boost::string_ref const& s, std::true_type) - { - static_assert(check_on_reason::value, - "on_reason requirements not met"); - if(h_max_ && s.size() > h_left_) - { - ec = parse_error::header_too_big; - return; - } - h_left_ -= s.size(); - impl().on_reason(s, ec); - } - - void call_on_reason(error_code&, - boost::string_ref const&, std::false_type) - { - } - - void call_on_reason(error_code& ec, boost::string_ref const& s) - { - call_on_reason(ec, s, - std::integral_constant{}); - } - - void call_on_request(error_code& ec, std::true_type) - { - static_assert(check_on_request::value, - "on_request requirements not met"); - impl().on_request(ec); - } - - void call_on_request(error_code&, std::false_type) - { - } - - void call_on_request(error_code& ec) - { - call_on_request(ec, - std::integral_constant{}); - } - - void call_on_response(error_code& ec, std::true_type) - { - static_assert(check_on_response::value, - "on_response requirements not met"); - impl().on_response(ec); - } - - void call_on_response(error_code&, std::false_type) - { - } - - void call_on_response(error_code& ec) - { - call_on_response(ec, - std::integral_constant{}); - } - - void call_on_field(error_code& ec, - boost::string_ref const& s) - { - static_assert(check_on_field::value, - "on_field requirements not met"); - if(h_max_ && s.size() > h_left_) - { - ec = parse_error::header_too_big; - return; - } - h_left_ -= s.size(); - impl().on_field(s, ec); - } - - void call_on_value(error_code& ec, - boost::string_ref const& s) - { - static_assert(check_on_value::value, - "on_value requirements not met"); - if(h_max_ && s.size() > h_left_) - { - ec = parse_error::header_too_big; - return; - } - h_left_ -= s.size(); - impl().on_value(s, ec); - } - - void - call_on_headers(error_code& ec) - { - static_assert(check_on_headers::value, - "on_header requirements not met"); - impl().on_header(content_length_, ec); - } - - body_what - call_on_body_what(error_code& ec) - { - static_assert(check_on_body_what::value, - "on_body_what requirements not met"); - return impl().on_body_what(content_length_, ec); - } - - void call_on_body(error_code& ec, - boost::string_ref const& s) - { - static_assert(check_on_body::value, - "on_body requirements not met"); - if(b_max_ && s.size() > b_left_) - { - ec = parse_error::body_too_big; - return; - } - b_left_ -= s.size(); - impl().on_body(s, ec); - } - - void call_on_complete(error_code& ec) - { - static_assert(check_on_complete::value, - "on_complete requirements not met"); - impl().on_complete(ec); - } -}; - -} // http -} // beast - -#include - -#endif diff --git a/src/beast/include/beast/http/buffer_body.hpp b/src/beast/include/beast/http/buffer_body.hpp new file mode 100644 index 0000000000..61a99f178f --- /dev/null +++ b/src/beast/include/beast/http/buffer_body.hpp @@ -0,0 +1,224 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_BUFFER_BODY_HPP +#define BEAST_HTTP_BUFFER_BODY_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +/** A @b Body using a caller provided buffer + + Messages using this body type may be serialized and parsed. + To use this class, the caller must initialize the members + of @ref buffer_body::value_type to appropriate values before + each call to read or write during a stream operation. +*/ +struct buffer_body +{ + /// The type of the body member when used in a message. + struct value_type + { + /** A pointer to a contiguous area of memory of @ref size octets, else `nullptr`. + + @par When Serializing + + If this is `nullptr` and `more` is `true`, the error + @ref error::need_buffer will be returned from @ref serializer::get + Otherwise, the serializer will use the memory pointed to + by `data` having `size` octets of valid storage as the + next buffer representing the body. + + @par When Parsing + + If this is `nullptr`, the error @ref error::need_buffer + will be returned from @ref parser::put. Otherwise, the + parser will store body octets into the memory pointed to + by `data` having `size` octets of valid storage. After + octets are stored, the `data` and `size` members are + adjusted: `data` is incremented to point to the next + octet after the data written, while `size` is decremented + to reflect the remaining space at the memory location + pointed to by `data`. + */ + void* data = nullptr; + + /** The number of octets in the buffer pointed to by @ref data. + + @par When Serializing + + If `data` is `nullptr` during serialization, this value + is ignored. Otherwise, it represents the number of valid + body octets pointed to by `data`. + + @par When Parsing + + The value of this field will be decremented during parsing + to indicate the number of remaining free octets in the + buffer pointed to by `data`. When it reaches zero, the + parser will return @ref error::need_buffer, indicating to + the caller that the values of `data` and `size` should be + updated to point to a new memory buffer. + */ + std::size_t size = 0; + + /** `true` if this is not the last buffer. + + @par When Serializing + + If this is `true` and `data` is `nullptr`, the error + @ref error::need_buffer will be returned from @ref serializer::get + + @par When Parsing + + This field is not used during parsing. + */ + bool more = true; + }; + + /** The algorithm for serializing the body + + Meets the requirements of @b BodyReader. + */ +#if BEAST_DOXYGEN + using reader = implementation_defined; +#else + class reader + { + bool toggle_ = false; + value_type const& body_; + + public: + using const_buffers_type = + boost::asio::const_buffers_1; + + template + explicit + reader(message const& msg) + : body_(msg.body) + { + } + + void + init(error_code& ec) + { + ec.assign(0, ec.category()); + } + + boost::optional< + std::pair> + get(error_code& ec) + { + if(toggle_) + { + if(body_.more) + { + toggle_ = false; + ec = error::need_buffer; + } + else + { + ec.assign(0, ec.category()); + } + return boost::none; + } + if(body_.data) + { + ec.assign(0, ec.category()); + toggle_ = true; + return {{const_buffers_type{ + body_.data, body_.size}, body_.more}}; + } + if(body_.more) + ec = error::need_buffer; + else + ec.assign(0, ec.category()); + return boost::none; + } + }; +#endif + + /** The algorithm for parsing the body + + Meets the requirements of @b BodyReader. + */ +#if BEAST_DOXYGEN + using writer = implementation_defined; +#else + class writer + { + value_type& body_; + + public: + template + explicit + writer(message& m) + : body_(m.body) + { + } + + void + init(boost::optional const&, error_code& ec) + { + ec.assign(0, ec.category()); + } + + template + std::size_t + put(ConstBufferSequence const& buffers, + error_code& ec) + { + using boost::asio::buffer_size; + using boost::asio::buffer_copy; + if(! body_.data) + { + ec = error::need_buffer; + return 0; + } + auto const bytes_transferred = + buffer_copy(boost::asio::buffer( + body_.data, body_.size), buffers); + body_.data = reinterpret_cast( + body_.data) + bytes_transferred; + body_.size -= bytes_transferred; + if(bytes_transferred == buffer_size(buffers)) + ec.assign(0, ec.category()); + else + ec = error::need_buffer; + return bytes_transferred; + } + + void + finish(error_code& ec) + { + ec.assign(0, ec.category()); + } + }; +#endif +}; + +#if ! BEAST_DOXYGEN +// operator<< is not supported for buffer_body +template +std::ostream& +operator<<(std::ostream& os, message const& msg) = delete; +#endif + +} // http +} // beast + +#endif diff --git a/src/beast/include/beast/http/chunk_encode.hpp b/src/beast/include/beast/http/chunk_encode.hpp deleted file mode 100644 index a437fe1839..0000000000 --- a/src/beast/include/beast/http/chunk_encode.hpp +++ /dev/null @@ -1,75 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_HTTP_CHUNK_ENCODE_HPP -#define BEAST_HTTP_CHUNK_ENCODE_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace http { - -/** Returns a chunk-encoded ConstBufferSequence. - - This returns a buffer sequence representing the - first chunk of a chunked transfer coded body. - - @param fin `true` if this is the last chunk. - - @param buffers The input buffer sequence. - - @return A chunk-encoded ConstBufferSequence representing the input. - - @see rfc7230 section 4.1.3 -*/ -template -#if GENERATING_DOCS -implementation_defined -#else -beast::detail::buffer_cat_helper< - detail::chunk_encode_delim, - ConstBufferSequence, - boost::asio::const_buffers_1> -#endif -chunk_encode(bool fin, ConstBufferSequence const& buffers) -{ - using boost::asio::buffer_size; - return buffer_cat( - detail::chunk_encode_delim{buffer_size(buffers)}, - buffers, - fin ? boost::asio::const_buffers_1{"\r\n0\r\n\r\n", 7} - : boost::asio::const_buffers_1{"\r\n", 2}); -} - -/** Returns a chunked encoding final chunk. - - @see rfc7230 section 4.1.3 -*/ -inline -#if GENERATING_DOCS -implementation_defined -#else -boost::asio::const_buffers_1 -#endif -chunk_encode_final() -{ - return boost::asio::const_buffers_1{"0\r\n\r\n", 5}; -} - -} // http -} // beast - -#endif diff --git a/src/beast/include/beast/http/concepts.hpp b/src/beast/include/beast/http/concepts.hpp deleted file mode 100644 index 2e83fe7a48..0000000000 --- a/src/beast/include/beast/http/concepts.hpp +++ /dev/null @@ -1,231 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_HTTP_TYPE_CHECK_HPP -#define BEAST_HTTP_TYPE_CHECK_HPP - -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace http { - -namespace detail { - -struct write_function -{ - template - void - operator()(ConstBufferSequence const&); -}; - -template> -struct has_value_type : std::false_type {}; - -template -struct has_value_type > : std::true_type {}; - -template> -struct has_content_length : std::false_type {}; - -template -struct has_content_length().content_length() - )> > : std::true_type -{ - static_assert(std::is_convertible< - decltype(std::declval().content_length()), - std::uint64_t>::value, - "Writer::content_length requirements not met"); -}; - -template -class is_Writer -{ - template().init(std::declval()), - std::true_type{})> - static R check1(int); - template - static std::false_type check1(...); - using type1 = decltype(check1(0)); - - // VFALCO This is unfortunate, we have to provide the template - // argument type because this is not a deduced context? - // - template().template write( - std::declval(), - std::declval())) - , bool>> - static R check2(int); - template - static std::false_type check2(...); - using type2 = decltype(check2(0)); - -public: - static_assert(std::is_same< - typename M::body_type::writer, T>::value, - "Mismatched writer and message"); - - using type = std::integral_constant::value - && type1::value - && type2::value - >; -}; - -template -class is_Parser -{ - template().complete()), - bool>> - static R check1(int); - template - static std::false_type check1(...); - using type1 = decltype(check1(0)); - - template().write( - std::declval(), - std::declval())), - std::size_t>> - static R check2(int); - template - static std::false_type check2(...); - using type2 = decltype(check2(0)); - - template().write_eof(std::declval()), - std::true_type{})> - static R check3(int); - template - static std::false_type check3(...); - using type3 = decltype(check3(0)); - -public: - using type = std::integral_constant; -}; - -} // detail - -/// Determine if `T` meets the requirements of @b Body. -template -#if GENERATING_DOCS -struct is_Body : std::integral_constant{}; -#else -using is_Body = detail::has_value_type; -#endif - -/** Determine if a @b Body has a nested type `reader`. - - @tparam T The type to check, which must meet the - requirements of @b Body. -*/ -#if GENERATING_DOCS -template -struct has_reader : std::integral_constant{}; -#else -template> -struct has_reader : std::false_type {}; - -template -struct has_reader > : std::true_type {}; -#endif - -/** Determine if a @b Body has a nested type `writer`. - - @tparam T The type to check, which must meet the - requirements of @b Body. -*/ -#if GENERATING_DOCS -template -struct has_writer : std::integral_constant{}; -#else -template> -struct has_writer : std::false_type {}; - -template -struct has_writer > : std::true_type {}; -#endif - -/** Determine if `T` meets the requirements of @b Reader for `M`. - - @tparam T The type to test. - - @tparam M The message type to test with, which must be of - type `message`. -*/ -#if GENERATING_DOCS -template -struct is_Reader : std::integral_constant {}; -#else -template> -struct is_Reader : std::false_type {}; - -template -struct is_Reader().init( - std::declval()), - std::declval().write( - std::declval(), - std::declval(), - std::declval()) - )> > : std::integral_constant::value - > -{ - static_assert(std::is_same< - typename M::body_type::reader, T>::value, - "Mismatched reader and message"); -}; -#endif - -/** Determine if `T` meets the requirements of @b Writer for `M`. - - @tparam T The type to test. - - @tparam M The message type to test with, which must be of - type `message`. -*/ -template -#if GENERATING_DOCS -struct is_Writer : std::integral_constant {}; -#else -using is_Writer = typename detail::is_Writer::type; -#endif - -/// Determine if `T` meets the requirements of @b Parser. -template -#if GENERATING_DOCS -struct is_Parser : std::integral_constant{}; -#else -using is_Parser = typename detail::is_Parser::type; -#endif - -} // http -} // beast - -#endif diff --git a/src/beast/include/beast/http/detail/basic_fields.hpp b/src/beast/include/beast/http/detail/basic_fields.hpp deleted file mode 100644 index 1b296df3b6..0000000000 --- a/src/beast/include/beast/http/detail/basic_fields.hpp +++ /dev/null @@ -1,214 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_HTTP_DETAIL_BASIC_FIELDS_HPP -#define BEAST_HTTP_DETAIL_BASIC_FIELDS_HPP - -#include -#include -#include -#include - -namespace beast { -namespace http { - -template -class basic_fields; - -namespace detail { - -class basic_fields_base -{ -public: - struct value_type - { - std::string first; - std::string second; - - value_type(boost::string_ref const& name_, - boost::string_ref const& value_) - : first(name_) - , second(value_) - { - } - - boost::string_ref - name() const - { - return first; - } - - boost::string_ref - value() const - { - return second; - } - }; - -protected: - template - friend class beast::http::basic_fields; - - struct element - : boost::intrusive::set_base_hook < - boost::intrusive::link_mode < - boost::intrusive::normal_link>> - , boost::intrusive::list_base_hook < - boost::intrusive::link_mode < - boost::intrusive::normal_link>> - { - value_type data; - - element(boost::string_ref const& name, - boost::string_ref const& value) - : data(name, value) - { - } - }; - - struct less : private beast::detail::ci_less - { - template - bool - operator()(String const& lhs, element const& rhs) const - { - return ci_less::operator()(lhs, rhs.data.first); - } - - template - bool - operator()(element const& lhs, String const& rhs) const - { - return ci_less::operator()(lhs.data.first, rhs); - } - - bool - operator()(element const& lhs, element const& rhs) const - { - return ci_less::operator()( - lhs.data.first, rhs.data.first); - } - }; - - using list_t = boost::intrusive::make_list>::type; - - using set_t = boost::intrusive::make_multiset, - boost::intrusive::compare>::type; - - // data - set_t set_; - list_t list_; - - basic_fields_base(set_t&& set, list_t&& list) - : set_(std::move(set)) - , list_(std::move(list)) - { - } - -public: - class const_iterator; - - using iterator = const_iterator; - - basic_fields_base() = default; -}; - -//------------------------------------------------------------------------------ - -class basic_fields_base::const_iterator -{ - using iter_type = list_t::const_iterator; - - iter_type it_; - - template - friend class beast::http::basic_fields; - - friend class basic_fields_base; - - const_iterator(iter_type it) - : it_(it) - { - } - -public: - using value_type = - typename basic_fields_base::value_type; - using pointer = value_type const*; - using reference = value_type const&; - using difference_type = std::ptrdiff_t; - using iterator_category = - std::bidirectional_iterator_tag; - - const_iterator() = default; - const_iterator(const_iterator&& other) = default; - const_iterator(const_iterator const& other) = default; - const_iterator& operator=(const_iterator&& other) = default; - const_iterator& operator=(const_iterator const& other) = default; - - bool - operator==(const_iterator const& other) const - { - return it_ == other.it_; - } - - bool - operator!=(const_iterator const& other) const - { - return !(*this == other); - } - - reference - operator*() const - { - return it_->data; - } - - pointer - operator->() const - { - return &**this; - } - - const_iterator& - operator++() - { - ++it_; - return *this; - } - - const_iterator - operator++(int) - { - auto temp = *this; - ++(*this); - return temp; - } - - const_iterator& - operator--() - { - --it_; - return *this; - } - - const_iterator - operator--(int) - { - auto temp = *this; - --(*this); - return temp; - } -}; - -} // detail -} // http -} // beast - -#endif diff --git a/src/beast/include/beast/http/detail/basic_parsed_list.hpp b/src/beast/include/beast/http/detail/basic_parsed_list.hpp new file mode 100644 index 0000000000..98f9acae17 --- /dev/null +++ b/src/beast/include/beast/http/detail/basic_parsed_list.hpp @@ -0,0 +1,194 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_DETAIL_BASIC_PARSED_LIST_HPP +#define BEAST_HTTP_DETAIL_BASIC_PARSED_LIST_HPP + +#include +#include +#include +#include + +namespace beast { +namespace http { +namespace detail { + +/** A list parser which presents the sequence as a container. +*/ +template +class basic_parsed_list +{ + string_view s_; + +public: + /// The type of policy this list uses for parsing. + using policy_type = Policy; + + /// The type of each element in the list. + using value_type = typename Policy::value_type; + + /// A constant iterator to a list element. +#if BEAST_DOXYGEN + using const_iterator = implementation_defined; +#else + class const_iterator; +#endif + + class const_iterator + : private beast::detail:: + empty_base_optimization + { + basic_parsed_list const* list_ = nullptr; + char const* it_ = nullptr; + typename Policy::value_type v_; + bool error_ = false; + + public: + using value_type = + typename Policy::value_type; + using reference = value_type const&; + using pointer = value_type const*; + using difference_type = std::ptrdiff_t; + using iterator_category = + std::forward_iterator_tag; + + const_iterator() = default; + + bool + operator==( + const_iterator const& other) const + { + return + other.list_ == list_ && + other.it_ == it_; + } + + bool + operator!=( + const_iterator const& other) const + { + return ! (*this == other); + } + + reference + operator*() const + { + return v_; + } + + const_iterator& + operator++() + { + increment(); + return *this; + } + + const_iterator + operator++(int) + { + auto temp = *this; + ++(*this); + return temp; + } + + bool + error() const + { + return error_; + } + + private: + friend class basic_parsed_list; + + const_iterator( + basic_parsed_list const& list, bool at_end) + : list_(&list) + , it_(at_end ? nullptr : + list.s_.begin()) + { + if(! at_end) + increment(); + } + + void + increment() + { + if(! this->member()( + v_, it_, list_->s_)) + { + it_ = nullptr; + error_ = true; + } + } + }; + + /// Construct a list from a string + explicit + basic_parsed_list(string_view s) + : s_(s) + { + } + + /// Return a const iterator to the beginning of the list + const_iterator begin() const; + + /// Return a const iterator to the end of the list + const_iterator end() const; + + /// Return a const iterator to the beginning of the list + const_iterator cbegin() const; + + /// Return a const iterator to the end of the list + const_iterator cend() const; +}; + +template +inline +auto +basic_parsed_list:: +begin() const -> + const_iterator +{ + return const_iterator{*this, false}; +} + +template +inline +auto +basic_parsed_list:: +end() const -> + const_iterator +{ + return const_iterator{*this, true}; +} + +template +inline +auto +basic_parsed_list:: +cbegin() const -> + const_iterator +{ + return const_iterator{*this, false}; +} + +template +inline +auto +basic_parsed_list:: +cend() const -> + const_iterator +{ + return const_iterator{*this, true}; +} + +} // detail +} // http +} // beast + +#endif + diff --git a/src/beast/include/beast/http/detail/basic_parser.hpp b/src/beast/include/beast/http/detail/basic_parser.hpp new file mode 100644 index 0000000000..d61a32dc93 --- /dev/null +++ b/src/beast/include/beast/http/detail/basic_parser.hpp @@ -0,0 +1,718 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_DETAIL_BASIC_PARSER_HPP +#define BEAST_HTTP_DETAIL_BASIC_PARSER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { +namespace detail { + +class basic_parser_base +{ +protected: + // limit on the size of the obs-fold buffer + // + // https://stackoverflow.com/questions/686217/maximum-on-http-header-values + // + static std::size_t constexpr max_obs_fold = 4096; + + enum class state + { + nothing_yet = 0, + start_line, + fields, + body0, + body, + body_to_eof0, + body_to_eof, + chunk_header0, + chunk_header, + chunk_body, + complete + }; + + static + bool + is_pathchar(char c) + { + // VFALCO This looks the same as the one below... + + // TEXT = + static bool constexpr tab[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 32 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 48 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 80 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 112 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 128 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 144 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 160 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 176 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 192 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 208 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 224 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 240 + }; + return tab[static_cast(c)]; + } + + static + inline + bool + unhex(unsigned char& d, char c) + { + static signed char constexpr tab[256] = { + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0 + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 16 + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 32 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1, // 48 + -1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 64 + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 80 + -1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 96 + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 // 112 + + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 // 128 + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 // 144 + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 // 160 + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 // 176 + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 // 192 + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 // 208 + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 // 224 + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 // 240 + }; + d = static_cast( + tab[static_cast(c)]); + return d != static_cast(-1); + } + + static + bool + is_digit(char c) + { + return static_cast(c-'0') < 10; + } + + static + bool + is_print(char c) + { + return static_cast(c-32) < 95; + } + + template + static + FwdIt + trim_front(FwdIt it, FwdIt const& end) + { + while(it != end) + { + if(*it != ' ' && *it != '\t') + break; + ++it; + } + return it; + } + + template + static + RanIt + trim_back( + RanIt it, RanIt const& first) + { + while(it != first) + { + auto const c = it[-1]; + if(c != ' ' && c != '\t') + break; + --it; + } + return it; + } + + static + string_view + make_string(char const* first, char const* last) + { + return {first, static_cast< + std::size_t>(last - first)}; + } + + //-------------------------------------------------------------------------- + + std::pair + find_fast( + char const* buf, + char const* buf_end, + char const* ranges, + size_t ranges_size) + { + bool found = false; + boost::ignore_unused(buf_end, ranges, ranges_size); + return {buf, found}; + } + + // VFALCO Can SIMD help this? + static + char const* + find_eol( + char const* it, char const* last, + error_code& ec) + { + for(;;) + { + if(it == last) + { + ec.assign(0, ec.category()); + return nullptr; + } + if(*it == '\r') + { + if(++it == last) + { + ec.assign(0, ec.category()); + return nullptr; + } + if(*it != '\n') + { + ec = error::bad_line_ending; + return nullptr; + } + ec.assign(0, ec.category()); + return ++it; + } + // VFALCO Should we handle the legacy case + // for lines terminated with a single '\n'? + ++it; + } + } + + // VFALCO Can SIMD help this? + static + char const* + find_eom(char const* p, char const* last) + { + for(;;) + { + if(p + 4 > last) + return nullptr; + if(p[3] != '\n') + { + if(p[3] == '\r') + ++p; + else + p += 4; + } + else if(p[2] != '\r') + { + p += 4; + } + else if(p[1] != '\n') + { + p += 2; + } + else if(p[0] != '\r') + { + p += 2; + } + else + { + return p + 4; + } + } + } + + //-------------------------------------------------------------------------- + + char const* + parse_token_to_eol( + char const* p, + char const* last, + char const*& token_last, + error_code& ec) + { + for(;; ++p) + { + if(p >= last) + { + ec = error::need_more; + return p; + } + if(BOOST_UNLIKELY(! is_print(*p))) + if((BOOST_LIKELY(static_cast< + unsigned char>(*p) < '\040') && + BOOST_LIKELY(*p != '\011')) || + BOOST_UNLIKELY(*p == '\177')) + goto found_control; + } + found_control: + if(BOOST_LIKELY(*p == '\r')) + { + if(++p >= last) + { + ec = error::need_more; + return last; + } + if(*p++ != '\n') + { + ec = error::bad_line_ending; + return last; + } + token_last = p - 2; + } + #if 0 + // VFALCO This allows `\n` by itself + // to terminate a line + else if(*p == '\n') + { + token_last = p; + ++p; + } + #endif + else + { + // invalid character + return nullptr; + } + return p; + } + + template + static + bool + parse_dec(Iter it, Iter last, Unsigned& v) + { + if(! is_digit(*it)) + return false; + v = *it - '0'; + for(;;) + { + if(! is_digit(*++it)) + break; + auto const d = *it - '0'; + if(v > ((std::numeric_limits< + Unsigned>::max)() - 10) / 10) + return false; + v = 10 * v + d; + } + return it == last; + } + + template + bool + parse_hex(Iter& it, Unsigned& v) + { + unsigned char d; + if(! unhex(d, *it)) + return false; + v = d; + for(;;) + { + if(! unhex(d, *++it)) + break; + auto const v0 = v; + v = 16 * v + d; + if(v < v0) + return false; + } + return true; + } + + static + bool + parse_crlf(char const*& it) + { + if( it[0] != '\r' || it[1] != '\n') + return false; + it += 2; + return true; + } + + static + void + parse_method( + char const*& it, char const* last, + string_view& result, error_code& ec) + { + // parse token SP + auto const first = it; + for(;; ++it) + { + if(it + 1 > last) + { + ec = error::need_more; + return; + } + if(! detail::is_tchar(*it)) + break; + } + if(it + 1 > last) + { + ec = error::need_more; + return; + } + if(*it != ' ') + { + ec = error::bad_method; + return; + } + if(it == first) + { + // cannot be empty + ec = error::bad_method; + return; + } + result = make_string(first, it++); + } + + static + void + parse_target( + char const*& it, char const* last, + string_view& result, error_code& ec) + { + // parse target SP + auto const first = it; + for(;; ++it) + { + if(it + 1 > last) + { + ec = error::need_more; + return; + } + if(! is_pathchar(*it)) + break; + } + if(it + 1 > last) + { + ec = error::need_more; + return; + } + if(*it != ' ') + { + ec = error::bad_target; + return; + } + if(it == first) + { + // cannot be empty + ec = error::bad_target; + return; + } + result = make_string(first, it++); + } + + static + void + parse_version( + char const*& it, char const* last, + int& result, error_code& ec) + { + if(it + 8 > last) + { + ec = error::need_more; + return; + } + if(*it++ != 'H') + { + ec = error::bad_version; + return; + } + if(*it++ != 'T') + { + ec = error::bad_version; + return; + } + if(*it++ != 'T') + { + ec = error::bad_version; + return; + } + if(*it++ != 'P') + { + ec = error::bad_version; + return; + } + if(*it++ != '/') + { + ec = error::bad_version; + return; + } + if(! is_digit(*it)) + { + ec = error::bad_version; + return; + } + result = 10 * (*it++ - '0'); + if(*it++ != '.') + { + ec = error::bad_version; + return; + } + if(! is_digit(*it)) + { + ec = error::bad_version; + return; + } + result += *it++ - '0'; + } + + static + void + parse_status( + char const*& it, char const* last, + unsigned short& result, error_code& ec) + { + // parse 3(digit) SP + if(it + 4 > last) + { + ec = error::need_more; + return; + } + if(! is_digit(*it)) + { + ec = error::bad_status; + return; + } + result = 100 * (*it++ - '0'); + if(! is_digit(*it)) + { + ec = error::bad_status; + return; + } + result += 10 * (*it++ - '0'); + if(! is_digit(*it)) + { + ec = error::bad_status; + return; + } + result += *it++ - '0'; + if(*it++ != ' ') + { + ec = error::bad_status; + return; + } + } + + void + parse_reason( + char const*& it, char const* last, + string_view& result, error_code& ec) + { + auto const first = it; + char const* token_last = nullptr; + auto p = parse_token_to_eol( + it, last, token_last, ec); + if(ec) + return; + if(! p) + { + ec = error::bad_reason; + return; + } + result = make_string(first, token_last); + it = p; + } + + template + void + parse_field( + char const*& p, + char const* last, + string_view& name, + string_view& value, + static_string& buf, + error_code& ec) + { + /* header-field = field-name ":" OWS field-value OWS + + field-name = token + field-value = *( field-content / obs-fold ) + field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] + field-vchar = VCHAR / obs-text + + obs-fold = CRLF 1*( SP / HTAB ) + ; obsolete line folding + ; see Section 3.2.4 + + token = 1* + CHAR = + sep = "(" | ")" | "<" | ">" | "@" + | "," | ";" | ":" | "\" | <"> + | "/" | "[" | "]" | "?" | "=" + | "{" | "}" | SP | HT + */ + static char const* is_token = + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\1\0\1\1\1\1\1\0\0\1\1\0\1\1\0\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0" + "\0\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\1\1" + "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\1\0\1\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; + + // name + BOOST_ALIGNMENT(16) static const char ranges1[] = + "\x00 " /* control chars and up to SP */ + "\"\"" /* 0x22 */ + "()" /* 0x28,0x29 */ + ",," /* 0x2c */ + "//" /* 0x2f */ + ":@" /* 0x3a-0x40 */ + "[]" /* 0x5b-0x5d */ + "{\377"; /* 0x7b-0xff */ + auto first = p; + bool found; + std::tie(p, found) = find_fast( + p, last, ranges1, sizeof(ranges1)-1); + if(! found && p >= last) + { + ec = error::need_more; + return; + } + for(;;) + { + if(*p == ':') + break; + if(! is_token[static_cast< + unsigned char>(*p)]) + { + ec = error::bad_field; + return; + } + ++p; + if(p >= last) + { + ec = error::need_more; + return; + } + } + if(p == first) + { + // empty name + ec = error::bad_field; + return; + } + name = make_string(first, p); + ++p; // eat ':' + char const* token_last; + for(;;) + { + // eat leading ' ' and '\t' + for(;;++p) + { + if(p + 1 > last) + { + ec = error::need_more; + return; + } + if(! (*p == ' ' || *p == '\t')) + break; + } + // parse to CRLF + first = p; + p = parse_token_to_eol(p, last, token_last, ec); + if(ec) + return; + if(! p) + { + ec = error::bad_value; + return; + } + // Look 1 char past the CRLF to handle obs-fold. + if(p + 1 > last) + { + ec = error::need_more; + return; + } + token_last = + trim_back(token_last, first); + if(*p != ' ' && *p != '\t') + { + value = make_string(first, token_last); + return; + } + ++p; + if(token_last != first) + break; + } + buf.resize(0); + buf.append(first, token_last); + BOOST_ASSERT(! buf.empty()); + try + { + for(;;) + { + // eat leading ' ' and '\t' + for(;;++p) + { + if(p + 1 > last) + { + ec = error::need_more; + return; + } + if(! (*p == ' ' || *p == '\t')) + break; + } + // parse to CRLF + first = p; + p = parse_token_to_eol(p, last, token_last, ec); + if(ec) + return; + if(! p) + { + ec = error::bad_value; + return; + } + // Look 1 char past the CRLF to handle obs-fold. + if(p + 1 > last) + { + ec = error::need_more; + return; + } + token_last = trim_back(token_last, first); + if(first != token_last) + { + buf.push_back(' '); + buf.append(first, token_last); + } + if(*p != ' ' && *p != '\t') + { + value = {buf.data(), buf.size()}; + return; + } + ++p; + } + } + catch(std::length_error const&) + { + ec = error::header_limit; + return; + } + } +}; + +} // detail +} // http +} // beast + +#endif diff --git a/src/beast/include/beast/http/detail/basic_parser_v1.hpp b/src/beast/include/beast/http/detail/basic_parser_v1.hpp deleted file mode 100644 index 2df947d142..0000000000 --- a/src/beast/include/beast/http/detail/basic_parser_v1.hpp +++ /dev/null @@ -1,146 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_HTTP_DETAIL_BASIC_PARSER_V1_HPP -#define BEAST_HTTP_DETAIL_BASIC_PARSER_V1_HPP - -#include - -namespace beast { -namespace http { -namespace detail { - -template -struct parser_str_t -{ - static char constexpr close[6] = "close"; - static char constexpr chunked[8] = "chunked"; - static char constexpr keep_alive[11] = "keep-alive"; - - static char constexpr upgrade[8] = "upgrade"; - static char constexpr connection[11] = "connection"; - static char constexpr content_length[15] = "content-length"; - static char constexpr proxy_connection[17] = "proxy-connection"; - static char constexpr transfer_encoding[18] = "transfer-encoding"; -}; - -template -char constexpr -parser_str_t<_>::close[6]; - -template -char constexpr -parser_str_t<_>::chunked[8]; - -template -char constexpr -parser_str_t<_>::keep_alive[11]; - -template -char constexpr -parser_str_t<_>::upgrade[8]; - -template -char constexpr -parser_str_t<_>::connection[11]; - -template -char constexpr -parser_str_t<_>::content_length[15]; - -template -char constexpr -parser_str_t<_>::proxy_connection[17]; - -template -char constexpr -parser_str_t<_>::transfer_encoding[18]; - -using parser_str = parser_str_t<>; - -class parser_base -{ -protected: - enum state : std::uint8_t - { - s_dead = 1, - - s_req_start, - s_req_method0, - s_req_method, - s_req_url0, - s_req_url, - s_req_http, - s_req_http_H, - s_req_http_HT, - s_req_http_HTT, - s_req_http_HTTP, - s_req_major, - s_req_dot, - s_req_minor, - s_req_cr, - s_req_lf, - - s_res_start, - s_res_H, - s_res_HT, - s_res_HTT, - s_res_HTTP, - s_res_major, - s_res_dot, - s_res_minor, - s_res_space_1, - s_res_status0, - s_res_status1, - s_res_status2, - s_res_space_2, - s_res_reason0, - s_res_reason, - s_res_line_lf, - s_res_line_done, - - s_header_name0, - s_header_name, - s_header_value0_lf, - s_header_value0_almost_done, - s_header_value0, - s_header_value, - s_header_value_lf, - s_header_value_almost_done, - s_header_value_unfold, - - s_headers_almost_done, - s_headers_done, - - s_chunk_size0, - s_chunk_size, - s_chunk_ext_name0, - s_chunk_ext_name, - s_chunk_ext_val, - s_chunk_size_lf, - s_chunk_data0, - s_chunk_data, - s_chunk_data_cr, - s_chunk_data_lf, - - s_body_pause, - s_body_identity0, - s_body_identity, - s_body_identity_eof0, - s_body_identity_eof, - - s_complete, - s_restart, - s_closed_complete - }; -}; - -} // detail -} // http -} // beast - -#endif diff --git a/src/beast/include/beast/http/detail/chunk_encode.hpp b/src/beast/include/beast/http/detail/chunk_encode.hpp index f496dcfe86..059f36344c 100644 --- a/src/beast/include/beast/http/detail/chunk_encode.hpp +++ b/src/beast/include/beast/http/detail/chunk_encode.hpp @@ -17,20 +17,22 @@ namespace beast { namespace http { namespace detail { -class chunk_encode_delim +/** A buffer sequence containing a chunk-encoding header +*/ +class chunk_header { boost::asio::const_buffer cb_; - // Storage for the longest hex string we might need, plus delimiters. - std::array buf_; + // Storage for the longest hex string we might need + char buf_[2 * sizeof(std::size_t)]; template void - copy(chunk_encode_delim const& other); + copy(chunk_header const& other); template void - setup(std::size_t n); + prepare_impl(std::size_t n); template static @@ -55,15 +57,27 @@ public: using const_iterator = value_type const*; - chunk_encode_delim(chunk_encode_delim const& other) + /** Constructor (default) + + Default-constructed chunk headers are in an + undefined state. + */ + chunk_header() = default; + + /// Copy constructor + chunk_header(chunk_header const& other) { copy(other); } + /** Construct a chunk header + + @param n The number of octets in this chunk. + */ explicit - chunk_encode_delim(std::size_t n) + chunk_header(std::size_t n) { - setup(n); + prepare_impl(n); } const_iterator @@ -77,31 +91,55 @@ public: { return begin() + 1; } + + void + prepare(std::size_t n) + { + prepare_impl(n); + } }; template void -chunk_encode_delim:: -copy(chunk_encode_delim const& other) +chunk_header:: +copy(chunk_header const& other) { + using boost::asio::buffer_copy; auto const n = boost::asio::buffer_size(other.cb_); - buf_ = other.buf_; - cb_ = boost::asio::const_buffer( - &buf_[buf_.size() - n], n); + auto const mb = boost::asio::mutable_buffers_1( + &buf_[sizeof(buf_) - n], n); + cb_ = *mb.begin(); + buffer_copy(mb, + boost::asio::const_buffers_1(other.cb_)); } template void -chunk_encode_delim:: -setup(std::size_t n) +chunk_header:: +prepare_impl(std::size_t n) { - buf_[buf_.size() - 2] = '\r'; - buf_[buf_.size() - 1] = '\n'; - auto it = to_hex(buf_.end() - 2, n); + auto const end = &buf_[sizeof(buf_)]; + auto it = to_hex(end, n); cb_ = boost::asio::const_buffer{&*it, static_cast( - std::distance(it, buf_.end()))}; + std::distance(it, end))}; +} + +/// Returns a buffer sequence holding a CRLF for chunk encoding +inline +boost::asio::const_buffers_1 +chunk_crlf() +{ + return {"\r\n", 2}; +} + +/// Returns a buffer sequence holding a final chunk header +inline +boost::asio::const_buffers_1 +chunk_final() +{ + return {"0\r\n", 3}; } } // detail diff --git a/src/beast/include/beast/http/detail/rfc7230.hpp b/src/beast/include/beast/http/detail/rfc7230.hpp index a68b4da835..cd7093d9ef 100644 --- a/src/beast/include/beast/http/detail/rfc7230.hpp +++ b/src/beast/include/beast/http/detail/rfc7230.hpp @@ -8,7 +8,7 @@ #ifndef BEAST_HTTP_DETAIL_RFC7230_HPP #define BEAST_HTTP_DETAIL_RFC7230_HPP -#include +#include #include #include @@ -45,8 +45,8 @@ is_alpha(char c) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 224 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 240 }; - static_assert(sizeof(tab) == 256, ""); - return tab[static_cast(c)]; + BOOST_STATIC_ASSERT(sizeof(tab) == 256); + return tab[static_cast(c)]; } inline @@ -72,8 +72,8 @@ is_text(char c) 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 224 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 240 }; - static_assert(sizeof(tab) == 256, ""); - return tab[static_cast(c)]; + BOOST_STATIC_ASSERT(sizeof(tab) == 256); + return tab[static_cast(c)]; } inline @@ -104,8 +104,8 @@ is_tchar(char c) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 224 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 240 }; - static_assert(sizeof(tab) == 256, ""); - return tab[static_cast(c)]; + BOOST_STATIC_ASSERT(sizeof(tab) == 256); + return tab[static_cast(c)]; } inline @@ -133,8 +133,8 @@ is_qdchar(char c) 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 224 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 240 }; - static_assert(sizeof(tab) == 256, ""); - return tab[static_cast(c)]; + BOOST_STATIC_ASSERT(sizeof(tab) == 256); + return tab[static_cast(c)]; } inline @@ -163,44 +163,8 @@ is_qpchar(char c) 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 224 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 240 }; - static_assert(sizeof(tab) == 256, ""); - return tab[static_cast(c)]; -} - -// converts to lower case, -// returns 0 if not a valid token char -// -inline -char -to_field_char(char c) -{ - /* token = 1* - CHAR = - sep = "(" | ")" | "<" | ">" | "@" - | "," | ";" | ":" | "\" | <"> - | "/" | "[" | "]" | "?" | "=" - | "{" | "}" | SP | HT - */ - static char constexpr tab[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, '!', 0, '#', '$', '%', '&', '\'', 0, 0, '*', '+', 0, '-', '.', 0, - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 0, 0, 0, 0, 0, 0, - 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', - 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, 0, 0, '^', '_', - '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', - 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, '|', 0, '~', 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - }; - static_assert(sizeof(tab) == 256, ""); - return tab[static_cast(c)]; + BOOST_STATIC_ASSERT(sizeof(tab) == 256); + return tab[static_cast(c)]; } // converts to lower case, @@ -229,15 +193,16 @@ to_value_char(char c) 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, // 224 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 // 240 }; - static_assert(sizeof(tab) == 256, ""); - return static_cast(tab[static_cast(c)]); + BOOST_STATIC_ASSERT(sizeof(tab) == 256); + return static_cast(tab[static_cast(c)]); } +// VFALCO TODO Make this return unsigned? inline std::int8_t unhex(char c) { - static char constexpr tab[] = { + static signed char constexpr tab[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 16 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 32 @@ -255,23 +220,69 @@ unhex(char c) -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 224 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 // 240 }; - static_assert(sizeof(tab) == 256, ""); - return tab[static_cast(c)]; + BOOST_STATIC_ASSERT(sizeof(tab) == 256); + return tab[static_cast(c)]; } template +inline void skip_ows(FwdIt& it, FwdIt const& end) { while(it != end) { - auto const c = *it; - if(c != ' ' && c != '\t') + if(*it != ' ' && *it != '\t') break; ++it; } } +template +inline +void +skip_ows_rev( + RanIt& it, RanIt const& first) +{ + while(it != first) + { + auto const c = it[-1]; + if(c != ' ' && c != '\t') + break; + --it; + } +} + +// obs-fold = CRLF 1*( SP / HTAB ) +// return `false` on parse error +// +template +inline +bool +skip_obs_fold( + FwdIt& it, FwdIt const& last) +{ + for(;;) + { + if(*it != '\r') + return true; + if(++it == last) + return false; + if(*it != '\n') + return false; + if(++it == last) + return false; + if(*it != ' ' && *it != '\t') + return false; + for(;;) + { + if(++it == last) + return true; + if(*it != ' ' && *it != '\t') + return true; + } + } +} + template void skip_token(FwdIt& it, FwdIt const& last) @@ -281,8 +292,8 @@ skip_token(FwdIt& it, FwdIt const& last) } inline -boost::string_ref -trim(boost::string_ref const& s) +string_view +trim(string_view s) { auto first = s.begin(); auto last = s.end(); @@ -302,12 +313,12 @@ trim(boost::string_ref const& s) struct param_iter { - using iter_type = boost::string_ref::const_iterator; + using iter_type = string_view::const_iterator; iter_type it; iter_type first; iter_type last; - std::pair v; + std::pair v; bool empty() const @@ -403,6 +414,53 @@ increment() } } +/* + #token = [ ( "," / token ) *( OWS "," [ OWS token ] ) ] +*/ +struct opt_token_list_policy +{ + using value_type = string_view; + + bool + operator()(value_type& v, + char const*& it, string_view s) const + { + v = {}; + auto need_comma = it != s.begin(); + for(;;) + { + detail::skip_ows(it, s.end()); + if(it == s.end()) + { + it = nullptr; + return true; + } + auto const c = *it; + if(detail::is_tchar(c)) + { + if(need_comma) + return false; + auto const p0 = it; + for(;;) + { + ++it; + if(it == s.end()) + break; + if(! detail::is_tchar(*it)) + break; + } + v = string_view{&*p0, + static_cast(it - p0)}; + return true; + } + if(c != ',') + return false; + need_comma = false; + ++it; + } + } +}; + } // detail } // http } // beast diff --git a/src/beast/include/beast/http/detail/type_traits.hpp b/src/beast/include/beast/http/detail/type_traits.hpp new file mode 100644 index 0000000000..bb10a2d7f5 --- /dev/null +++ b/src/beast/include/beast/http/detail/type_traits.hpp @@ -0,0 +1,188 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_DETAIL_TYPE_TRAITS_HPP +#define BEAST_HTTP_DETAIL_TYPE_TRAITS_HPP + +#include +#include +#include + +namespace beast { +namespace http { + +template +struct header; + +template +struct message; + +template +class parser; + +namespace detail { + +template +class is_header_impl +{ + template + static std::true_type check( + header const*); + static std::false_type check(...); +public: + using type = decltype(check((T*)0)); +}; + +template +using is_header = typename is_header_impl::type; + +template +struct is_parser : std::false_type {}; + +template +struct is_parser> : std::true_type {}; + +struct fields_model +{ + string_view method() const; + string_view reason() const; + string_view target() const; + +protected: + string_view get_method_impl() const; + string_view get_target_impl() const; + string_view get_reason_impl() const; + bool get_chunked_impl() const; + bool get_keep_alive_impl(unsigned) const; + void set_method_impl(string_view); + void set_target_impl(string_view); + void set_reason_impl(string_view); + void set_chunked_impl(bool); + void set_content_length_impl(boost::optional); + void set_keep_alive_impl(unsigned, bool); +}; + +template> +struct has_value_type : std::false_type {}; + +template +struct has_value_type > : std::true_type {}; + +/** Determine if a @b Body type has a size + + This metafunction is equivalent to `std::true_type` if + Body contains a static member function called `size`. +*/ +template +struct is_body_sized : std::false_type {}; + +template +struct is_body_sized() = + T::size(std::declval()), + (void)0)>> : std::true_type {}; + +template +struct is_fields_helper : T +{ + template + static auto f1(int) -> decltype( + std::declval() = std::declval().get_method_impl(), + std::true_type()); + static auto f1(...) -> std::false_type; + using t1 = decltype(f1(0)); + + template + static auto f2(int) -> decltype( + std::declval() = std::declval().get_target_impl(), + std::true_type()); + static auto f2(...) -> std::false_type; + using t2 = decltype(f2(0)); + + template + static auto f3(int) -> decltype( + std::declval() = std::declval().get_reason_impl(), + std::true_type()); + static auto f3(...) -> std::false_type; + using t3 = decltype(f3(0)); + + template + static auto f4(int) -> decltype( + std::declval() = std::declval().get_chunked_impl(), + std::true_type()); + static auto f4(...) -> std::false_type; + using t4 = decltype(f4(0)); + + template + static auto f5(int) -> decltype( + std::declval() = std::declval().get_keep_alive_impl( + std::declval()), + std::true_type()); + static auto f5(...) -> std::false_type; + using t5 = decltype(f5(0)); + + template + static auto f6(int) -> decltype( + void(std::declval().set_method_impl(std::declval())), + std::true_type()); + static auto f6(...) -> std::false_type; + using t6 = decltype(f6(0)); + + template + static auto f7(int) -> decltype( + void(std::declval().set_target_impl(std::declval())), + std::true_type()); + static auto f7(...) -> std::false_type; + using t7 = decltype(f7(0)); + + template + static auto f8(int) -> decltype( + void(std::declval().set_reason_impl(std::declval())), + std::true_type()); + static auto f8(...) -> std::false_type; + using t8 = decltype(f8(0)); + + template + static auto f9(int) -> decltype( + void(std::declval().set_chunked_impl(std::declval())), + std::true_type()); + static auto f9(...) -> std::false_type; + using t9 = decltype(f9(0)); + + template + static auto f10(int) -> decltype( + void(std::declval().set_content_length_impl( + std::declval>())), + std::true_type()); + static auto f10(...) -> std::false_type; + using t10 = decltype(f10(0)); + + template + static auto f11(int) -> decltype( + void(std::declval().set_keep_alive_impl( + std::declval(), + std::declval())), + std::true_type()); + static auto f11(...) -> std::false_type; + using t11 = decltype(f11(0)); + + using type = std::integral_constant; +}; + +} // detail +} // http +} // beast + +#endif diff --git a/src/beast/include/beast/http/dynamic_body.hpp b/src/beast/include/beast/http/dynamic_body.hpp new file mode 100644 index 0000000000..c51420848e --- /dev/null +++ b/src/beast/include/beast/http/dynamic_body.hpp @@ -0,0 +1,168 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_DYNAMIC_BODY_HPP +#define BEAST_HTTP_DYNAMIC_BODY_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +/** A @b Body using a @b DynamicBuffer + + This body uses a @b DynamicBuffer as a memory-based container + for holding message payloads. Messages using this body type + may be serialized and parsed. +*/ +template +struct basic_dynamic_body +{ + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + + /** The type of container used for the body + + This determines the type of @ref message::body + when this body type is used with a message container. + */ + using value_type = DynamicBuffer; + + /** Returns the payload size of the body + + When this body is used with @ref message::prepare_payload, + the Content-Length will be set to the payload size, and + any chunked Transfer-Encoding will be removed. + */ + static + std::uint64_t + size(value_type const& v) + { + return v.size(); + } + + /** The algorithm for serializing the body + + Meets the requirements of @b BodyReader. + */ +#if BEAST_DOXYGEN + using reader = implementation_defined; +#else + class reader + { + DynamicBuffer const& body_; + + public: + using const_buffers_type = + typename DynamicBuffer::const_buffers_type; + + template + explicit + reader(message const& m) + : body_(m.body) + { + } + + void + init(error_code& ec) + { + ec.assign(0, ec.category()); + } + + boost::optional> + get(error_code& ec) + { + ec.assign(0, ec.category()); + return {{body_.data(), false}}; + } + }; +#endif + + /** The algorithm for parsing the body + + Meets the requirements of @b BodyReader. + */ +#if BEAST_DOXYGEN + using writer = implementation_defined; +#else + class writer + { + value_type& body_; + + public: + template + explicit + writer(message& msg) + : body_(msg.body) + { + } + + void + init(boost::optional const&, error_code& ec) + { + ec.assign(0, ec.category()); + } + + template + std::size_t + put(ConstBufferSequence const& buffers, + error_code& ec) + { + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + auto const n = buffer_size(buffers); + if(body_.size() > body_.max_size() - n) + { + ec = error::buffer_overflow; + return 0; + } + boost::optional b; + try + { + b.emplace(body_.prepare((std::min)(n, + body_.max_size() - body_.size()))); + } + catch(std::length_error const&) + { + ec = error::buffer_overflow; + return 0; + } + ec.assign(0, ec.category()); + auto const bytes_transferred = + buffer_copy(*b, buffers); + body_.commit(bytes_transferred); + return bytes_transferred; + } + + void + finish(error_code& ec) + { + ec.assign(0, ec.category()); + } + }; +#endif +}; + +/** A dynamic message body represented by a @ref multi_buffer + + Meets the requirements of @b Body. +*/ +using dynamic_body = basic_dynamic_body; + +} // http +} // beast + +#endif diff --git a/src/beast/include/beast/http/empty_body.hpp b/src/beast/include/beast/http/empty_body.hpp index 40f549d9f4..2db58a011b 100644 --- a/src/beast/include/beast/http/empty_body.hpp +++ b/src/beast/include/beast/http/empty_body.hpp @@ -9,62 +9,117 @@ #define BEAST_HTTP_EMPTY_BODY_HPP #include -#include +#include #include -#include -#include -#include -#include +#include namespace beast { namespace http { -/** An empty content-body. +/** An empty @b Body - Meets the requirements of @b `Body`. + This body is used to represent messages which do not have a + message body. If this body is used with a parser, and the + parser encounters octets corresponding to a message body, + the parser will fail with the error @ref http::unexpected_body. + + The Content-Length of this body is always 0. */ struct empty_body { -#if GENERATING_DOCS - /// The type of the `message::body` member - using value_type = void; + /** The type of container used for the body + + This determines the type of @ref message::body + when this body type is used with a message container. + */ + struct value_type + { + }; + + /** Returns the payload size of the body + + When this body is used with @ref message::prepare_payload, + the Content-Length will be set to the payload size, and + any chunked Transfer-Encoding will be removed. + */ + static + std::uint64_t + size(value_type) + { + return 0; + } + + /** The algorithm for serializing the body + + Meets the requirements of @b BodyReader. + */ +#if BEAST_DOXYGEN + using reader = implementation_defined; #else - struct value_type {}; + struct reader + { + using const_buffers_type = + boost::asio::null_buffers; + + template + explicit + reader(message const&) + { + } + + void + init(error_code& ec) + { + ec.assign(0, ec.category()); + } + + boost::optional> + get(error_code& ec) + { + ec.assign(0, ec.category()); + return boost::none; + } + }; #endif -#if GENERATING_DOCS -private: -#endif + /** The algorithm for parsing the body + Meets the requirements of @b BodyReader. + */ +#if BEAST_DOXYGEN + using writer = implementation_defined; +#else struct writer { template explicit - writer(message const& m) noexcept + writer(message&) { - beast::detail::ignore_unused(m); } void - init(error_code& ec) noexcept + init(boost::optional const&, error_code& ec) { - beast::detail::ignore_unused(ec); + ec.assign(0, ec.category()); } - std::uint64_t - content_length() const noexcept + template + std::size_t + put(ConstBufferSequence const&, + error_code& ec) { + ec = error::unexpected_body; return 0; } - template - bool - write(error_code&, WriteFunction&& wf) noexcept + void + finish(error_code& ec) { - wf(boost::asio::null_buffers{}); - return true; + ec.assign(0, ec.category()); } }; +#endif }; } // http diff --git a/src/beast/include/beast/http/error.hpp b/src/beast/include/beast/http/error.hpp new file mode 100644 index 0000000000..861620e342 --- /dev/null +++ b/src/beast/include/beast/http/error.hpp @@ -0,0 +1,150 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_ERROR_HPP +#define BEAST_HTTP_ERROR_HPP + +#include +#include + +namespace beast { +namespace http { + +/// Error codes returned from HTTP algorithms and operations. +enum class error +{ + /** The end of the stream was reached. + + This error is returned under the following conditions: + + @li When attempting to read HTTP data from a stream and the stream + read returns the error `boost::asio::error::eof` before any new octets + have been received. + + @li When sending a complete HTTP message at once and the semantics of + the message are that the connection should be closed to indicate the + end of the message. + */ + end_of_stream = 1, + + /** The incoming message is incomplete. + + This happens when the end of stream is reached during + parsing and some octets have been received, but not the + entire message. + */ + partial_message, + + /** Additional buffers are required. + + This error is returned during parsing when additional + octets are needed. The caller should append more data + to the existing buffer and retry the parse operaetion. + */ + need_more, + + /** An unexpected body was encountered during parsing. + + This error is returned when attempting to parse body + octets into a message container which has the + @ref empty_body body type. + + @see @ref empty_body + */ + unexpected_body, + + /** Additional buffers are required. + + This error is returned under the following conditions: + + @li During serialization when using @ref buffer_body. + The caller should update the body to point to a new + buffer or indicate that there are no more octets in + the body. + + @li During parsing when using @ref buffer_body. + The caller should update the body to point to a new + storage area to receive additional body octets. + */ + need_buffer, + + /** Buffer maximum exceeded. + + This error is returned when reading HTTP content + into a dynamic buffer, and the operation would + exceed the maximum size of the buffer. + */ + buffer_overflow, + + /** Header limit exceeded. + + The parser detected an incoming message header which + exceeded a configured limit. + */ + header_limit, + + /** Body limit exceeded. + + The parser detected an incoming message body which + exceeded a configured limit. + */ + body_limit, + + /** A memory allocation failed. + + When basic_fields throws std::bad_alloc, it is + converted into this error by @ref parser. + */ + bad_alloc, + + // + // (parser errors) + // + + /// The line ending was malformed + bad_line_ending, + + /// The method is invalid. + bad_method, + + /// The request-target is invalid. + bad_target, + + /// The HTTP-version is invalid. + bad_version, + + /// The status-code is invalid. + bad_status, + + /// The reason-phrase is invalid. + bad_reason, + + /// The field name is invalid. + bad_field, + + /// The field value is invalid. + bad_value, + + /// The Content-Length is invalid. + bad_content_length, + + /// The Transfer-Encoding is invalid. + bad_transfer_encoding, + + /// The chunk syntax is invalid. + bad_chunk, + + /// An obs-fold exceeded an internal limit. + bad_obs_fold +}; + +} // http +} // beast + +#include + +#endif diff --git a/src/beast/include/beast/http/field.hpp b/src/beast/include/beast/http/field.hpp new file mode 100644 index 0000000000..62d6a3f7bf --- /dev/null +++ b/src/beast/include/beast/http/field.hpp @@ -0,0 +1,405 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_FIELD_HPP +#define BEAST_HTTP_FIELD_HPP + +#include +#include +#include + +namespace beast { +namespace http { + +enum class field : unsigned short +{ + unknown = 0, + + a_im, + accept, + accept_additions, + accept_charset, + accept_datetime, + accept_encoding, + accept_features, + accept_language, + accept_patch, + accept_post, + accept_ranges, + access_control, + access_control_allow_credentials, + access_control_allow_headers, + access_control_allow_methods, + access_control_allow_origin, + access_control_max_age, + access_control_request_headers, + access_control_request_method, + age, + allow, + alpn, + also_control, + alt_svc, + alt_used, + alternate_recipient, + alternates, + apparently_to, + apply_to_redirect_ref, + approved, + archive, + archived_at, + article_names, + article_updates, + authentication_control, + authentication_info, + authentication_results, + authorization, + auto_submitted, + autoforwarded, + autosubmitted, + base, + bcc, + body, + c_ext, + c_man, + c_opt, + c_pep, + c_pep_info, + cache_control, + caldav_timezones, + cancel_key, + cancel_lock, + cc, + close, + comments, + compliance, + connection, + content_alternative, + content_base, + content_description, + content_disposition, + content_duration, + content_encoding, + content_features, + content_id, + content_identifier, + content_language, + content_length, + content_location, + content_md5, + content_range, + content_return, + content_script_type, + content_style_type, + content_transfer_encoding, + content_type, + content_version, + control, + conversion, + conversion_with_loss, + cookie, + cookie2, + cost, + dasl, + date, + date_received, + dav, + default_style, + deferred_delivery, + delivery_date, + delta_base, + depth, + derived_from, + destination, + differential_id, + digest, + discarded_x400_ipms_extensions, + discarded_x400_mts_extensions, + disclose_recipients, + disposition_notification_options, + disposition_notification_to, + distribution, + dkim_signature, + dl_expansion_history, + downgraded_bcc, + downgraded_cc, + downgraded_disposition_notification_to, + downgraded_final_recipient, + downgraded_from, + downgraded_in_reply_to, + downgraded_mail_from, + downgraded_message_id, + downgraded_original_recipient, + downgraded_rcpt_to, + downgraded_references, + downgraded_reply_to, + downgraded_resent_bcc, + downgraded_resent_cc, + downgraded_resent_from, + downgraded_resent_reply_to, + downgraded_resent_sender, + downgraded_resent_to, + downgraded_return_path, + downgraded_sender, + downgraded_to, + ediint_features, + eesst_version, + encoding, + encrypted, + errors_to, + etag, + expect, + expires, + expiry_date, + ext, + followup_to, + forwarded, + from, + generate_delivery_report, + getprofile, + hobareg, + host, + http2_settings, + if_, + if_match, + if_modified_since, + if_none_match, + if_range, + if_schedule_tag_match, + if_unmodified_since, + im, + importance, + in_reply_to, + incomplete_copy, + injection_date, + injection_info, + jabber_id, + keep_alive, + keywords, + label, + language, + last_modified, + latest_delivery_time, + lines, + link, + list_archive, + list_help, + list_id, + list_owner, + list_post, + list_subscribe, + list_unsubscribe, + list_unsubscribe_post, + location, + lock_token, + man, + max_forwards, + memento_datetime, + message_context, + message_id, + message_type, + meter, + method_check, + method_check_expires, + mime_version, + mmhs_acp127_message_identifier, + mmhs_authorizing_users, + mmhs_codress_message_indicator, + mmhs_copy_precedence, + mmhs_exempted_address, + mmhs_extended_authorisation_info, + mmhs_handling_instructions, + mmhs_message_instructions, + mmhs_message_type, + mmhs_originator_plad, + mmhs_originator_reference, + mmhs_other_recipients_indicator_cc, + mmhs_other_recipients_indicator_to, + mmhs_primary_precedence, + mmhs_subject_indicator_codes, + mt_priority, + negotiate, + newsgroups, + nntp_posting_date, + nntp_posting_host, + non_compliance, + obsoletes, + opt, + optional, + optional_www_authenticate, + ordering_type, + organization, + origin, + original_encoded_information_types, + original_from, + original_message_id, + original_recipient, + original_sender, + original_subject, + originator_return_address, + overwrite, + p3p, + path, + pep, + pep_info, + pics_label, + position, + posting_version, + pragma, + prefer, + preference_applied, + prevent_nondelivery_report, + priority, + privicon, + profileobject, + protocol, + protocol_info, + protocol_query, + protocol_request, + proxy_authenticate, + proxy_authentication_info, + proxy_authorization, + proxy_connection, + proxy_features, + proxy_instruction, + public_, + public_key_pins, + public_key_pins_report_only, + range, + received, + received_spf, + redirect_ref, + references, + referer, + referer_root, + relay_version, + reply_by, + reply_to, + require_recipient_valid_since, + resent_bcc, + resent_cc, + resent_date, + resent_from, + resent_message_id, + resent_reply_to, + resent_sender, + resent_to, + resolution_hint, + resolver_location, + retry_after, + return_path, + safe, + schedule_reply, + schedule_tag, + sec_websocket_accept, + sec_websocket_extensions, + sec_websocket_key, + sec_websocket_protocol, + sec_websocket_version, + security_scheme, + see_also, + sender, + sensitivity, + server, + set_cookie, + set_cookie2, + setprofile, + sio_label, + sio_label_history, + slug, + soapaction, + solicitation, + status_uri, + strict_transport_security, + subject, + subok, + subst, + summary, + supersedes, + surrogate_capability, + surrogate_control, + tcn, + te, + timeout, + title, + to, + topic, + trailer, + transfer_encoding, + ttl, + ua_color, + ua_media, + ua_pixels, + ua_resolution, + ua_windowpixels, + upgrade, + urgency, + uri, + user_agent, + variant_vary, + vary, + vbr_info, + version, + via, + want_digest, + warning, + www_authenticate, + x_archived_at, + x_device_accept, + x_device_accept_charset, + x_device_accept_encoding, + x_device_accept_language, + x_device_user_agent, + x_frame_options, + x_mittente, + x_pgp_sig, + x_ricevuta, + x_riferimento_message_id, + x_tiporicevuta, + x_trasporto, + x_verificasicurezza, + x400_content_identifier, + x400_content_return, + x400_content_type, + x400_mts_identifier, + x400_originator, + x400_received, + x400_recipients, + x400_trace, + xref, +}; + +/** Convert a field enum to a string. + + @param f The field to convert +*/ +string_view +to_string(field f); + +/** Attempt to convert a string to a field enum. + + The string comparison is case-insensitive. + + @return The corresponding field, or @ref field::unknown + if no known field matches. +*/ +field +string_to_field(string_view s); + +/// Write the text for a field name to an output stream. +inline +std::ostream& +operator<<(std::ostream& os, field f) +{ + return os << to_string(f); +} + +} // http +} // beast + +#include + +#endif diff --git a/src/beast/include/beast/http/fields.hpp b/src/beast/include/beast/http/fields.hpp index 7c2ef2a59a..005c4ab2db 100644 --- a/src/beast/include/beast/http/fields.hpp +++ b/src/beast/include/beast/http/fields.hpp @@ -9,17 +9,709 @@ #define BEAST_HTTP_FIELDS_HPP #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include namespace beast { namespace http { +/** A container for storing HTTP header fields. + + This container is designed to store the field value pairs that make + up the fields and trailers in an HTTP message. Objects of this type + are iterable, with each element holding the field name and field + value. + + Field names are stored as-is, but comparisons are case-insensitive. + The container behaves as a `std::multiset`; there will be a separate + value for each occurrence of the same field name. When the container + is iterated the fields are presented in the order of insertion, with + fields having the same name following each other consecutively. + + Meets the requirements of @b Fields + + @tparam Allocator The allocator to use. This must meet the + requirements of @b Allocator. +*/ +template +class basic_fields +{ + static std::size_t constexpr max_static_buffer = 4096; + + using off_t = std::uint16_t; + +public: + /// The type of allocator used. + using allocator_type = Allocator; + + /// The type of element used to represent a field + class value_type + { + friend class basic_fields; + + boost::asio::const_buffer + buffer() const; + + value_type(field name, + string_view sname, string_view value); + + boost::intrusive::list_member_hook< + boost::intrusive::link_mode< + boost::intrusive::normal_link>> + list_hook_; + boost::intrusive::set_member_hook< + boost::intrusive::link_mode< + boost::intrusive::normal_link>> + set_hook_; + off_t off_; + off_t len_; + field f_; + + public: + /// Returns the field enum, which can be @ref field::unknown + field + name() const; + + /// Returns the field name as a string + string_view + name_string() const; + + /// Returns the value of the field + string_view + value() const; + }; + + /** A strictly less predicate for comparing keys, using a case-insensitive comparison. + + The case-comparison operation is defined only for low-ASCII characters. + */ + struct key_compare : beast::iless + { + /// Returns `true` if lhs is less than rhs using a strict ordering + template + bool + operator()(String const& lhs, value_type const& rhs) const + { + if(lhs.size() < rhs.name_string().size()) + return true; + if(lhs.size() > rhs.name_string().size()) + return false; + return iless::operator()(lhs, rhs.name_string()); + } + + /// Returns `true` if lhs is less than rhs using a strict ordering + template + bool + operator()(value_type const& lhs, String const& rhs) const + { + if(lhs.name_string().size() < rhs.size()) + return true; + if(lhs.name_string().size() > rhs.size()) + return false; + return iless::operator()(lhs.name_string(), rhs); + } + + /// Returns `true` if lhs is less than rhs using a strict ordering + bool + operator()(value_type const& lhs, value_type const& rhs) const + { + if(lhs.name_string().size() < rhs.name_string().size()) + return true; + if(lhs.name_string().size() > rhs.name_string().size()) + return false; + return iless::operator()(lhs.name_string(), rhs.name_string()); + } + }; + + /// The algorithm used to serialize the header +#if BEAST_DOXYGEN + using reader = implementation_defined; +#else + class reader; +#endif + +private: + using list_t = typename boost::intrusive::make_list< + value_type, boost::intrusive::member_hook< + value_type, boost::intrusive::list_member_hook< + boost::intrusive::link_mode< + boost::intrusive::normal_link>>, + &value_type::list_hook_>, + boost::intrusive::constant_time_size< + false>>::type; + + using set_t = typename boost::intrusive::make_multiset< + value_type, boost::intrusive::member_hook>, + &value_type::set_hook_>, + boost::intrusive::constant_time_size, + boost::intrusive::compare>::type; + + +protected: + friend class fields_test; // for `header` + + /// Destructor + ~basic_fields(); + + /// Constructor. + basic_fields() = default; + + /** Constructor. + + @param alloc The allocator to use. + */ + explicit + basic_fields(Allocator const& alloc); + + /** Move constructor. + + The state of the moved-from object is + as if constructed using the same allocator. + */ + basic_fields(basic_fields&&); + + /** Move constructor. + + The state of the moved-from object is + as if constructed using the same allocator. + + @param alloc The allocator to use. + */ + basic_fields(basic_fields&&, Allocator const& alloc); + + /// Copy constructor. + basic_fields(basic_fields const&); + + /** Copy constructor. + + @param alloc The allocator to use. + */ + basic_fields(basic_fields const&, Allocator const& alloc); + + /// Copy constructor. + template + basic_fields(basic_fields const&); + + /** Copy constructor. + + @param alloc The allocator to use. + */ + template + basic_fields(basic_fields const&, + Allocator const& alloc); + + /** Move assignment. + + The state of the moved-from object is + as if constructed using the same allocator. + */ + basic_fields& operator=(basic_fields&&); + + /// Copy assignment. + basic_fields& operator=(basic_fields const&); + + /// Copy assignment. + template + basic_fields& operator=(basic_fields const&); + +public: + /// A constant iterator to the field sequence. +#if BEAST_DOXYGEN + using const_iterator = implementation_defined; +#else + using const_iterator = typename list_t::const_iterator; +#endif + + /// A constant iterator to the field sequence. + using iterator = const_iterator; + + /// Return a copy of the allocator associated with the container. + allocator_type + get_allocator() const + { + return typename std::allocator_traits< + Allocator>::template rebind_alloc< + value_type>(alloc_); + } + + //-------------------------------------------------------------------------- + // + // Element access + // + //-------------------------------------------------------------------------- + + /** Returns the value for a field, or throws an exception. + + @param name The name of the field. + + @return The field value. + + @throws std::out_of_range if the field is not found. + */ + string_view + at(field name) const; + + /** Returns the value for a field, or throws an exception. + + @param name The name of the field. + + @return The field value. + + @throws std::out_of_range if the field is not found. + */ + string_view + at(string_view name) const; + + /** Returns the value for a field, or `""` if it does not exist. + + If more than one field with the specified name exists, the + first field defined by insertion order is returned. + + @param name The name of the field. + */ + string_view + operator[](field name) const; + + /** Returns the value for a case-insensitive matching header, or `""` if it does not exist. + + If more than one field with the specified name exists, the + first field defined by insertion order is returned. + + @param name The name of the field. + */ + string_view + operator[](string_view name) const; + + //-------------------------------------------------------------------------- + // + // Iterators + // + //-------------------------------------------------------------------------- + + /// Return a const iterator to the beginning of the field sequence. + const_iterator + begin() const + { + return list_.cbegin(); + } + + /// Return a const iterator to the end of the field sequence. + const_iterator + end() const + { + return list_.cend(); + } + + /// Return a const iterator to the beginning of the field sequence. + const_iterator + cbegin() const + { + return list_.cbegin(); + } + + /// Return a const iterator to the end of the field sequence. + const_iterator + cend() const + { + return list_.cend(); + } + + //-------------------------------------------------------------------------- + // + // Capacity + // + //-------------------------------------------------------------------------- + +private: + // VFALCO Since the header and message derive from Fields, + // what does the expression m.empty() mean? Its confusing. + bool + empty() const + { + return list_.empty(); + } +public: + + //-------------------------------------------------------------------------- + // + // Modifiers + // + //-------------------------------------------------------------------------- + +private: + // VFALCO But this leaves behind the method, target, and reason! + /** Remove all fields from the container + + All references, pointers, or iterators referring to contained + elements are invalidated. All past-the-end iterators are also + invalidated. + */ + void + clear(); +public: + + /** Insert a field. + + If one or more fields with the same name already exist, + the new field will be inserted after the last field with + the matching name, in serialization order. + + @param name The field name. + + @param value The value of the field, as a @ref string_param + */ + void + insert(field name, string_param const& value); + + /** Insert a field. + + If one or more fields with the same name already exist, + the new field will be inserted after the last field with + the matching name, in serialization order. + + @param name The field name. + + @param value The value of the field, as a @ref string_param + */ + void + insert(string_view name, string_param const& value); + + /** Insert a field. + + If one or more fields with the same name already exist, + the new field will be inserted after the last field with + the matching name, in serialization order. + + @param name The field name. + + @param name_string The literal text corresponding to the + field name. If `name != field::unknown`, then this value + must be equal to `to_string(name)` using a case-insensitive + comparison, otherwise the behavior is undefined. + + @param value The value of the field, as a @ref string_param + */ + void + insert(field name, string_view name_string, + string_param const& value); + + /** Set a field value, removing any other instances of that field. + + First removes any values with matching field names, then + inserts the new field value. + + @param name The field name. + + @param value The value of the field, as a @ref string_param + + @return The field value. + */ + void + set(field name, string_param const& value); + + /** Set a field value, removing any other instances of that field. + + First removes any values with matching field names, then + inserts the new field value. + + @param name The field name. + + @param value The value of the field, as a @ref string_param + */ + void + set(string_view name, string_param const& value); + + /** Remove a field. + + References and iterators to the erased elements are + invalidated. Other references and iterators are not + affected. + + @param pos An iterator to the element to remove. + + @return An iterator following the last removed element. + If the iterator refers to the last element, the end() + iterator is returned. + */ + const_iterator + erase(const_iterator pos); + + /** Remove all fields with the specified name. + + All fields with the same field name are erased from the + container. + References and iterators to the erased elements are + invalidated. Other references and iterators are not + affected. + + @param name The field name. + + @return The number of fields removed. + */ + std::size_t + erase(field name); + + /** Remove all fields with the specified name. + + All fields with the same field name are erased from the + container. + References and iterators to the erased elements are + invalidated. Other references and iterators are not + affected. + + @param name The field name. + + @return The number of fields removed. + */ + std::size_t + erase(string_view name); + + /// Swap this container with another + void + swap(basic_fields& other); + + /// Swap two field containers + template + friend + void + swap(basic_fields& lhs, basic_fields& rhs); + + //-------------------------------------------------------------------------- + // + // Lookup + // + //-------------------------------------------------------------------------- + + /** Return the number of fields with the specified name. + + @param name The field name. + */ + std::size_t + count(field name) const; + + /** Return the number of fields with the specified name. + + @param name The field name. + */ + std::size_t + count(string_view name) const; + + /** Returns an iterator to the case-insensitive matching field. + + If more than one field with the specified name exists, the + first field defined by insertion order is returned. + + @param name The field name. + + @return An iterator to the matching field, or `end()` if + no match was found. + */ + const_iterator + find(field name) const; + + /** Returns an iterator to the case-insensitive matching field name. + + If more than one field with the specified name exists, the + first field defined by insertion order is returned. + + @param name The field name. + + @return An iterator to the matching field, or `end()` if + no match was found. + */ + const_iterator + find(string_view name) const; + + /** Returns a range of iterators to the fields with the specified name. + + @param name The field name. + + @return A range of iterators to fields with the same name, + otherwise an empty range. + */ + std::pair + equal_range(field name) const; + + /** Returns a range of iterators to the fields with the specified name. + + @param name The field name. + + @return A range of iterators to fields with the same name, + otherwise an empty range. + */ + std::pair + equal_range(string_view name) const; + + //-------------------------------------------------------------------------- + // + // Observers + // + //-------------------------------------------------------------------------- + + /// Returns a copy of the key comparison function + key_compare + key_comp() const + { + return key_compare{}; + } + +protected: + /** Returns the request-method string. + + @note Only called for requests. + */ + string_view + get_method_impl() const; + + /** Returns the request-target string. + + @note Only called for requests. + */ + string_view + get_target_impl() const; + + /** Returns the response reason-phrase string. + + @note Only called for responses. + */ + string_view + get_reason_impl() const; + + /** Returns the chunked Transfer-Encoding setting + */ + bool + get_chunked_impl() const; + + /** Returns the keep-alive setting + */ + bool + get_keep_alive_impl(unsigned version) const; + + /** Set or clear the method string. + + @note Only called for requests. + */ + void + set_method_impl(string_view s); + + /** Set or clear the target string. + + @note Only called for requests. + */ + void + set_target_impl(string_view s); + + /** Set or clear the reason string. + + @note Only called for responses. + */ + void + set_reason_impl(string_view s); + + /** Adjusts the chunked Transfer-Encoding value + */ + void + set_chunked_impl(bool value); + + /** Sets or clears the Content-Length field + */ + void + set_content_length_impl( + boost::optional const& value); + + /** Adjusts the Connection field + */ + void + set_keep_alive_impl( + unsigned version, bool keep_alive); + +private: + template + friend class basic_fields; + + using alloc_type = typename + std::allocator_traits:: + template rebind_alloc; + + using alloc_traits = + std::allocator_traits; + + using size_type = + typename std::allocator_traits::size_type; + + value_type& + new_element(field name, + string_view sname, string_view value); + + void + delete_element(value_type& e); + + void + set_element(value_type& e); + + void + realloc_string(string_view& dest, string_view s); + + void + realloc_target( + string_view& dest, string_view s); + + template + void + copy_all(basic_fields const&); + + void + clear_all(); + + void + delete_list(); + + void + move_assign(basic_fields&, std::true_type); + + void + move_assign(basic_fields&, std::false_type); + + void + copy_assign(basic_fields const&, std::true_type); + + void + copy_assign(basic_fields const&, std::false_type); + + void + swap(basic_fields& other, std::true_type); + + void + swap(basic_fields& other, std::false_type); + + alloc_type alloc_; + set_t set_; + list_t list_; + string_view method_; + string_view target_or_reason_; +}; + /// A typical HTTP header fields container -using fields = - basic_fields>; +using fields = basic_fields>; } // http } // beast +#include + #endif diff --git a/src/beast/include/beast/http/file_body.hpp b/src/beast/include/beast/http/file_body.hpp new file mode 100644 index 0000000000..bcc3ab407c --- /dev/null +++ b/src/beast/include/beast/http/file_body.hpp @@ -0,0 +1,522 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_FILE_BODY_HPP +#define BEAST_HTTP_FILE_BODY_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +//[example_http_file_body_1 + +/** A message body represented by a file on the filesystem. + + Messages with this type have bodies represented by a + file on the file system. When parsing a message using + this body type, the data is stored in the file pointed + to by the path, which must be writable. When serializing, + the implementation will read the file and present those + octets as the body content. This may be used to serve + content from a directory as part of a web service. + + @tparam File The implementation to use for accessing files. + This type must meet the requirements of @b File. +*/ +template +struct basic_file_body +{ + static_assert(is_file::value, + "File requirements not met"); + + /// The type of File this body uses + using file_type = File; + + /** Algorithm for retrieving buffers when serializing. + + Objects of this type are created during serialization + to extract the buffers representing the body. + */ + class reader; + + /** Algorithm for storing buffers when parsing. + + Objects of this type are created during parsing + to store incoming buffers representing the body. + */ + class writer; + + /** The type of the @ref message::body member. + + Messages declared using `basic_file_body` will have this + type for the body member. This rich class interface + allow the file to be opened with the file handle + maintained directly in the object, which is attached + to the message. + */ + class value_type; + + /** Returns the size of the body + + @param v The file body to use + */ + static + std::uint64_t + size(value_type const& v); +}; + +//] + +//[example_http_file_body_2 + +// The body container holds a handle to the file when +// it is open, and also caches the size when set. +// +template +class basic_file_body::value_type +{ + friend class reader; + friend class writer; + friend struct basic_file_body; + + // This represents the open file + File file_; + + // The cached file size + std::uint64_t file_size_ = 0; + +public: + /** Destructor. + + If the file is open, it is closed first. + */ + ~value_type() = default; + + /// Constructor + value_type() = default; + + /// Constructor + value_type(value_type&& other) = default; + + /// Move assignment + value_type& operator=(value_type&& other) = default; + + /// Returns `true` if the file is open + bool + is_open() const + { + return file_.is_open(); + } + + /// Returns the size of the file if open + std::uint64_t + size() const + { + return file_size_; + } + + /// Close the file if open + void + close(); + + /** Open a file at the given path with the specified mode + + @param path The utf-8 encoded path to the file + + @param mode The file mode to use + + @param ec Set to the error, if any occurred + */ + void + open(char const* path, file_mode mode, error_code& ec); + + /** Set the open file + + This function is used to set the open + */ + void + reset(File&& file, error_code& ec); +}; + +template +void +basic_file_body:: +value_type:: +close() +{ + error_code ignored; + file_.close(ignored); +} + +template +void +basic_file_body:: +value_type:: +open(char const* path, file_mode mode, error_code& ec) +{ + // Open the file + file_.open(path, mode, ec); + if(ec) + return; + + // Cache the size + file_size_ = file_.size(ec); + if(ec) + { + close(); + return; + } +} + +template +void +basic_file_body:: +value_type:: +reset(File&& file, error_code& ec) +{ + // First close the file if open + if(file_.is_open()) + { + error_code ignored; + file_.close(ignored); + } + + // Take ownership of the new file + file_ = std::move(file); + + // Cache the size + file_size_ = file_.size(ec); +} + +// This is called from message::payload_size +template +std::uint64_t +basic_file_body:: +size(value_type const& v) +{ + // Forward the call to the body + return v.size(); +} + +//] + +//[example_http_file_body_3 + +template +class basic_file_body::reader +{ + value_type& body_; // The body we are reading from + std::uint64_t remain_; // The number of unread bytes + char buf_[4096]; // Small buffer for reading + +public: + // The type of buffer sequence returned by `get`. + // + using const_buffers_type = + boost::asio::const_buffers_1; + + // Constructor. + // + // `m` holds the message we are sending, which will + // always have the `file_body` as the body type. + // + // Note that the message is passed by non-const reference. + // This is intentional, because reading from the file + // changes its "current position" which counts makes the + // operation logically not-const (although it is bitwise + // const). + // + // The BodyReader concept allows the reader to choose + // whether to take the message by const reference or + // non-const reference. Depending on the choice, a + // serializer constructed using that body type will + // require the same const or non-const reference to + // construct. + // + // Readers which accept const messages usually allow + // the same body to be serialized by multiple threads + // concurrently, while readers accepting non-const + // messages may only be serialized by one thread at + // a time. + // + template + reader(message< + isRequest, basic_file_body, Fields>& m); + + // Initializer + // + // This is called before the body is serialized and + // gives the reader a chance to do something that might + // need to return an error code. + // + void + init(error_code& ec); + + // This function is called zero or more times to + // retrieve buffers. A return value of `boost::none` + // means there are no more buffers. Otherwise, + // the contained pair will have the next buffer + // to serialize, and a `bool` indicating whether + // or not there may be additional buffers. + boost::optional> + get(error_code& ec); +}; + +//] + +//[example_http_file_body_4 + +// Here we just stash a reference to the path for later. +// Rather than dealing with messy constructor exceptions, +// we save the things that might fail for the call to `init`. +// +template +template +basic_file_body:: +reader:: +reader(message& m) + : body_(m.body) +{ + // The file must already be open + BOOST_ASSERT(body_.file_.is_open()); + + // Get the size of the file + remain_ = body_.file_size_; +} + +// Initializer +template +void +basic_file_body:: +reader:: +init(error_code& ec) +{ + // The error_code specification requires that we + // either set the error to some value, or set it + // to indicate no error. + // + // We don't do anything fancy so set "no error" + ec.assign(0, ec.category()); +} + +// This function is called repeatedly by the serializer to +// retrieve the buffers representing the body. Our strategy +// is to read into our buffer and return it until we have +// read through the whole file. +// +template +auto +basic_file_body:: +reader:: +get(error_code& ec) -> + boost::optional> +{ + // Calculate the smaller of our buffer size, + // or the amount of unread data in the file. + auto const amount = remain_ > sizeof(buf_) ? + sizeof(buf_) : static_cast(remain_); + + // Handle the case where the file is zero length + if(amount == 0) + { + // Modify the error code to indicate success + // This is required by the error_code specification. + // + // NOTE We use the existing category instead of calling + // into the library to get the generic category because + // that saves us a possibly expensive atomic operation. + // + ec.assign(0, ec.category()); + return boost::none; + } + + // Now read the next buffer + auto const nread = body_.file_.read(buf_, amount, ec); + if(ec) + return boost::none; + + // Make sure there is forward progress + BOOST_ASSERT(nread != 0); + BOOST_ASSERT(nread <= remain_); + + // Update the amount remaining based on what we got + remain_ -= nread; + + // Return the buffer to the caller. + // + // The second element of the pair indicates whether or + // not there is more data. As long as there is some + // unread bytes, there will be more data. Otherwise, + // we set this bool to `false` so we will not be called + // again. + // + ec.assign(0, ec.category()); + return {{ + const_buffers_type{buf_, nread}, // buffer to return. + remain_ > 0 // `true` if there are more buffers. + }}; +} + +//] + +//[example_http_file_body_5 + +template +class basic_file_body::writer +{ + value_type& body_; // The body we are writing to + +public: + // Constructor. + // + // This is called after the header is parsed and + // indicates that a non-zero sized body may be present. + // `m` holds the message we are receiving, which will + // always have the `file_body` as the body type. + // + template + explicit + writer( + message& m); + + // Initializer + // + // This is called before the body is parsed and + // gives the writer a chance to do something that might + // need to return an error code. It informs us of + // the payload size (`content_length`) which we can + // optionally use for optimization. + // + void + init(boost::optional const&, error_code& ec); + + // This function is called one or more times to store + // buffer sequences corresponding to the incoming body. + // + template + std::size_t + put(ConstBufferSequence const& buffers, + error_code& ec); + + // This function is called when writing is complete. + // It is an opportunity to perform any final actions + // which might fail, in order to return an error code. + // Operations that might fail should not be attemped in + // destructors, since an exception thrown from there + // would terminate the program. + // + void + finish(error_code& ec); +}; + +//] + +//[example_http_file_body_6 + +// We don't do much in the writer constructor since the +// file is already open. +// +template +template +basic_file_body:: +writer:: +writer(message& m) + : body_(m.body) +{ +} + +template +void +basic_file_body:: +writer:: +init( + boost::optional const& content_length, + error_code& ec) +{ + // The file must already be open for writing + BOOST_ASSERT(body_.file_.is_open()); + + // We don't do anything with this but a sophisticated + // application might check available space on the device + // to see if there is enough room to store the body. + boost::ignore_unused(content_length); + + // The error_code specification requires that we + // either set the error to some value, or set it + // to indicate no error. + // + // We don't do anything fancy so set "no error" + ec.assign(0, ec.category()); +} + +// This will get called one or more times with body buffers +// +template +template +std::size_t +basic_file_body:: +writer:: +put(ConstBufferSequence const& buffers, error_code& ec) +{ + // This function must return the total number of + // bytes transferred from the input buffers. + std::size_t nwritten = 0; + + // Loop over all the buffers in the sequence, + // and write each one to the file. + for(boost::asio::const_buffer buffer : buffers) + { + // Write this buffer to the file + nwritten += body_.file_.write( + boost::asio::buffer_cast(buffer), + boost::asio::buffer_size(buffer), + ec); + if(ec) + return nwritten; + } + + // Indicate success + // This is required by the error_code specification + ec.assign(0, ec.category()); + + return nwritten; +} + +// Called after writing is done when there's no error. +template +void +basic_file_body:: +writer:: +finish(error_code& ec) +{ + // This has to be cleared before returning, to + // indicate no error. The specification requires it. + ec.assign(0, ec.category()); +} + +//] + +/// A message body represented by a file on the filesystem. +using file_body = basic_file_body; + +} // http +} // beast + +#include + +#endif diff --git a/src/beast/include/beast/http/header_parser_v1.hpp b/src/beast/include/beast/http/header_parser_v1.hpp deleted file mode 100644 index c6f732d21a..0000000000 --- a/src/beast/include/beast/http/header_parser_v1.hpp +++ /dev/null @@ -1,233 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_HTTP_HEADERS_PARSER_V1_HPP -#define BEAST_HTTP_HEADERS_PARSER_V1_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace http { - -namespace detail { - -struct request_parser_base -{ - std::string method_; - std::string uri_; -}; - -struct response_parser_base -{ - std::string reason_; -}; - -} // detail - -/** A parser for a HTTP/1 request or response header. - - This class uses the HTTP/1 wire format parser to - convert a series of octets into a request or - response @ref header. - - @note A new instance of the parser is required for each message. -*/ -template -class header_parser_v1 - : public basic_parser_v1> - , private std::conditional::type -{ -public: - /// The type of the header this parser produces. - using header_type = header; - -private: - // VFALCO Check Fields requirements? - - std::string field_; - std::string value_; - header_type h_; - bool flush_ = false; - -public: - /// Default constructor - header_parser_v1() = default; - - /// Move constructor - header_parser_v1(header_parser_v1&&) = default; - - /// Copy constructor (disallowed) - header_parser_v1(header_parser_v1 const&) = delete; - - /// Move assignment (disallowed) - header_parser_v1& operator=(header_parser_v1&&) = delete; - - /// Copy assignment (disallowed) - header_parser_v1& operator=(header_parser_v1 const&) = delete; - - /** Construct the parser. - - @param args Forwarded to the header constructor. - */ -#if GENERATING_DOCS - template - explicit - header_parser_v1(Args&&... args); -#else - template::type, header_parser_v1>::value>> - explicit - header_parser_v1(Arg1&& arg1, ArgN&&... argn) - : h_(std::forward(arg1), - std::forward(argn)...) - { - } -#endif - - /** Returns the parsed header - - Only valid if @ref complete would return `true`. - */ - header_type const& - get() const - { - return h_; - } - - /** Returns the parsed header. - - Only valid if @ref complete would return `true`. - */ - header_type& - get() - { - return h_; - } - - /** Returns ownership of the parsed header. - - Ownership is transferred to the caller. Only - valid if @ref complete would return `true`. - - Requires: - @ref header_type is @b MoveConstructible - */ - header_type - release() - { - static_assert(std::is_move_constructible::value, - "MoveConstructible requirements not met"); - return std::move(h_); - } - -private: - friend class basic_parser_v1; - - void flush() - { - if(! flush_) - return; - flush_ = false; - BOOST_ASSERT(! field_.empty()); - h_.fields.insert(field_, value_); - field_.clear(); - value_.clear(); - } - - void on_start(error_code&) - { - } - - void on_method(boost::string_ref const& s, error_code&) - { - this->method_.append(s.data(), s.size()); - } - - void on_uri(boost::string_ref const& s, error_code&) - { - this->uri_.append(s.data(), s.size()); - } - - void on_reason(boost::string_ref const& s, error_code&) - { - this->reason_.append(s.data(), s.size()); - } - - void on_request_or_response(std::true_type) - { - h_.method = std::move(this->method_); - h_.url = std::move(this->uri_); - } - - void on_request_or_response(std::false_type) - { - h_.status = this->status_code(); - h_.reason = std::move(this->reason_); - } - - void on_request(error_code& ec) - { - on_request_or_response( - std::integral_constant{}); - } - - void on_response(error_code& ec) - { - on_request_or_response( - std::integral_constant{}); - } - - void on_field(boost::string_ref const& s, error_code&) - { - flush(); - field_.append(s.data(), s.size()); - } - - void on_value(boost::string_ref const& s, error_code&) - { - value_.append(s.data(), s.size()); - flush_ = true; - } - - void - on_header(std::uint64_t, error_code&) - { - flush(); - h_.version = 10 * this->http_major() + this->http_minor(); - } - - body_what - on_body_what(std::uint64_t, error_code&) - { - return body_what::pause; - } - - void on_body(boost::string_ref const&, error_code&) - { - } - - void on_complete(error_code&) - { - } -}; - -} // http -} // beast - -#endif diff --git a/src/beast/include/beast/http/impl/basic_fields.ipp b/src/beast/include/beast/http/impl/basic_fields.ipp deleted file mode 100644 index 069087207c..0000000000 --- a/src/beast/include/beast/http/impl/basic_fields.ipp +++ /dev/null @@ -1,266 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_HTTP_IMPL_BASIC_FIELDS_IPP -#define BEAST_HTTP_IMPL_BASIC_FIELDS_IPP - -#include -#include - -namespace beast { -namespace http { - -template -void -basic_fields:: -delete_all() -{ - for(auto it = list_.begin(); it != list_.end();) - { - auto& e = *it++; - alloc_traits::destroy(this->member(), &e); - alloc_traits::deallocate( - this->member(), &e, 1); - } -} - -template -inline -void -basic_fields:: -move_assign(basic_fields& other, std::false_type) -{ - if(this->member() != other.member()) - { - copy_from(other); - other.clear(); - } - else - { - set_ = std::move(other.set_); - list_ = std::move(other.list_); - } -} - -template -inline -void -basic_fields:: -move_assign(basic_fields& other, std::true_type) -{ - this->member() = std::move(other.member()); - set_ = std::move(other.set_); - list_ = std::move(other.list_); -} - -template -inline -void -basic_fields:: -copy_assign(basic_fields const& other, std::false_type) -{ - copy_from(other); -} - -template -inline -void -basic_fields:: -copy_assign(basic_fields const& other, std::true_type) -{ - this->member() = other.member(); - copy_from(other); -} - -//------------------------------------------------------------------------------ - -template -basic_fields:: -~basic_fields() -{ - delete_all(); -} - -template -basic_fields:: -basic_fields(Allocator const& alloc) - : beast::detail::empty_base_optimization< - alloc_type>(alloc) -{ -} - -template -basic_fields:: -basic_fields(basic_fields&& other) - : beast::detail::empty_base_optimization( - std::move(other.member())) - , detail::basic_fields_base( - std::move(other.set_), std::move(other.list_)) -{ -} - -template -auto -basic_fields:: -operator=(basic_fields&& other) -> - basic_fields& -{ - if(this == &other) - return *this; - clear(); - move_assign(other, std::integral_constant{}); - return *this; -} - -template -basic_fields:: -basic_fields(basic_fields const& other) - : basic_fields(alloc_traits:: - select_on_container_copy_construction(other.member())) -{ - copy_from(other); -} - -template -auto -basic_fields:: -operator=(basic_fields const& other) -> - basic_fields& -{ - clear(); - copy_assign(other, std::integral_constant{}); - return *this; -} - -template -template -basic_fields:: -basic_fields(basic_fields const& other) -{ - copy_from(other); -} - -template -template -auto -basic_fields:: -operator=(basic_fields const& other) -> - basic_fields& -{ - clear(); - copy_from(other); - return *this; -} - -template -template -basic_fields:: -basic_fields(FwdIt first, FwdIt last) -{ - for(;first != last; ++first) - insert(first->name(), first->value()); -} - -template -std::size_t -basic_fields:: -count(boost::string_ref const& name) const -{ - auto const it = set_.find(name, less{}); - if(it == set_.end()) - return 0; - auto const last = set_.upper_bound(name, less{}); - return static_cast(std::distance(it, last)); -} - -template -auto -basic_fields:: -find(boost::string_ref const& name) const -> - iterator -{ - auto const it = set_.find(name, less{}); - if(it == set_.end()) - return list_.end(); - return list_.iterator_to(*it); -} - -template -boost::string_ref -basic_fields:: -operator[](boost::string_ref const& name) const -{ - auto const it = find(name); - if(it == end()) - return {}; - return it->second; -} - -template -void -basic_fields:: -clear() noexcept -{ - delete_all(); - list_.clear(); - set_.clear(); -} - -template -std::size_t -basic_fields:: -erase(boost::string_ref const& name) -{ - auto it = set_.find(name, less{}); - if(it == set_.end()) - return 0; - auto const last = set_.upper_bound(name, less{}); - std::size_t n = 1; - for(;;) - { - auto& e = *it++; - set_.erase(set_.iterator_to(e)); - list_.erase(list_.iterator_to(e)); - alloc_traits::destroy(this->member(), &e); - alloc_traits::deallocate(this->member(), &e, 1); - if(it == last) - break; - ++n; - } - return n; -} - -template -void -basic_fields:: -insert(boost::string_ref const& name, - boost::string_ref value) -{ - value = detail::trim(value); - auto const p = alloc_traits::allocate(this->member(), 1); - alloc_traits::construct(this->member(), p, name, value); - set_.insert_before(set_.upper_bound(name, less{}), *p); - list_.push_back(*p); -} - -template -void -basic_fields:: -replace(boost::string_ref const& name, - boost::string_ref value) -{ - value = detail::trim(value); - erase(name); - insert(name, value); -} - -} // http -} // beast - -#endif diff --git a/src/beast/include/beast/http/impl/basic_parser.ipp b/src/beast/include/beast/http/impl/basic_parser.ipp new file mode 100644 index 0000000000..9ea02bae0a --- /dev/null +++ b/src/beast/include/beast/http/impl/basic_parser.ipp @@ -0,0 +1,932 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_IMPL_BASIC_PARSER_IPP +#define BEAST_HTTP_IMPL_BASIC_PARSER_IPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +template +basic_parser:: +basic_parser() + : body_limit_( + default_body_limit(is_request{})) +{ +} + +template +template +basic_parser:: +basic_parser(basic_parser< + isRequest, OtherDerived>&& other) + : body_limit_(other.body_limit_) + , len_(other.len_) + , buf_(std::move(other.buf_)) + , buf_len_(other.buf_len_) + , skip_(other.skip_) + , state_(other.state_) + , f_(other.f_) +{ +} + +template +bool +basic_parser:: +is_keep_alive() const +{ + BOOST_ASSERT(is_header_done()); + if(f_ & flagHTTP11) + { + if(f_ & flagConnectionClose) + return false; + } + else + { + if(! (f_ & flagConnectionKeepAlive)) + return false; + } + return (f_ & flagNeedEOF) == 0; +} + +template +boost::optional +basic_parser:: +content_length() const +{ + BOOST_ASSERT(is_header_done()); + if(! (f_ & flagContentLength)) + return boost::none; + return len_; +} + +template +void +basic_parser:: +skip(bool v) +{ + BOOST_ASSERT(! got_some()); + if(v) + f_ |= flagSkipBody; + else + f_ &= ~flagSkipBody; +} + +template +template +std::size_t +basic_parser:: +put(ConstBufferSequence const& buffers, + error_code& ec) +{ + static_assert(is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + using boost::asio::buffer_cast; + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + auto const p = buffers.begin(); + auto const last = buffers.end(); + if(p == last) + { + ec.assign(0, ec.category()); + return 0; + } + if(std::next(p) == last) + { + // single buffer + auto const b = *p; + return put(boost::asio::const_buffers_1{ + buffer_cast(b), + buffer_size(b)}, ec); + } + auto const size = buffer_size(buffers); + if(size <= max_stack_buffer) + return put_from_stack(size, buffers, ec); + if(size > buf_len_) + { + // reallocate + buf_ = boost::make_unique_noinit(size); + buf_len_ = size; + } + // flatten + buffer_copy(boost::asio::buffer( + buf_.get(), buf_len_), buffers); + return put(boost::asio::const_buffers_1{ + buf_.get(), buf_len_}, ec); +} + +template +std::size_t +basic_parser:: +put(boost::asio::const_buffers_1 const& buffer, + error_code& ec) +{ + BOOST_ASSERT(state_ != state::complete); + using boost::asio::buffer_size; + auto p = boost::asio::buffer_cast< + char const*>(*buffer.begin()); + auto n = buffer_size(*buffer.begin()); + auto const p0 = p; + auto const p1 = p0 + n; + ec.assign(0, ec.category()); +loop: + switch(state_) + { + case state::nothing_yet: + if(n == 0) + { + ec = error::need_more; + return 0; + } + state_ = state::start_line; + BEAST_FALLTHROUGH; + + case state::start_line: + { + maybe_need_more(p, n, ec); + if(ec) + goto done; + parse_start_line(p, p + std::min( + header_limit_, n), ec, is_request{}); + if(ec) + { + if(ec == error::need_more) + { + if(n >= header_limit_) + { + ec = error::header_limit; + goto done; + } + if(p + 3 <= p1) + skip_ = static_cast< + std::size_t>(p1 - p - 3); + } + goto done; + } + BOOST_ASSERT(! is_done()); + n = static_cast(p1 - p); + if(p >= p1) + { + ec = error::need_more; + goto done; + } + BEAST_FALLTHROUGH; + } + + case state::fields: + maybe_need_more(p, n, ec); + if(ec) + goto done; + parse_fields(p, p + std::min( + header_limit_, n), ec); + if(ec) + { + if(ec == error::need_more) + { + if(n >= header_limit_) + { + ec = error::header_limit; + goto done; + } + if(p + 3 <= p1) + skip_ = static_cast< + std::size_t>(p1 - p - 3); + } + goto done; + } + finish_header(ec, is_request{}); + break; + + case state::body0: + BOOST_ASSERT(! skip_); + impl().on_body(content_length(), ec); + if(ec) + goto done; + state_ = state::body; + BEAST_FALLTHROUGH; + + case state::body: + BOOST_ASSERT(! skip_); + parse_body(p, n, ec); + if(ec) + goto done; + break; + + case state::body_to_eof0: + BOOST_ASSERT(! skip_); + impl().on_body(content_length(), ec); + if(ec) + goto done; + state_ = state::body_to_eof; + BEAST_FALLTHROUGH; + + case state::body_to_eof: + BOOST_ASSERT(! skip_); + parse_body_to_eof(p, n, ec); + if(ec) + goto done; + break; + + case state::chunk_header0: + impl().on_body(content_length(), ec); + if(ec) + goto done; + state_ = state::chunk_header; + BEAST_FALLTHROUGH; + + case state::chunk_header: + parse_chunk_header(p, n, ec); + if(ec) + goto done; + break; + + case state::chunk_body: + parse_chunk_body(p, n, ec); + if(ec) + goto done; + break; + + case state::complete: + ec.assign(0, ec.category()); + goto done; + } + if(p < p1 && ! is_done() && eager()) + { + n = static_cast(p1 - p); + goto loop; + } +done: + return static_cast(p - p0); +} + +template +void +basic_parser:: +put_eof(error_code& ec) +{ + BOOST_ASSERT(got_some()); + if( state_ == state::start_line || + state_ == state::fields) + { + ec = error::partial_message; + return; + } + if(f_ & (flagContentLength | flagChunked)) + { + if(state_ != state::complete) + { + ec = error::partial_message; + return; + } + ec.assign(0, ec.category()); + return; + } + impl().on_complete(ec); + if(ec) + return; + state_ = state::complete; +} + +template +template +std::size_t +basic_parser:: +put_from_stack(std::size_t size, + ConstBufferSequence const& buffers, + error_code& ec) +{ + char buf[max_stack_buffer]; + using boost::asio::buffer; + using boost::asio::buffer_copy; + buffer_copy(buffer(buf, sizeof(buf)), buffers); + return put(boost::asio::const_buffers_1{ + buf, size}, ec); +} + +template +inline +void +basic_parser:: +maybe_need_more( + char const* p, std::size_t n, + error_code& ec) +{ + if(skip_ == 0) + return; + if( n > header_limit_) + n = header_limit_; + if(n < skip_ + 4) + { + ec = error::need_more; + return; + } + auto const term = + find_eom(p + skip_, p + n); + if(! term) + { + skip_ = n - 3; + if(skip_ + 4 > header_limit_) + { + ec = error::header_limit; + return; + } + ec = error::need_more; + return; + } + skip_ = 0; +} + +template +inline +void +basic_parser:: +parse_start_line( + char const*& in, char const* last, + error_code& ec, std::true_type) +{ +/* + request-line = method SP request-target SP HTTP-version CRLF + method = token +*/ + auto p = in; + + string_view method; + parse_method(p, last, method, ec); + if(ec) + return; + + string_view target; + parse_target(p, last, target, ec); + if(ec) + return; + + int version = 0; + parse_version(p, last, version, ec); + if(ec) + return; + if(version < 10 || version > 11) + { + ec = error::bad_version; + return; + } + + if(p + 2 > last) + { + ec = error::need_more; + return; + } + if(p[0] != '\r' || p[1] != '\n') + { + ec = error::bad_version; + return; + } + p += 2; + + if(version >= 11) + f_ |= flagHTTP11; + + impl().on_request(string_to_verb(method), + method, target, version, ec); + if(ec) + return; + + in = p; + state_ = state::fields; +} + +template +inline +void +basic_parser:: +parse_start_line( + char const*& in, char const* last, + error_code& ec, std::false_type) +{ +/* + status-line = HTTP-version SP status-code SP reason-phrase CRLF + status-code = 3*DIGIT + reason-phrase = *( HTAB / SP / VCHAR / obs-text ) +*/ + auto p = in; + + int version = 0; + parse_version(p, last, version, ec); + if(ec) + return; + if(version < 10 || version > 11) + { + ec = error::bad_version; + return; + } + + // SP + if(p + 1 > last) + { + ec = error::need_more; + return; + } + if(*p++ != ' ') + { + ec = error::bad_version; + return; + } + + parse_status(p, last, status_, ec); + if(ec) + return; + + // parse reason CRLF + string_view reason; + parse_reason(p, last, reason, ec); + if(ec) + return; + + if(version >= 11) + f_ |= flagHTTP11; + + impl().on_response( + status_, reason, version, ec); + if(ec) + return; + + in = p; + state_ = state::fields; +} + +template +void +basic_parser:: +parse_fields(char const*& in, + char const* last, error_code& ec) +{ + string_view name; + string_view value; + // https://stackoverflow.com/questions/686217/maximum-on-http-header-values + static_string buf; + auto p = in; + for(;;) + { + if(p + 2 > last) + { + ec = error::need_more; + return; + } + if(p[0] == '\r') + { + if(p[1] != '\n') + ec = error::bad_line_ending; + in = p + 2; + return; + } + parse_field(p, last, name, value, buf, ec); + if(ec) + return; + auto const f = string_to_field(name); + do_field(f, value, ec); + if(ec) + return; + impl().on_field(f, name, value, ec); + if(ec) + return; + in = p; + } +} + +template +inline +void +basic_parser:: +finish_header(error_code& ec, std::true_type) +{ + // RFC 7230 section 3.3 + // https://tools.ietf.org/html/rfc7230#section-3.3 + + if(f_ & flagSkipBody) + { + state_ = state::complete; + } + else if(f_ & flagContentLength) + { + if(len_ > 0) + { + f_ |= flagHasBody; + state_ = state::body0; + } + else + { + state_ = state::complete; + } + } + else if(f_ & flagChunked) + { + f_ |= flagHasBody; + state_ = state::chunk_header0; + } + else + { + len_ = 0; + state_ = state::complete; + } + + impl().on_header(ec); + if(ec) + return; + if(state_ == state::complete) + { + impl().on_complete(ec); + if(ec) + return; + } +} + +template +inline +void +basic_parser:: +finish_header(error_code& ec, std::false_type) +{ + // RFC 7230 section 3.3 + // https://tools.ietf.org/html/rfc7230#section-3.3 + + if( (f_ & flagSkipBody) || // e.g. response to a HEAD request + status_ / 100 == 1 || // 1xx e.g. Continue + status_ == 204 || // No Content + status_ == 304) // Not Modified + { + state_ = state::complete; + return; + } + + if(f_ & flagContentLength) + { + if(len_ > 0) + { + f_ |= flagHasBody; + state_ = state::body0; + } + else + { + state_ = state::complete; + } + } + else if(f_ & flagChunked) + { + f_ |= flagHasBody; + state_ = state::chunk_header0; + } + else + { + f_ |= flagHasBody; + f_ |= flagNeedEOF; + state_ = state::body_to_eof0; + } + + impl().on_header(ec); + if(ec) + return; + if(state_ == state::complete) + { + impl().on_complete(ec); + if(ec) + return; + } +} + +template +inline +void +basic_parser:: +parse_body(char const*& p, + std::size_t n, error_code& ec) +{ + n = impl().on_data(string_view{p, + beast::detail::clamp(len_, n)}, ec); + p += n; + len_ -= n; + if(ec) + return; + if(len_ > 0) + return; + impl().on_complete(ec); + if(ec) + return; + state_ = state::complete; +} + +template +inline +void +basic_parser:: +parse_body_to_eof(char const*& p, + std::size_t n, error_code& ec) +{ + if(n > body_limit_) + { + ec = error::body_limit; + return; + } + body_limit_ = body_limit_ - n; + n = impl().on_data(string_view{p, n}, ec); + p += n; + if(ec) + return; +} + +template +void +basic_parser:: +parse_chunk_header(char const*& p0, + std::size_t n, error_code& ec) +{ +/* + chunked-body = *chunk last-chunk trailer-part CRLF + + chunk = chunk-size [ chunk-ext ] CRLF chunk-data CRLF + last-chunk = 1*("0") [ chunk-ext ] CRLF + trailer-part = *( header-field CRLF ) + + chunk-size = 1*HEXDIG + chunk-data = 1*OCTET ; a sequence of chunk-size octets + chunk-ext = *( ";" chunk-ext-name [ "=" chunk-ext-val ] ) + chunk-ext-name = token + chunk-ext-val = token / quoted-string +*/ + + auto p = p0; + auto const pend = p + n; + char const* eol; + + if(! (f_ & flagFinalChunk)) + { + if(n < skip_ + 2) + { + ec = error::need_more; + return; + } + if(f_ & flagExpectCRLF) + { + // Treat the last CRLF in a chunk as + // part of the next chunk, so p can + // be parsed in one call instead of two. + if(! parse_crlf(p)) + { + ec = error::bad_chunk; + return; + } + } + eol = find_eol(p0 + skip_, pend, ec); + if(ec) + return; + if(! eol) + { + ec = error::need_more; + skip_ = n - 1; + return; + } + skip_ = static_cast< + std::size_t>(eol - 2 - p0); + + std::uint64_t v; + if(! parse_hex(p, v)) + { + ec = error::bad_chunk; + return; + } + if(v != 0) + { + if(v > body_limit_) + { + ec = error::body_limit; + return; + } + body_limit_ -= v; + if(*p == ';') + { + // VFALCO TODO Validate extension + impl().on_chunk(v, make_string( + p, eol - 2), ec); + if(ec) + return; + } + else if(p == eol - 2) + { + impl().on_chunk(v, {}, ec); + if(ec) + return; + } + else + { + ec = error::bad_chunk; + return; + } + len_ = v; + skip_ = 2; + p0 = eol; + f_ |= flagExpectCRLF; + state_ = state::chunk_body; + return; + } + + f_ |= flagFinalChunk; + } + else + { + BOOST_ASSERT(n >= 5); + if(f_ & flagExpectCRLF) + BOOST_VERIFY(parse_crlf(p)); + std::uint64_t v; + BOOST_VERIFY(parse_hex(p, v)); + eol = find_eol(p, pend, ec); + BOOST_ASSERT(! ec); + } + + auto eom = find_eom(p0 + skip_, pend); + if(! eom) + { + BOOST_ASSERT(n >= 3); + skip_ = n - 3; + ec = error::need_more; + return; + } + + if(*p == ';') + { + // VFALCO TODO Validate extension + impl().on_chunk(0, make_string( + p, eol - 2), ec); + if(ec) + return; + } + p = eol; + parse_fields(p, eom, ec); + if(ec) + return; + BOOST_ASSERT(p == eom); + p0 = eom; + + impl().on_complete(ec); + if(ec) + return; + state_ = state::complete; +} + +template +inline +void +basic_parser:: +parse_chunk_body(char const*& p, + std::size_t n, error_code& ec) +{ + n = impl().on_data(string_view{p, + beast::detail::clamp(len_, n)}, ec); + p += n; + len_ -= n; + if(ec) + return; + if(len_ > 0) + return; + state_ = state::chunk_header; +} + +template +void +basic_parser:: +do_field(field f, + string_view value, error_code& ec) +{ + // Connection + if(f == field::connection || + f == field::proxy_connection) + { + auto const list = opt_token_list{value}; + if(! validate_list(list)) + { + // VFALCO Should this be a field specific error? + ec = error::bad_value; + return; + } + for(auto const& s : list) + { + if(iequals({"close", 5}, s)) + { + f_ |= flagConnectionClose; + continue; + } + + if(iequals({"keep-alive", 10}, s)) + { + f_ |= flagConnectionKeepAlive; + continue; + } + + if(iequals({"upgrade", 7}, s)) + { + f_ |= flagConnectionUpgrade; + continue; + } + } + ec.assign(0, ec.category()); + return; + } + + // Content-Length + if(f == field::content_length) + { + if(f_ & flagContentLength) + { + // duplicate + ec = error::bad_content_length; + return; + } + + if(f_ & flagChunked) + { + // conflicting field + ec = error::bad_content_length; + return; + } + + std::uint64_t v; + if(! parse_dec( + value.begin(), value.end(), v)) + { + ec = error::bad_content_length; + return; + } + + if(v > body_limit_) + { + ec = error::body_limit; + return; + } + + ec.assign(0, ec.category()); + len_ = v; + f_ |= flagContentLength; + return; + } + + // Transfer-Encoding + if(f == field::transfer_encoding) + { + if(f_ & flagChunked) + { + // duplicate + ec = error::bad_transfer_encoding; + return; + } + + if(f_ & flagContentLength) + { + // conflicting field + ec = error::bad_transfer_encoding; + return; + } + + ec.assign(0, ec.category()); + auto const v = token_list{value}; + auto const p = std::find_if(v.begin(), v.end(), + [&](typename token_list::value_type const& s) + { + return iequals({"chunked", 7}, s); + }); + if(p == v.end()) + return; + if(std::next(p) != v.end()) + return; + len_ = 0; + f_ |= flagChunked; + return; + } + + // Upgrade + if(f == field::upgrade) + { + ec.assign(0, ec.category()); + f_ |= flagUpgrade; + return; + } + + ec.assign(0, ec.category()); +} + +} // http +} // beast + +#endif diff --git a/src/beast/include/beast/http/impl/basic_parser_v1.ipp b/src/beast/include/beast/http/impl/basic_parser_v1.ipp deleted file mode 100644 index 9b1be47dfc..0000000000 --- a/src/beast/include/beast/http/impl/basic_parser_v1.ipp +++ /dev/null @@ -1,1289 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_HTTP_IMPL_BASIC_PARSER_V1_IPP -#define BEAST_HTTP_IMPL_BASIC_PARSER_V1_IPP - -#include -#include -#include - -namespace beast { -namespace http { - -/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev - * - * Additional changes are licensed under the same terms as NGINX and - * copyright Joyent, Inc. and other Node contributors. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ -/* This code is a modified version of nodejs/http-parser, copyright above: - https://github.com/nodejs/http-parser -*/ - -template -basic_parser_v1:: -basic_parser_v1() - : flags_(0) -{ - init(); -} - -template -template -basic_parser_v1:: -basic_parser_v1(basic_parser_v1< - isRequest, OtherDerived> const& other) - : h_max_(other.h_max_) - , h_left_(other.h_left_) - , b_max_(other.b_max_) - , b_left_(other.b_left_) - , content_length_(other.content_length_) - , cb_(nullptr) - , s_(other.s_) - , fs_(other.fs_) - , pos_(other.pos_) - , http_major_(other.http_major_) - , http_minor_(other.http_minor_) - , status_code_(other.status_code_) - , flags_(other.flags_) - , upgrade_(other.upgrade_) -{ - BOOST_ASSERT(! other.cb_); -} - -template -template -auto -basic_parser_v1:: -operator=(basic_parser_v1< - isRequest, OtherDerived> const& other) -> - basic_parser_v1& -{ - BOOST_ASSERT(! other.cb_); - h_max_ = other.h_max_; - h_left_ = other.h_left_; - b_max_ = other.b_max_; - b_left_ = other.b_left_; - content_length_ = other.content_length_; - cb_ = nullptr; - s_ = other.s_; - fs_ = other.fs_; - pos_ = other.pos_; - http_major_ = other.http_major_; - http_minor_ = other.http_minor_; - status_code_ = other.status_code_; - flags_ = other.flags_; - upgrade_ = other.upgrade_; - flags_ &= ~parse_flag::paused; - return *this; -} - -template -bool -basic_parser_v1:: -keep_alive() const -{ - if(http_major_ >= 1 && http_minor_ >= 1) - { - if(flags_ & parse_flag::connection_close) - return false; - } - else - { - if(! (flags_ & parse_flag::connection_keep_alive)) - return false; - } - return ! needs_eof(); -} - -template -template -typename std::enable_if< - ! std::is_convertible::value, - std::size_t>::type -basic_parser_v1:: -write(ConstBufferSequence const& buffers, error_code& ec) -{ - static_assert(is_ConstBufferSequence::value, - "ConstBufferSequence requirements not met"); - std::size_t used = 0; - for(auto const& buffer : buffers) - { - used += write(buffer, ec); - if(ec) - break; - } - return used; -} - -template -std::size_t -basic_parser_v1:: -write(boost::asio::const_buffer const& buffer, error_code& ec) -{ - using beast::http::detail::is_digit; - using beast::http::detail::is_tchar; - using beast::http::detail::is_text; - using beast::http::detail::to_field_char; - using beast::http::detail::to_value_char; - using beast::http::detail::unhex; - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - - auto const data = buffer_cast(buffer); - auto const size = buffer_size(buffer); - - if(size == 0 && s_ != s_dead) - return 0; - - auto begin = - reinterpret_cast(data); - auto const end = begin + size; - auto p = begin; - auto used = [&] - { - return p - reinterpret_cast(data); - }; - auto err = [&](parse_error ev) - { - ec = ev; - s_ = s_dead; - return used(); - }; - auto errc = [&] - { - s_ = s_dead; - return used(); - }; - auto piece = [&] - { - return boost::string_ref{ - begin, static_cast(p - begin)}; - }; - auto cb = [&](pmf_t next) - { - if(cb_ && p != begin) - { - (this->*cb_)(ec, piece()); - if(ec) - return true; // error - } - cb_ = next; - if(cb_) - begin = p; - return false; - }; - for(;p != end; ++p) - { - unsigned char ch = *p; - redo: - switch(s_) - { - case s_dead: - case s_closed_complete: - return err(parse_error::connection_closed); - break; - - case s_req_start: - flags_ = 0; - cb_ = nullptr; - content_length_ = no_content_length; - s_ = s_req_method0; - goto redo; - - case s_req_method0: - if(! is_tchar(ch)) - return err(parse_error::bad_method); - call_on_start(ec); - if(ec) - return errc(); - BOOST_ASSERT(! cb_); - cb(&self::call_on_method); - s_ = s_req_method; - break; - - case s_req_method: - if(ch == ' ') - { - if(cb(nullptr)) - return errc(); - s_ = s_req_url0; - break; - } - if(! is_tchar(ch)) - return err(parse_error::bad_method); - break; - - case s_req_url0: - { - if(ch == ' ') - return err(parse_error::bad_uri); - // VFALCO TODO Better checking for valid URL characters - if(! is_text(ch)) - return err(parse_error::bad_uri); - BOOST_ASSERT(! cb_); - cb(&self::call_on_uri); - s_ = s_req_url; - break; - } - - case s_req_url: - if(ch == ' ') - { - if(cb(nullptr)) - return errc(); - s_ = s_req_http; - break; - } - // VFALCO TODO Better checking for valid URL characters - if(! is_text(ch)) - return err(parse_error::bad_uri); - break; - - case s_req_http: - if(ch != 'H') - return err(parse_error::bad_version); - s_ = s_req_http_H; - break; - - case s_req_http_H: - if(ch != 'T') - return err(parse_error::bad_version); - s_ = s_req_http_HT; - break; - - case s_req_http_HT: - if(ch != 'T') - return err(parse_error::bad_version); - s_ = s_req_http_HTT; - break; - - case s_req_http_HTT: - if(ch != 'P') - return err(parse_error::bad_version); - s_ = s_req_http_HTTP; - break; - - case s_req_http_HTTP: - if(ch != '/') - return err(parse_error::bad_version); - s_ = s_req_major; - break; - - case s_req_major: - if(! is_digit(ch)) - return err(parse_error::bad_version); - http_major_ = ch - '0'; - s_ = s_req_dot; - break; - - case s_req_dot: - if(ch != '.') - return err(parse_error::bad_version); - s_ = s_req_minor; - break; - - case s_req_minor: - if(! is_digit(ch)) - return err(parse_error::bad_version); - http_minor_ = ch - '0'; - s_ = s_req_cr; - break; - - case s_req_cr: - if(ch != '\r') - return err(parse_error::bad_version); - s_ = s_req_lf; - break; - - case s_req_lf: - if(ch != '\n') - return err(parse_error::bad_crlf); - call_on_request(ec); - if(ec) - return errc(); - s_ = s_header_name0; - break; - - //---------------------------------------------------------------------- - - case s_res_start: - flags_ = 0; - cb_ = nullptr; - content_length_ = no_content_length; - if(ch != 'H') - return err(parse_error::bad_version); - call_on_start(ec); - if(ec) - return errc(); - s_ = s_res_H; - break; - - case s_res_H: - if(ch != 'T') - return err(parse_error::bad_version); - s_ = s_res_HT; - break; - - case s_res_HT: - if(ch != 'T') - return err(parse_error::bad_version); - s_ = s_res_HTT; - break; - - case s_res_HTT: - if(ch != 'P') - return err(parse_error::bad_version); - s_ = s_res_HTTP; - break; - - case s_res_HTTP: - if(ch != '/') - return err(parse_error::bad_version); - s_ = s_res_major; - break; - - case s_res_major: - if(! is_digit(ch)) - return err(parse_error::bad_version); - http_major_ = ch - '0'; - s_ = s_res_dot; - break; - - case s_res_dot: - if(ch != '.') - return err(parse_error::bad_version); - s_ = s_res_minor; - break; - - case s_res_minor: - if(! is_digit(ch)) - return err(parse_error::bad_version); - http_minor_ = ch - '0'; - s_ = s_res_space_1; - break; - - case s_res_space_1: - if(ch != ' ') - return err(parse_error::bad_version); - s_ = s_res_status0; - break; - - case s_res_status0: - if(! is_digit(ch)) - return err(parse_error::bad_status); - status_code_ = ch - '0'; - s_ = s_res_status1; - break; - - case s_res_status1: - if(! is_digit(ch)) - return err(parse_error::bad_status); - status_code_ = status_code_ * 10 + ch - '0'; - s_ = s_res_status2; - break; - - case s_res_status2: - if(! is_digit(ch)) - return err(parse_error::bad_status); - status_code_ = status_code_ * 10 + ch - '0'; - s_ = s_res_space_2; - break; - - case s_res_space_2: - if(ch != ' ') - return err(parse_error::bad_status); - s_ = s_res_reason0; - break; - - case s_res_reason0: - if(ch == '\r') - { - s_ = s_res_line_lf; - break; - } - if(! is_text(ch)) - return err(parse_error::bad_reason); - BOOST_ASSERT(! cb_); - cb(&self::call_on_reason); - s_ = s_res_reason; - break; - - case s_res_reason: - if(ch == '\r') - { - if(cb(nullptr)) - return errc(); - s_ = s_res_line_lf; - break; - } - if(! is_text(ch)) - return err(parse_error::bad_reason); - break; - - case s_res_line_lf: - if(ch != '\n') - return err(parse_error::bad_crlf); - s_ = s_res_line_done; - break; - - case s_res_line_done: - call_on_response(ec); - if(ec) - return errc(); - s_ = s_header_name0; - goto redo; - - //---------------------------------------------------------------------- - - case s_header_name0: - { - if(ch == '\r') - { - s_ = s_headers_almost_done; - break; - } - auto c = to_field_char(ch); - if(! c) - return err(parse_error::bad_field); - switch(c) - { - case 'c': pos_ = 0; fs_ = h_C; break; - case 'p': pos_ = 0; fs_ = h_matching_proxy_connection; break; - case 't': pos_ = 0; fs_ = h_matching_transfer_encoding; break; - case 'u': pos_ = 0; fs_ = h_matching_upgrade; break; - default: - fs_ = h_general; - break; - } - BOOST_ASSERT(! cb_); - cb(&self::call_on_field); - s_ = s_header_name; - break; - } - - case s_header_name: - { - for(; p != end; ++p) - { - ch = *p; - auto c = to_field_char(ch); - if(! c) - break; - switch(fs_) - { - default: - case h_general: - break; - case h_C: ++pos_; fs_ = c=='o' ? h_CO : h_general; break; - case h_CO: ++pos_; fs_ = c=='n' ? h_CON : h_general; break; - case h_CON: - ++pos_; - switch(c) - { - case 'n': fs_ = h_matching_connection; break; - case 't': fs_ = h_matching_content_length; break; - default: - fs_ = h_general; - } - break; - - case h_matching_connection: - ++pos_; - if(c != detail::parser_str::connection[pos_]) - fs_ = h_general; - else if(pos_ == sizeof(detail::parser_str::connection)-2) - fs_ = h_connection; - break; - - case h_matching_proxy_connection: - ++pos_; - if(c != detail::parser_str::proxy_connection[pos_]) - fs_ = h_general; - else if(pos_ == sizeof(detail::parser_str::proxy_connection)-2) - fs_ = h_connection; - break; - - case h_matching_content_length: - ++pos_; - if(c != detail::parser_str::content_length[pos_]) - fs_ = h_general; - else if(pos_ == sizeof(detail::parser_str::content_length)-2) - { - if(flags_ & parse_flag::contentlength) - return err(parse_error::bad_content_length); - fs_ = h_content_length0; - } - break; - - case h_matching_transfer_encoding: - ++pos_; - if(c != detail::parser_str::transfer_encoding[pos_]) - fs_ = h_general; - else if(pos_ == sizeof(detail::parser_str::transfer_encoding)-2) - fs_ = h_transfer_encoding; - break; - - case h_matching_upgrade: - ++pos_; - if(c != detail::parser_str::upgrade[pos_]) - fs_ = h_general; - else if(pos_ == sizeof(detail::parser_str::upgrade)-2) - fs_ = h_upgrade; - break; - - case h_connection: - case h_content_length0: - case h_transfer_encoding: - case h_upgrade: - fs_ = h_general; - break; - } - } - if(p == end) - { - --p; - break; - } - if(ch == ':') - { - if(cb(nullptr)) - return errc(); - s_ = s_header_value0; - break; - } - return err(parse_error::bad_field); - } - /* - header-field = field-name ":" OWS field-value OWS - field-name = token - field-value = *( field-content / obs-fold ) - field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] - field-vchar = VCHAR / obs-text - obs-fold = CRLF 1*( SP / HTAB ) - ; obsolete line folding - */ - case s_header_value0: - if(ch == ' ' || ch == '\t') - break; - if(ch == '\r') - { - s_ = s_header_value0_lf; - break; - } - if(fs_ == h_content_length0) - { - content_length_ = 0; - flags_ |= parse_flag::contentlength; - } - BOOST_ASSERT(! cb_); - cb(&self::call_on_value); - s_ = s_header_value; - // fall through - - case s_header_value: - { - for(; p != end; ++p) - { - ch = *p; - if(ch == '\r') - { - if(cb(nullptr)) - return errc(); - s_ = s_header_value_lf; - break; - } - auto const c = to_value_char(ch); - if(! c) - return err(parse_error::bad_value); - switch(fs_) - { - case h_general: - default: - break; - - case h_connection: - switch(c) - { - case 'k': - pos_ = 0; - fs_ = h_matching_connection_keep_alive; - break; - case 'c': - pos_ = 0; - fs_ = h_matching_connection_close; - break; - case 'u': - pos_ = 0; - fs_ = h_matching_connection_upgrade; - break; - default: - if(ch == ' ' || ch == '\t' || ch == ',') - break; - if(! is_tchar(ch)) - return err(parse_error::bad_value); - fs_ = h_connection_token; - break; - } - break; - - case h_matching_connection_keep_alive: - ++pos_; - if(c != detail::parser_str::keep_alive[pos_]) - fs_ = h_connection_token; - else if(pos_ == sizeof(detail::parser_str::keep_alive)- 2) - fs_ = h_connection_keep_alive; - break; - - case h_matching_connection_close: - ++pos_; - if(c != detail::parser_str::close[pos_]) - fs_ = h_connection_token; - else if(pos_ == sizeof(detail::parser_str::close)-2) - fs_ = h_connection_close; - break; - - case h_matching_connection_upgrade: - ++pos_; - if(c != detail::parser_str::upgrade[pos_]) - fs_ = h_connection_token; - else if(pos_ == sizeof(detail::parser_str::upgrade)-2) - fs_ = h_connection_upgrade; - break; - - case h_connection_close: - if(ch == ',') - { - fs_ = h_connection; - flags_ |= parse_flag::connection_close; - } - else if(ch == ' ' || ch == '\t') - fs_ = h_connection_close_ows; - else if(is_tchar(ch)) - fs_ = h_connection_token; - else - return err(parse_error::bad_value); - break; - - case h_connection_close_ows: - if(ch == ',') - { - fs_ = h_connection; - flags_ |= parse_flag::connection_close; - break; - } - if(ch == ' ' || ch == '\t') - break; - return err(parse_error::bad_value); - - case h_connection_keep_alive: - if(ch == ',') - { - fs_ = h_connection; - flags_ |= parse_flag::connection_keep_alive; - } - else if(ch == ' ' || ch == '\t') - fs_ = h_connection_keep_alive_ows; - else if(is_tchar(ch)) - fs_ = h_connection_token; - else - return err(parse_error::bad_value); - break; - - case h_connection_keep_alive_ows: - if(ch == ',') - { - fs_ = h_connection; - flags_ |= parse_flag::connection_keep_alive; - break; - } - if(ch == ' ' || ch == '\t') - break; - return err(parse_error::bad_value); - - case h_connection_upgrade: - if(ch == ',') - { - fs_ = h_connection; - flags_ |= parse_flag::connection_upgrade; - } - else if(ch == ' ' || ch == '\t') - fs_ = h_connection_upgrade_ows; - else if(is_tchar(ch)) - fs_ = h_connection_token; - else - return err(parse_error::bad_value); - break; - - case h_connection_upgrade_ows: - if(ch == ',') - { - fs_ = h_connection; - flags_ |= parse_flag::connection_upgrade; - break; - } - if(ch == ' ' || ch == '\t') - break; - return err(parse_error::bad_value); - - case h_connection_token: - if(ch == ',') - fs_ = h_connection; - else if(ch == ' ' || ch == '\t') - fs_ = h_connection_token_ows; - else if(! is_tchar(ch)) - return err(parse_error::bad_value); - break; - - case h_connection_token_ows: - if(ch == ',') - { - fs_ = h_connection; - break; - } - if(ch == ' ' || ch == '\t') - break; - return err(parse_error::bad_value); - - case h_content_length0: - if(! is_digit(ch)) - return err(parse_error::bad_content_length); - content_length_ = ch - '0'; - fs_ = h_content_length; - break; - - case h_content_length: - if(ch == ' ' || ch == '\t') - { - fs_ = h_content_length_ows; - break; - } - if(! is_digit(ch)) - return err(parse_error::bad_content_length); - if(content_length_ > (no_content_length - 10) / 10) - return err(parse_error::bad_content_length); - content_length_ = - content_length_ * 10 + ch - '0'; - break; - - case h_content_length_ows: - if(ch != ' ' && ch != '\t') - return err(parse_error::bad_content_length); - break; - - case h_transfer_encoding: - if(c == 'c') - { - pos_ = 0; - fs_ = h_matching_transfer_encoding_chunked; - } - else if(c != ' ' && c != '\t' && c != ',') - { - fs_ = h_matching_transfer_encoding_general; - } - break; - - case h_matching_transfer_encoding_chunked: - ++pos_; - if(c != detail::parser_str::chunked[pos_]) - fs_ = h_matching_transfer_encoding_general; - else if(pos_ == sizeof(detail::parser_str::chunked)-2) - fs_ = h_transfer_encoding_chunked; - break; - - case h_matching_transfer_encoding_general: - if(c == ',') - fs_ = h_transfer_encoding; - break; - - case h_transfer_encoding_chunked: - if(c != ' ' && c != '\t' && c != ',') - fs_ = h_transfer_encoding; - break; - - case h_upgrade: - flags_ |= parse_flag::upgrade; - fs_ = h_general; - break; - } - } - if(p == end) - --p; - break; - } - - case s_header_value0_lf: - if(ch != '\n') - return err(parse_error::bad_crlf); - s_ = s_header_value0_almost_done; - break; - - case s_header_value0_almost_done: - if(ch == ' ' || ch == '\t') - { - s_ = s_header_value0; - break; - } - if(fs_ == h_content_length0) - return err(parse_error::bad_content_length); - if(fs_ == h_upgrade) - flags_ |= parse_flag::upgrade; - BOOST_ASSERT(! cb_); - call_on_value(ec, boost::string_ref{"", 0}); - if(ec) - return errc(); - s_ = s_header_name0; - goto redo; - - case s_header_value_lf: - if(ch != '\n') - return err(parse_error::bad_crlf); - s_ = s_header_value_almost_done; - break; - - case s_header_value_almost_done: - if(ch == ' ' || ch == '\t') - { - switch(fs_) - { - case h_matching_connection_keep_alive: - case h_matching_connection_close: - case h_matching_connection_upgrade: - fs_ = h_connection_token_ows; - break; - - case h_connection_close: - fs_ = h_connection_close_ows; - break; - - case h_connection_keep_alive: - fs_ = h_connection_keep_alive_ows; - break; - - case h_connection_upgrade: - fs_ = h_connection_upgrade_ows; - break; - - case h_content_length: - fs_ = h_content_length_ows; - break; - - case h_matching_transfer_encoding_chunked: - fs_ = h_matching_transfer_encoding_general; - break; - - default: - break; - } - call_on_value(ec, boost::string_ref(" ", 1)); - s_ = s_header_value_unfold; - break; - } - switch(fs_) - { - case h_connection_keep_alive: - case h_connection_keep_alive_ows: - flags_ |= parse_flag::connection_keep_alive; - break; - case h_connection_close: - case h_connection_close_ows: - flags_ |= parse_flag::connection_close; - break; - - case h_connection_upgrade: - case h_connection_upgrade_ows: - flags_ |= parse_flag::connection_upgrade; - break; - - case h_transfer_encoding_chunked: - case h_transfer_encoding_chunked_ows: - flags_ |= parse_flag::chunked; - break; - - default: - break; - } - s_ = s_header_name0; - goto redo; - - case s_header_value_unfold: - BOOST_ASSERT(! cb_); - cb(&self::call_on_value); - s_ = s_header_value; - goto redo; - - case s_headers_almost_done: - { - if(ch != '\n') - return err(parse_error::bad_crlf); - if(flags_ & parse_flag::trailing) - { - //if(cb(&self::call_on_chunk_complete)) return errc(); - s_ = s_complete; - goto redo; - } - if((flags_ & parse_flag::chunked) && (flags_ & parse_flag::contentlength)) - return err(parse_error::illegal_content_length); - upgrade_ = ((flags_ & (parse_flag::upgrade | parse_flag::connection_upgrade)) == - (parse_flag::upgrade | parse_flag::connection_upgrade)) /*|| method == "connect"*/; - call_on_headers(ec); - if(ec) - return errc(); - auto const what = call_on_body_what(ec); - if(ec) - return errc(); - switch(what) - { - case body_what::normal: - break; - case body_what::upgrade: - upgrade_ = true; - // fall through - case body_what::skip: - flags_ |= parse_flag::skipbody; - break; - case body_what::pause: - ++p; - s_ = s_body_pause; - flags_ |= parse_flag::paused; - return used(); - } - s_ = s_headers_done; - goto redo; - } - - case s_body_pause: - { - auto const what = call_on_body_what(ec); - if(ec) - return errc(); - switch(what) - { - case body_what::normal: - break; - case body_what::upgrade: - upgrade_ = true; - // fall through - case body_what::skip: - flags_ |= parse_flag::skipbody; - break; - case body_what::pause: - return used(); - } - --p; - s_ = s_headers_done; - // fall through - } - - case s_headers_done: - { - BOOST_ASSERT(! cb_); - if(ec) - return errc(); - bool const hasBody = - (flags_ & parse_flag::chunked) || (content_length_ > 0 && - content_length_ != no_content_length); - if(upgrade_ && (/*method == "connect" ||*/ (flags_ & parse_flag::skipbody) || ! hasBody)) - { - s_ = s_complete; - } - else if((flags_ & parse_flag::skipbody) || content_length_ == 0) - { - s_ = s_complete; - } - else if(flags_ & parse_flag::chunked) - { - s_ = s_chunk_size0; - break; - } - else if(content_length_ != no_content_length) - { - s_ = s_body_identity0; - break; - } - else if(! needs_eof()) - { - s_ = s_complete; - } - else - { - s_ = s_body_identity_eof0; - break; - } - goto redo; - } - - case s_body_identity0: - BOOST_ASSERT(! cb_); - cb(&self::call_on_body); - s_ = s_body_identity; - // fall through - - case s_body_identity: - { - std::size_t n; - if(static_cast((end - p)) < content_length_) - n = end - p; - else - n = static_cast(content_length_); - BOOST_ASSERT(content_length_ != 0 && content_length_ != no_content_length); - content_length_ -= n; - if(content_length_ == 0) - { - p += n - 1; - s_ = s_complete; - goto redo; // ???? - } - p += n - 1; - break; - } - - case s_body_identity_eof0: - BOOST_ASSERT(! cb_); - cb(&self::call_on_body); - s_ = s_body_identity_eof; - // fall through - - case s_body_identity_eof: - p = end - 1; - break; - - case s_chunk_size0: - { - auto v = unhex(ch); - if(v == -1) - return err(parse_error::invalid_chunk_size); - content_length_ = v; - s_ = s_chunk_size; - break; - } - - case s_chunk_size: - { - if(ch == '\r') - { - s_ = s_chunk_size_lf; - break; - } - if(ch == ';') - { - s_ = s_chunk_ext_name0; - break; - } - auto v = unhex(ch); - if(v == -1) - return err(parse_error::invalid_chunk_size); - if(content_length_ > (no_content_length - 16) / 16) - return err(parse_error::bad_content_length); - content_length_ = - content_length_ * 16 + v; - break; - } - - case s_chunk_ext_name0: - if(! is_tchar(ch)) - return err(parse_error::invalid_ext_name); - s_ = s_chunk_ext_name; - break; - - case s_chunk_ext_name: - if(ch == '\r') - { - s_ = s_chunk_size_lf; - break; - } - if(ch == '=') - { - s_ = s_chunk_ext_val; - break; - } - if(ch == ';') - { - s_ = s_chunk_ext_name0; - break; - } - if(! is_tchar(ch)) - return err(parse_error::invalid_ext_name); - break; - - case s_chunk_ext_val: - if(ch == '\r') - { - s_ = s_chunk_size_lf; - break; - } - break; - - case s_chunk_size_lf: - if(ch != '\n') - return err(parse_error::bad_crlf); - if(content_length_ == 0) - { - flags_ |= parse_flag::trailing; - s_ = s_header_name0; - break; - } - //call_chunk_header(ec); if(ec) return errc(); - s_ = s_chunk_data0; - break; - - case s_chunk_data0: - BOOST_ASSERT(! cb_); - cb(&self::call_on_body); - s_ = s_chunk_data; - goto redo; // VFALCO fall through? - - case s_chunk_data: - { - std::size_t n; - if(static_cast((end - p)) < content_length_) - n = end - p; - else - n = static_cast(content_length_); - content_length_ -= n; - p += n - 1; - if(content_length_ == 0) - s_ = s_chunk_data_cr; - break; - } - - case s_chunk_data_cr: - if(ch != '\r') - return err(parse_error::bad_crlf); - if(cb(nullptr)) - return errc(); - s_ = s_chunk_data_lf; - break; - - case s_chunk_data_lf: - if(ch != '\n') - return err(parse_error::bad_crlf); - s_ = s_chunk_size0; - break; - - case s_complete: - ++p; - if(cb(nullptr)) - return errc(); - call_on_complete(ec); - if(ec) - return errc(); - s_ = s_restart; - return used(); - - case s_restart: - if(keep_alive()) - reset(); - else - s_ = s_dead; - goto redo; - } - } - if(cb_) - { - (this->*cb_)(ec, piece()); - if(ec) - return errc(); - } - return used(); -} - -template -void -basic_parser_v1:: -write_eof(error_code& ec) -{ - switch(s_) - { - case s_restart: - s_ = s_closed_complete; - break; - - case s_dead: - case s_closed_complete: - break; - - case s_body_identity_eof0: - case s_body_identity_eof: - cb_ = nullptr; - call_on_complete(ec); - if(ec) - { - s_ = s_dead; - break; - } - s_ = s_closed_complete; - break; - - default: - s_ = s_dead; - ec = parse_error::short_read; - break; - } -} - -template -void -basic_parser_v1:: -reset() -{ - cb_ = nullptr; - h_left_ = h_max_; - b_left_ = b_max_; - reset(std::integral_constant{}); -} - -template -bool -basic_parser_v1:: -needs_eof(std::true_type) const -{ - return false; -} - -template -bool -basic_parser_v1:: -needs_eof(std::false_type) const -{ - // See RFC 2616 section 4.4 - if( status_code_ / 100 == 1 || // 1xx e.g. Continue - status_code_ == 204 || // No Content - status_code_ == 304 || // Not Modified - flags_ & parse_flag::skipbody) // response to a HEAD request - return false; - - if((flags_ & parse_flag::chunked) || - content_length_ != no_content_length) - return false; - - return true; -} - -} // http -} // beast - -#endif diff --git a/src/beast/include/beast/http/impl/error.ipp b/src/beast/include/beast/http/impl/error.ipp new file mode 100644 index 0000000000..fc2438513d --- /dev/null +++ b/src/beast/include/beast/http/impl/error.ipp @@ -0,0 +1,115 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_IMPL_ERROR_IPP +#define BEAST_HTTP_IMPL_ERROR_IPP + +#include + +namespace boost { +namespace system { +template<> +struct is_error_code_enum +{ + static bool const value = true; +}; +} // system +} // boost + +namespace beast { +namespace http { +namespace detail { + +class http_error_category : public error_category +{ +public: + const char* + name() const noexcept override + { + return "beast.http"; + } + + std::string + message(int ev) const override + { + switch(static_cast(ev)) + { + case error::end_of_stream: return "end of stream"; + case error::partial_message: return "partial message"; + case error::need_more: return "need more"; + case error::unexpected_body: return "unexpected body"; + case error::need_buffer: return "need buffer"; + case error::buffer_overflow: return "buffer overflow"; + case error::header_limit: return "header limit exceeded"; + case error::body_limit: return "body limit exceeded"; + case error::bad_alloc: return "bad alloc"; + case error::bad_line_ending: return "bad line ending"; + case error::bad_method: return "bad method"; + case error::bad_target: return "bad target"; + case error::bad_version: return "bad version"; + case error::bad_status: return "bad status"; + case error::bad_reason: return "bad reason"; + case error::bad_field: return "bad field"; + case error::bad_value: return "bad value"; + case error::bad_content_length: return "bad Content-Length"; + case error::bad_transfer_encoding: return "bad Transfer-Encoding"; + case error::bad_chunk: return "bad chunk"; + case error::bad_obs_fold: return "bad obs-fold"; + + default: + return "beast.http error"; + } + } + + error_condition + default_error_condition( + int ev) const noexcept override + { + return error_condition{ev, *this}; + } + + bool + equivalent(int ev, + error_condition const& condition + ) const noexcept override + { + return condition.value() == ev && + &condition.category() == this; + } + + bool + equivalent(error_code const& error, + int ev) const noexcept override + { + return error.value() == ev && + &error.category() == this; + } +}; + +inline +error_category const& +get_http_error_category() +{ + static http_error_category const cat{}; + return cat; +} + +} // detail + +inline +error_code +make_error_code(error ev) +{ + return error_code{ + static_cast::type>(ev), + detail::get_http_error_category()}; +} + +} // http +} // beast + +#endif diff --git a/src/beast/include/beast/http/impl/field.ipp b/src/beast/include/beast/http/impl/field.ipp new file mode 100644 index 0000000000..6d4f2c5029 --- /dev/null +++ b/src/beast/include/beast/http/impl/field.ipp @@ -0,0 +1,557 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_IMPL_FIELD_IPP +#define BEAST_HTTP_IMPL_FIELD_IPP + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +namespace detail { + +struct field_table +{ + using array_type = + std::array; + + struct hash + { + std::size_t + operator()(string_view const& s) const + { + auto const n = s.size(); + return + beast::detail::ascii_tolower(s[0]) * + beast::detail::ascii_tolower(s[n/2]) ^ + beast::detail::ascii_tolower(s[n-1]); // hist[] = 331, 10, max_load_factor = 0.15f + } + }; + + struct iequal + { + // assumes inputs have equal length + bool + operator()( + string_view const& lhs, + string_view const& rhs) const + { + auto p1 = lhs.data(); + auto p2 = rhs.data(); + auto pend = lhs.end(); + char a, b; + while(p1 < pend) + { + a = *p1++; + b = *p2++; + if(a != b) + goto slow; + } + return true; + + while(p1 < pend) + { + slow: + if( beast::detail::ascii_tolower(a) != + beast::detail::ascii_tolower(b)) + return false; + a = *p1++; + b = *p2++; + } + return true; + } + }; + + using map_type = std::unordered_map< + string_view, field, hash, iequal>; + + array_type by_name_; + std::vector by_size_; +/* + From: + + https://www.iana.org/assignments/message-headers/message-headers.xhtml +*/ + field_table() + : by_name_({{ + "", + "A-IM", + "Accept", + "Accept-Additions", + "Accept-Charset", + "Accept-Datetime", + "Accept-Encoding", + "Accept-Features", + "Accept-Language", + "Accept-Patch", + "Accept-Post", + "Accept-Ranges", + "Access-Control", + "Access-Control-Allow-Credentials", + "Access-Control-Allow-Headers", + "Access-Control-Allow-Methods", + "Access-Control-Allow-Origin", + "Access-Control-Max-Age", + "Access-Control-Request-Headers", + "Access-Control-Request-Method", + "Age", + "Allow", + "ALPN", + "Also-Control", + "Alt-Svc", + "Alt-Used", + "Alternate-Recipient", + "Alternates", + "Apparently-To", + "Apply-To-Redirect-Ref", + "Approved", + "Archive", + "Archived-At", + "Article-Names", + "Article-Updates", + "Authentication-Control", + "Authentication-Info", + "Authentication-Results", + "Authorization", + "Auto-Submitted", + "Autoforwarded", + "Autosubmitted", + "Base", + "Bcc", + "Body", + "C-Ext", + "C-Man", + "C-Opt", + "C-PEP", + "C-PEP-Info", + "Cache-Control", + "CalDAV-Timezones", + "Cancel-Key", + "Cancel-Lock", + "Cc", + "Close", + "Comments", + "Compliance", + "Connection", + "Content-Alternative", + "Content-Base", + "Content-Description", + "Content-Disposition", + "Content-Duration", + "Content-Encoding", + "Content-features", + "Content-ID", + "Content-Identifier", + "Content-Language", + "Content-Length", + "Content-Location", + "Content-MD5", + "Content-Range", + "Content-Return", + "Content-Script-Type", + "Content-Style-Type", + "Content-Transfer-Encoding", + "Content-Type", + "Content-Version", + "Control", + "Conversion", + "Conversion-With-Loss", + "Cookie", + "Cookie2", + "Cost", + "DASL", + "Date", + "Date-Received", + "DAV", + "Default-Style", + "Deferred-Delivery", + "Delivery-Date", + "Delta-Base", + "Depth", + "Derived-From", + "Destination", + "Differential-ID", + "Digest", + "Discarded-X400-IPMS-Extensions", + "Discarded-X400-MTS-Extensions", + "Disclose-Recipients", + "Disposition-Notification-Options", + "Disposition-Notification-To", + "Distribution", + "DKIM-Signature", + "DL-Expansion-History", + "Downgraded-Bcc", + "Downgraded-Cc", + "Downgraded-Disposition-Notification-To", + "Downgraded-Final-Recipient", + "Downgraded-From", + "Downgraded-In-Reply-To", + "Downgraded-Mail-From", + "Downgraded-Message-Id", + "Downgraded-Original-Recipient", + "Downgraded-Rcpt-To", + "Downgraded-References", + "Downgraded-Reply-To", + "Downgraded-Resent-Bcc", + "Downgraded-Resent-Cc", + "Downgraded-Resent-From", + "Downgraded-Resent-Reply-To", + "Downgraded-Resent-Sender", + "Downgraded-Resent-To", + "Downgraded-Return-Path", + "Downgraded-Sender", + "Downgraded-To", + "EDIINT-Features", + "Eesst-Version", + "Encoding", + "Encrypted", + "Errors-To", + "ETag", + "Expect", + "Expires", + "Expiry-Date", + "Ext", + "Followup-To", + "Forwarded", + "From", + "Generate-Delivery-Report", + "GetProfile", + "Hobareg", + "Host", + "HTTP2-Settings", + "If", + "If-Match", + "If-Modified-Since", + "If-None-Match", + "If-Range", + "If-Schedule-Tag-Match", + "If-Unmodified-Since", + "IM", + "Importance", + "In-Reply-To", + "Incomplete-Copy", + "Injection-Date", + "Injection-Info", + "Jabber-ID", + "Keep-Alive", + "Keywords", + "Label", + "Language", + "Last-Modified", + "Latest-Delivery-Time", + "Lines", + "Link", + "List-Archive", + "List-Help", + "List-ID", + "List-Owner", + "List-Post", + "List-Subscribe", + "List-Unsubscribe", + "List-Unsubscribe-Post", + "Location", + "Lock-Token", + "Man", + "Max-Forwards", + "Memento-Datetime", + "Message-Context", + "Message-ID", + "Message-Type", + "Meter", + "Method-Check", + "Method-Check-Expires", + "MIME-Version", + "MMHS-Acp127-Message-Identifier", + "MMHS-Authorizing-Users", + "MMHS-Codress-Message-Indicator", + "MMHS-Copy-Precedence", + "MMHS-Exempted-Address", + "MMHS-Extended-Authorisation-Info", + "MMHS-Handling-Instructions", + "MMHS-Message-Instructions", + "MMHS-Message-Type", + "MMHS-Originator-PLAD", + "MMHS-Originator-Reference", + "MMHS-Other-Recipients-Indicator-CC", + "MMHS-Other-Recipients-Indicator-To", + "MMHS-Primary-Precedence", + "MMHS-Subject-Indicator-Codes", + "MT-Priority", + "Negotiate", + "Newsgroups", + "NNTP-Posting-Date", + "NNTP-Posting-Host", + "Non-Compliance", + "Obsoletes", + "Opt", + "Optional", + "Optional-WWW-Authenticate", + "Ordering-Type", + "Organization", + "Origin", + "Original-Encoded-Information-Types", + "Original-From", + "Original-Message-ID", + "Original-Recipient", + "Original-Sender", + "Original-Subject", + "Originator-Return-Address", + "Overwrite", + "P3P", + "Path", + "PEP", + "Pep-Info", + "PICS-Label", + "Position", + "Posting-Version", + "Pragma", + "Prefer", + "Preference-Applied", + "Prevent-NonDelivery-Report", + "Priority", + "Privicon", + "ProfileObject", + "Protocol", + "Protocol-Info", + "Protocol-Query", + "Protocol-Request", + "Proxy-Authenticate", + "Proxy-Authentication-Info", + "Proxy-Authorization", + "Proxy-Connection", + "Proxy-Features", + "Proxy-Instruction", + "Public", + "Public-Key-Pins", + "Public-Key-Pins-Report-Only", + "Range", + "Received", + "Received-SPF", + "Redirect-Ref", + "References", + "Referer", + "Referer-Root", + "Relay-Version", + "Reply-By", + "Reply-To", + "Require-Recipient-Valid-Since", + "Resent-Bcc", + "Resent-Cc", + "Resent-Date", + "Resent-From", + "Resent-Message-ID", + "Resent-Reply-To", + "Resent-Sender", + "Resent-To", + "Resolution-Hint", + "Resolver-Location", + "Retry-After", + "Return-Path", + "Safe", + "Schedule-Reply", + "Schedule-Tag", + "Sec-WebSocket-Accept", + "Sec-WebSocket-Extensions", + "Sec-WebSocket-Key", + "Sec-WebSocket-Protocol", + "Sec-WebSocket-Version", + "Security-Scheme", + "See-Also", + "Sender", + "Sensitivity", + "Server", + "Set-Cookie", + "Set-Cookie2", + "SetProfile", + "SIO-Label", + "SIO-Label-History", + "SLUG", + "SoapAction", + "Solicitation", + "Status-URI", + "Strict-Transport-Security", + "Subject", + "SubOK", + "Subst", + "Summary", + "Supersedes", + "Surrogate-Capability", + "Surrogate-Control", + "TCN", + "TE", + "Timeout", + "Title", + "To", + "Topic", + "Trailer", + "Transfer-Encoding", + "TTL", + "UA-Color", + "UA-Media", + "UA-Pixels", + "UA-Resolution", + "UA-Windowpixels", + "Upgrade", + "Urgency", + "URI", + "User-Agent", + "Variant-Vary", + "Vary", + "VBR-Info", + "Version", + "Via", + "Want-Digest", + "Warning", + "WWW-Authenticate", + "X-Archived-At", + "X-Device-Accept", + "X-Device-Accept-Charset", + "X-Device-Accept-Encoding", + "X-Device-Accept-Language", + "X-Device-User-Agent", + "X-Frame-Options", + "X-Mittente", + "X-PGP-Sig", + "X-Ricevuta", + "X-Riferimento-Message-ID", + "X-TipoRicevuta", + "X-Trasporto", + "X-VerificaSicurezza", + "X400-Content-Identifier", + "X400-Content-Return", + "X400-Content-Type", + "X400-MTS-Identifier", + "X400-Originator", + "X400-Received", + "X400-Recipients", + "X400-Trace", + "Xref" + }}) + { + // find the longest field length + std::size_t high = 0; + for(auto const& s : by_name_) + if(high < s.size()) + high = s.size(); + // build by_size map + // skip field::unknown + by_size_.resize(high + 1); + for(auto& map : by_size_) + map.max_load_factor(.15f); + for(std::size_t i = 1; + i < by_name_.size(); ++i) + { + auto const& s = by_name_[i]; + by_size_[s.size()].emplace( + s, static_cast(i)); + } + +#if 0 + // This snippet calculates the performance + // of the hash function and map settings + { + std::vector hist; + for(auto const& map : by_size_) + { + for(std::size_t i = 0; i < map.bucket_count(); ++i) + { + auto const n = map.bucket_size(i); + if(n > 0) + { + if(hist.size() < n) + hist.resize(n); + ++hist[n-1]; + } + } + } + } +#endif + } + + field + string_to_field(string_view s) const + { + if(s.size() >= by_size_.size()) + return field::unknown; + auto const& map = by_size_[s.size()]; + if(map.empty()) + return field::unknown; + auto it = map.find(s); + if(it == map.end()) + return field::unknown; + return it->second; + } + + // + // Deprecated + // + + using const_iterator = + array_type::const_iterator; + + std::size_t + size() const + { + return by_name_.size(); + } + + const_iterator + begin() const + { + return by_name_.begin(); + } + + const_iterator + end() const + { + return by_name_.end(); + } +}; + +inline +field_table const& +get_field_table() +{ + static field_table const tab; + return tab; +} + +template +string_view +to_string(field f) +{ + auto const& v = get_field_table(); + BOOST_ASSERT(static_cast(f) < v.size()); + return v.begin()[static_cast(f)]; +} + +} // detail + +inline +string_view +to_string(field f) +{ + return detail::to_string(f); +} + +inline +field +string_to_field(string_view s) +{ + return detail::get_field_table().string_to_field(s); +} + +} // http +} // beast + +#endif diff --git a/src/beast/include/beast/http/impl/fields.ipp b/src/beast/include/beast/http/impl/fields.ipp new file mode 100644 index 0000000000..f5b4a1598d --- /dev/null +++ b/src/beast/include/beast/http/impl/fields.ipp @@ -0,0 +1,1348 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_IMPL_FIELDS_IPP +#define BEAST_HTTP_IMPL_FIELDS_IPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(BOOST_LIBSTDCXX_VERSION) && BOOST_LIBSTDCXX_VERSION < 60000 + // Workaround for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56437 +#ifndef BEAST_HTTP_NO_FIELDS_BASIC_STRING_ALLOCATOR +#define BEAST_HTTP_NO_FIELDS_BASIC_STRING_ALLOCATOR +#endif +#endif + +namespace beast { +namespace http { + +template +class basic_fields::reader +{ +public: + using iter_type = typename list_t::const_iterator; + + struct field_iterator + { + iter_type it_; + + using value_type = boost::asio::const_buffer; + using pointer = value_type const*; + using reference = value_type const; + using difference_type = std::ptrdiff_t; + using iterator_category = + std::bidirectional_iterator_tag; + + field_iterator() = default; + field_iterator(field_iterator&& other) = default; + field_iterator(field_iterator const& other) = default; + field_iterator& operator=(field_iterator&& other) = default; + field_iterator& operator=(field_iterator const& other) = default; + + explicit + field_iterator(iter_type it) + : it_(it) + { + } + + bool + operator==(field_iterator const& other) const + { + return it_ == other.it_; + } + + bool + operator!=(field_iterator const& other) const + { + return !(*this == other); + } + + reference + operator*() const + { + return it_->buffer(); + } + + field_iterator& + operator++() + { + ++it_; + return *this; + } + + field_iterator + operator++(int) + { + auto temp = *this; + ++(*this); + return temp; + } + + field_iterator& + operator--() + { + --it_; + return *this; + } + + field_iterator + operator--(int) + { + auto temp = *this; + --(*this); + return temp; + } + }; + + class field_range + { + field_iterator first_; + field_iterator last_; + + public: + using const_iterator = + field_iterator; + + using value_type = + typename const_iterator::value_type; + + field_range(field_range const&) = default; + + field_range(iter_type first, iter_type last) + : first_(first) + , last_(last) + { + } + + const_iterator + begin() const + { + return first_; + } + + const_iterator + end() const + { + return last_; + } + }; + + basic_fields const& f_; + boost::asio::const_buffer cb_[3]; + char buf_[13]; + +public: + using const_buffers_type = + buffer_cat_view< + boost::asio::const_buffers_1, + boost::asio::const_buffers_1, + boost::asio::const_buffers_1, + field_range, + boost::asio::const_buffers_1>; + + reader(basic_fields const& f, + unsigned version, verb v); + + reader(basic_fields const& f, + unsigned version, unsigned code); + + const_buffers_type + get() const + { + return buffer_cat( + boost::asio::const_buffers_1{cb_[0]}, + boost::asio::const_buffers_1{cb_[1]}, + boost::asio::const_buffers_1{cb_[2]}, + field_range(f_.list_.begin(), f_.list_.end()), + detail::chunk_crlf()); + } +}; + +template +basic_fields::reader:: +reader(basic_fields const& f, + unsigned version, verb v) + : f_(f) +{ +/* + request + "" + " " + " HTTP/X.Y\r\n" (11 chars) +*/ + string_view sv; + if(v == verb::unknown) + sv = f_.get_method_impl(); + else + sv = to_string(v); + cb_[0] = {sv.data(), sv.size()}; + + // target_or_reason_ has a leading SP + cb_[1] = { + f_.target_or_reason_.data(), + f_.target_or_reason_.size()}; + + buf_[0] = ' '; + buf_[1] = 'H'; + buf_[2] = 'T'; + buf_[3] = 'T'; + buf_[4] = 'P'; + buf_[5] = '/'; + buf_[6] = '0' + static_cast(version / 10); + buf_[7] = '.'; + buf_[8] = '0' + static_cast(version % 10); + buf_[9] = '\r'; + buf_[10]= '\n'; + cb_[2] = {buf_, 11}; +} + +template +basic_fields::reader:: +reader(basic_fields const& f, + unsigned version, unsigned code) + : f_(f) +{ +/* + response + "HTTP/X.Y ### " (13 chars) + "" + "\r\n" +*/ + buf_[0] = 'H'; + buf_[1] = 'T'; + buf_[2] = 'T'; + buf_[3] = 'P'; + buf_[4] = '/'; + buf_[5] = '0' + static_cast(version / 10); + buf_[6] = '.'; + buf_[7] = '0' + static_cast(version % 10); + buf_[8] = ' '; + buf_[9] = '0' + static_cast(code / 100); + buf_[10]= '0' + static_cast((code / 10) % 10); + buf_[11]= '0' + static_cast(code % 10); + buf_[12]= ' '; + cb_[0] = {buf_, 13}; + + string_view sv; + if(! f_.target_or_reason_.empty()) + sv = f_.target_or_reason_; + else + sv = obsolete_reason(static_cast(code)); + cb_[1] = {sv.data(), sv.size()}; + + cb_[2] = detail::chunk_crlf(); +} + +//------------------------------------------------------------------------------ + +template +basic_fields:: +value_type:: +value_type(field name, + string_view sname, string_view value) + : off_(static_cast(sname.size() + 2)) + , len_(static_cast(value.size())) + , f_(name) +{ + //BOOST_ASSERT(name == field::unknown || + // iequals(sname, to_string(name))); + char* p = reinterpret_cast(this + 1); + p[off_-2] = ':'; + p[off_-1] = ' '; + p[off_ + len_] = '\r'; + p[off_ + len_ + 1] = '\n'; + std::memcpy(p, sname.data(), sname.size()); + std::memcpy(p + off_, value.data(), value.size()); +} + +template +inline +field +basic_fields:: +value_type:: +name() const +{ + return f_; +} + +template +inline +string_view +basic_fields:: +value_type:: +name_string() const +{ + return {reinterpret_cast< + char const*>(this + 1), + static_cast(off_ - 2)}; +} + +template +inline +string_view +basic_fields:: +value_type:: +value() const +{ + return {reinterpret_cast< + char const*>(this + 1) + off_, + static_cast(len_)}; +} + +template +inline +boost::asio::const_buffer +basic_fields:: +value_type:: +buffer() const +{ + return boost::asio::const_buffer{ + reinterpret_cast(this + 1), + static_cast(off_) + len_ + 2}; +} + +//------------------------------------------------------------------------------ + +template +basic_fields:: +~basic_fields() +{ + delete_list(); + realloc_string(method_, {}); + realloc_string( + target_or_reason_, {}); +} + +template +basic_fields:: +basic_fields(Allocator const& alloc) + : alloc_(alloc) +{ +} + +template +basic_fields:: +basic_fields(basic_fields&& other) + : alloc_(std::move(other.alloc_)) + , set_(std::move(other.set_)) + , list_(std::move(other.list_)) + , method_(other.method_) + , target_or_reason_(other.target_or_reason_) +{ + other.method_.clear(); + other.target_or_reason_.clear(); +} + +template +basic_fields:: +basic_fields(basic_fields&& other, Allocator const& alloc) + : alloc_(alloc) +{ + if(alloc_ != other.alloc_) + { + copy_all(other); + other.clear_all(); + } + else + { + set_ = std::move(other.set_); + list_ = std::move(other.list_); + method_ = other.method_; + target_or_reason_ = other.target_or_reason_; + } +} + +template +basic_fields:: +basic_fields(basic_fields const& other) + : alloc_(alloc_traits:: + select_on_container_copy_construction(other.alloc_)) +{ + copy_all(other); +} + +template +basic_fields:: +basic_fields(basic_fields const& other, + Allocator const& alloc) + : alloc_(alloc) +{ + copy_all(other); +} + +template +template +basic_fields:: +basic_fields(basic_fields const& other) +{ + copy_all(other); +} + +template +template +basic_fields:: +basic_fields(basic_fields const& other, + Allocator const& alloc) + : alloc_(alloc) +{ + copy_all(other); +} + +template +auto +basic_fields:: +operator=(basic_fields&& other) -> + basic_fields& +{ + if(this == &other) + return *this; + move_assign(other, typename alloc_traits:: + propagate_on_container_move_assignment{}); + return *this; +} + +template +auto +basic_fields:: +operator=(basic_fields const& other) -> + basic_fields& +{ + copy_assign(other, typename alloc_traits:: + propagate_on_container_copy_assignment{}); + return *this; +} + +template +template +auto +basic_fields:: +operator=(basic_fields const& other) -> + basic_fields& +{ + clear_all(); + copy_all(other); + return *this; +} + +//------------------------------------------------------------------------------ +// +// Element access +// +//------------------------------------------------------------------------------ + +template +string_view +basic_fields:: +at(field name) const +{ + BOOST_ASSERT(name != field::unknown); + auto const it = find(name); + if(it == end()) + BOOST_THROW_EXCEPTION(std::out_of_range{ + "field not found"}); + return it->value(); +} + +template +string_view +basic_fields:: +at(string_view name) const +{ + auto const it = find(name); + if(it == end()) + BOOST_THROW_EXCEPTION(std::out_of_range{ + "field not found"}); + return it->value(); +} + +template +string_view +basic_fields:: +operator[](field name) const +{ + BOOST_ASSERT(name != field::unknown); + auto const it = find(name); + if(it == end()) + return {}; + return it->value(); +} + +template +string_view +basic_fields:: +operator[](string_view name) const +{ + auto const it = find(name); + if(it == end()) + return {}; + return it->value(); +} + +//------------------------------------------------------------------------------ +// +// Modifiers +// +//------------------------------------------------------------------------------ + +template +void +basic_fields:: +clear() +{ + delete_list(); + set_.clear(); + list_.clear(); +} + +template +inline +void +basic_fields:: +insert(field name, string_param const& value) +{ + BOOST_ASSERT(name != field::unknown); + insert(name, to_string(name), value); +} + +template +void +basic_fields:: +insert(string_view sname, string_param const& value) +{ + auto const name = + string_to_field(sname); + insert(name, sname, value); +} + +template +void +basic_fields:: +insert(field name, + string_view sname, string_param const& value) +{ + auto& e = new_element(name, sname, + static_cast(value)); + auto const before = + set_.upper_bound(sname, key_compare{}); + if(before == set_.begin()) + { + BOOST_ASSERT(count(sname) == 0); + set_.insert_before(before, e); + list_.push_back(e); + return; + } + auto const last = std::prev(before); + // VFALCO is it worth comparing `field name` first? + if(! iequals(sname, last->name_string())) + { + BOOST_ASSERT(count(sname) == 0); + set_.insert_before(before, e); + list_.push_back(e); + return; + } + // keep duplicate fields together in the list + set_.insert_before(before, e); + list_.insert(++list_.iterator_to(*last), e); +} + +template +void +basic_fields:: +set(field name, string_param const& value) +{ + BOOST_ASSERT(name != field::unknown); + set_element(new_element(name, to_string(name), + static_cast(value))); +} + +template +void +basic_fields:: +set(string_view sname, string_param const& value) +{ + set_element(new_element( + string_to_field(sname), sname, + static_cast(value))); +} + +template +auto +basic_fields:: +erase(const_iterator pos) -> + const_iterator +{ + auto next = pos.iter(); + auto& e = *next++; + set_.erase(e); + list_.erase(e); + delete_element(e); + return next; +} + +template +std::size_t +basic_fields:: +erase(field name) +{ + BOOST_ASSERT(name != field::unknown); + return erase(to_string(name)); +} + +template +std::size_t +basic_fields:: +erase(string_view name) +{ + std::size_t n =0; + set_.erase_and_dispose(name, key_compare{}, + [&](value_type* e) + { + ++n; + list_.erase(list_.iterator_to(*e)); + delete_element(*e); + }); + return n; +} + +template +void +basic_fields:: +swap(basic_fields& other) +{ + swap(other, typename alloc_traits:: + propagate_on_container_swap{}); +} + +template +void +swap( + basic_fields& lhs, + basic_fields& rhs) +{ + lhs.swap(rhs); +} + +//------------------------------------------------------------------------------ +// +// Lookup +// +//------------------------------------------------------------------------------ + +template +inline +std::size_t +basic_fields:: +count(field name) const +{ + BOOST_ASSERT(name != field::unknown); + return count(to_string(name)); +} + +template +std::size_t +basic_fields:: +count(string_view name) const +{ + return set_.count(name, key_compare{}); +} + +template +inline +auto +basic_fields:: +find(field name) const -> + const_iterator +{ + BOOST_ASSERT(name != field::unknown); + return find(to_string(name)); +} + +template +auto +basic_fields:: +find(string_view name) const -> + const_iterator +{ + auto const it = set_.find( + name, key_compare{}); + if(it == set_.end()) + return list_.end(); + return list_.iterator_to(*it); +} + +template +inline +auto +basic_fields:: +equal_range(field name) const -> + std::pair +{ + BOOST_ASSERT(name != field::unknown); + return equal_range(to_string(name)); +} + +template +auto +basic_fields:: +equal_range(string_view name) const -> + std::pair +{ + auto const result = + set_.equal_range(name, key_compare{}); + return { + list_.iterator_to(result->first), + list_.iterator_to(result->second)}; +} + +//------------------------------------------------------------------------------ + +namespace detail { + +// Filter a token list +// +template +void +filter_token_list( + String& s, + string_view const& value, + Pred&& pred) +{ + token_list te{value}; + auto it = te.begin(); + auto last = te.end(); + if(it == last) + return; + while(pred(*it)) + if(++it == last) + return; + s.append(it->data(), it->size()); + while(++it != last) + { + if(! pred(*it)) + { + s.append(", "); + s.append(it->data(), it->size()); + } + } +} + +// Filter the last item in a token list +template +void +filter_token_list_last( + String& s, + string_view const& value, + Pred&& pred) +{ + token_list te{value}; + if(te.begin() != te.end()) + { + auto it = te.begin(); + auto next = std::next(it); + if(next == te.end()) + { + if(! pred(*it)) + s.append(it->data(), it->size()); + return; + } + s.append(it->data(), it->size()); + for(;;) + { + it = next; + next = std::next(it); + if(next == te.end()) + { + if(! pred(*it)) + { + s.append(", "); + s.append(it->data(), it->size()); + } + return; + } + s.append(", "); + s.append(it->data(), it->size()); + } + } +} + +template +void +keep_alive_impl( + String& s, string_view const& value, + unsigned version, bool keep_alive) +{ + if(version < 11) + { + if(keep_alive) + { + // remove close + filter_token_list(s, value, + [](string_view s) + { + return iequals(s, "close"); + }); + // add keep-alive + if(s.empty()) + s.append("keep-alive"); + else if(! token_list{value}.exists("keep-alive")) + s.append(", keep-alive"); + } + else + { + // remove close and keep-alive + filter_token_list(s, value, + [](string_view s) + { + return + iequals(s, "close") || + iequals(s, "keep-alive"); + }); + } + } + else + { + if(keep_alive) + { + // remove close and keep-alive + filter_token_list(s, value, + [](string_view s) + { + return + iequals(s, "close") || + iequals(s, "keep-alive"); + }); + } + else + { + // remove keep-alive + filter_token_list(s, value, + [](string_view s) + { + return iequals(s, "keep-alive"); + }); + // add close + if(s.empty()) + s.append("close"); + else if(! token_list{value}.exists("close")) + s.append(", close"); + } + } +} + +} // detail + +//------------------------------------------------------------------------------ + +// Fields + +template +inline +string_view +basic_fields:: +get_method_impl() const +{ + return method_; +} + +template +inline +string_view +basic_fields:: +get_target_impl() const +{ + if(target_or_reason_.empty()) + return target_or_reason_; + return { + target_or_reason_.data() + 1, + target_or_reason_.size() - 1}; +} + +template +inline +string_view +basic_fields:: +get_reason_impl() const +{ + return target_or_reason_; +} + +template +bool +basic_fields:: +get_chunked_impl() const +{ + auto const te = token_list{ + (*this)[field::transfer_encoding]}; + for(auto it = te.begin(); it != te.end();) + { + auto const next = std::next(it); + if(next == te.end()) + return iequals(*it, "chunked"); + it = next; + } + return false; +} + +template +bool +basic_fields:: +get_keep_alive_impl(unsigned version) const +{ + auto const it = find(field::connection); + if(version < 11) + { + if(it == end()) + return false; + return token_list{ + it->value()}.exists("keep-alive"); + } + if(it == end()) + return true; + return ! token_list{ + it->value()}.exists("close"); +} + +template +inline +void +basic_fields:: +set_method_impl(string_view s) +{ + realloc_string(method_, s); +} + +template +inline +void +basic_fields:: +set_target_impl(string_view s) +{ + realloc_target( + target_or_reason_, s); +} + +template +inline +void +basic_fields:: +set_reason_impl(string_view s) +{ + realloc_string( + target_or_reason_, s); +} + +template +void +basic_fields:: +set_chunked_impl(bool value) +{ + auto it = find(field::transfer_encoding); + if(value) + { + // append "chunked" + if(it == end()) + { + set(field::transfer_encoding, "chunked"); + return; + } + auto const te = token_list{it->value()}; + for(auto itt = te.begin();;) + { + auto const next = std::next(itt); + if(next == te.end()) + { + if(iequals(*itt, "chunked")) + return; // already set + break; + } + itt = next; + } + static_string buf; + if(it->value().size() <= buf.size() + 9) + { + buf.append(it->value().data(), it->value().size()); + buf.append(", chunked", 9); + set(field::transfer_encoding, buf); + } + else + { + #ifdef BEAST_HTTP_NO_FIELDS_BASIC_STRING_ALLOCATOR + // Workaround for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56437 + std::string s; + #else + using rebind_type = + typename std::allocator_traits< + Allocator>::template rebind_alloc; + std::basic_string< + char, + std::char_traits, + rebind_type> s{rebind_type{alloc_}}; + #endif + s.reserve(it->value().size() + 9); + s.append(it->value().data(), it->value().size()); + s.append(", chunked", 9); + set(field::transfer_encoding, s); + } + return; + } + // filter "chunked" + if(it == end()) + return; + try + { + static_string buf; + detail::filter_token_list_last(buf, it->value(), + [](string_view const& s) + { + return iequals(s, "chunked"); + }); + if(! buf.empty()) + set(field::transfer_encoding, buf); + else + erase(field::transfer_encoding); + } + catch(std::length_error const&) + { + #ifdef BEAST_HTTP_NO_FIELDS_BASIC_STRING_ALLOCATOR + // Workaround for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56437 + std::string s; + #else + using rebind_type = + typename std::allocator_traits< + Allocator>::template rebind_alloc; + std::basic_string< + char, + std::char_traits, + rebind_type> s{rebind_type{alloc_}}; + #endif + s.reserve(it->value().size()); + detail::filter_token_list_last(s, it->value(), + [](string_view const& s) + { + return iequals(s, "chunked"); + }); + if(! s.empty()) + set(field::transfer_encoding, s); + else + erase(field::transfer_encoding); + } +} + +template +void +basic_fields:: +set_content_length_impl( + boost::optional const& value) +{ + if(! value) + erase(field::content_length); + else + set(field::content_length, *value); +} + +template +void +basic_fields:: +set_keep_alive_impl( + unsigned version, bool keep_alive) +{ + // VFALCO What about Proxy-Connection ? + auto const value = (*this)[field::connection]; + try + { + static_string buf; + detail::keep_alive_impl( + buf, value, version, keep_alive); + if(buf.empty()) + erase(field::connection); + else + set(field::connection, buf); + } + catch(std::length_error const&) + { + #ifdef BEAST_HTTP_NO_FIELDS_BASIC_STRING_ALLOCATOR + // Workaround for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56437 + std::string s; + #else + using rebind_type = + typename std::allocator_traits< + Allocator>::template rebind_alloc; + std::basic_string< + char, + std::char_traits, + rebind_type> s{rebind_type{alloc_}}; + #endif + s.reserve(value.size()); + detail::keep_alive_impl( + s, value, version, keep_alive); + if(s.empty()) + erase(field::connection); + else + set(field::connection, s); + } +} + +//------------------------------------------------------------------------------ + +template +auto +basic_fields:: +new_element(field name, + string_view sname, string_view value) -> + value_type& +{ + if(sname.size() + 2 > + (std::numeric_limits::max)()) + BOOST_THROW_EXCEPTION(std::length_error{ + "field name too large"}); + if(value.size() + 2 > + (std::numeric_limits::max)()) + BOOST_THROW_EXCEPTION(std::length_error{ + "field value too large"}); + value = detail::trim(value); + std::uint16_t const off = + static_cast(sname.size() + 2); + std::uint16_t const len = + static_cast(value.size()); + auto const p = alloc_traits::allocate(alloc_, + 1 + (off + len + 2 + sizeof(value_type) - 1) / + sizeof(value_type)); + // VFALCO allocator can't call the constructor because its private + //alloc_traits::construct(alloc_, p, name, sname, value); + new(p) value_type{name, sname, value}; + return *p; +} + +template +void +basic_fields:: +delete_element(value_type& e) +{ + auto const n = 1 + (e.off_ + e.len_ + 2 + + sizeof(value_type) - 1) / sizeof(value_type); + alloc_traits::destroy(alloc_, &e); + alloc_traits::deallocate(alloc_, &e, n); +} + +template +void +basic_fields:: +set_element(value_type& e) +{ + auto it = set_.lower_bound( + e.name_string(), key_compare{}); + if(it == set_.end() || ! iequals( + e.name_string(), it->name_string())) + { + set_.insert_before(it, e); + list_.push_back(e); + return; + } + for(;;) + { + auto next = it; + ++next; + set_.erase(it); + list_.erase(list_.iterator_to(*it)); + delete_element(*it); + it = next; + if(it == set_.end() || + ! iequals(e.name_string(), it->name_string())) + break; + } + set_.insert_before(it, e); + list_.push_back(e); +} + +template +void +basic_fields:: +realloc_string(string_view& dest, string_view s) +{ + if(dest.empty() && s.empty()) + return; + auto a = typename std::allocator_traits< + Allocator>::template rebind_alloc< + char>(alloc_); + if(! dest.empty()) + { + a.deallocate(const_cast( + dest.data()), dest.size()); + dest.clear(); + } + if(! s.empty()) + { + auto const p = a.allocate(s.size()); + std::memcpy(p, s.data(), s.size()); + dest = {p, s.size()}; + } +} + +template +void +basic_fields:: +realloc_target( + string_view& dest, string_view s) +{ + // The target string are stored with an + // extra space at the beginning to help + // the reader class. + if(dest.empty() && s.empty()) + return; + auto a = typename std::allocator_traits< + Allocator>::template rebind_alloc< + char>(alloc_); + if(! dest.empty()) + { + a.deallocate(const_cast( + dest.data()), dest.size()); + dest.clear(); + } + if(! s.empty()) + { + auto const p = a.allocate(1 + s.size()); + p[0] = ' '; + std::memcpy(p + 1, s.data(), s.size()); + dest = {p, 1 + s.size()}; + } +} + +template +template +void +basic_fields:: +copy_all(basic_fields const& other) +{ + for(auto const& e : other.list_) + insert(e.name(), e.name_string(), e.value()); + realloc_string(method_, other.method_); + realloc_string(target_or_reason_, + other.target_or_reason_); +} + +template +void +basic_fields:: +clear_all() +{ + clear(); + realloc_string(method_, {}); + realloc_string(target_or_reason_, {}); +} + +template +void +basic_fields:: +delete_list() +{ + for(auto it = list_.begin(); it != list_.end();) + delete_element(*it++); +} + +//------------------------------------------------------------------------------ + +template +inline +void +basic_fields:: +move_assign(basic_fields& other, std::true_type) +{ + clear_all(); + set_ = std::move(other.set_); + list_ = std::move(other.list_); + method_ = other.method_; + target_or_reason_ = other.target_or_reason_; + other.method_.clear(); + other.target_or_reason_.clear(); + alloc_ = other.alloc_; +} + +template +inline +void +basic_fields:: +move_assign(basic_fields& other, std::false_type) +{ + clear_all(); + if(alloc_ != other.alloc_) + { + copy_all(other); + other.clear_all(); + } + else + { + set_ = std::move(other.set_); + list_ = std::move(other.list_); + method_ = other.method_; + target_or_reason_ = other.target_or_reason_; + other.method_.clear(); + other.target_or_reason_.clear(); + } +} + +template +inline +void +basic_fields:: +copy_assign(basic_fields const& other, std::true_type) +{ + clear_all(); + alloc_ = other.alloc_; + copy_all(other); +} + +template +inline +void +basic_fields:: +copy_assign(basic_fields const& other, std::false_type) +{ + clear_all(); + copy_all(other); +} + +template +inline +void +basic_fields:: +swap(basic_fields& other, std::true_type) +{ + using std::swap; + swap(alloc_, other.alloc_); + swap(set_, other.set_); + swap(list_, other.list_); + swap(method_, other.method_); + swap(target_or_reason_, other.target_or_reason_); +} + +template +inline +void +basic_fields:: +swap(basic_fields& other, std::false_type) +{ + BOOST_ASSERT(alloc_ == other.alloc_); + using std::swap; + swap(set_, other.set_); + swap(list_, other.list_); + swap(method_, other.method_); + swap(target_or_reason_, other.target_or_reason_); +} + +} // http +} // beast + +#endif diff --git a/src/beast/include/beast/http/impl/file_body_win32.ipp b/src/beast/include/beast/http/impl/file_body_win32.ipp new file mode 100644 index 0000000000..03734339fe --- /dev/null +++ b/src/beast/include/beast/http/impl/file_body_win32.ipp @@ -0,0 +1,579 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_IMPL_FILE_BODY_WIN32_IPP +#define BEAST_HTTP_IMPL_FILE_BODY_WIN32_IPP + +#if BEAST_USE_WIN32_FILE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +namespace detail { +template +class write_some_win32_op; +} // detail + +template<> +struct basic_file_body +{ + using file_type = file_win32; + + class reader; + class writer; + + //-------------------------------------------------------------------------- + + class value_type + { + friend class reader; + friend class writer; + friend struct basic_file_body; + + template + friend class detail::write_some_win32_op; + template + friend + void + write_some( + boost::asio::basic_stream_socket& sock, + serializer, + Fields, Decorator>& sr, + error_code& ec); + + file_win32 file_; + std::uint64_t size_ = 0; // cached file size + std::uint64_t first_; // starting offset of the range + std::uint64_t last_; // ending offset of the range + + public: + ~value_type() = default; + value_type() = default; + value_type(value_type&& other) = default; + value_type& operator=(value_type&& other) = default; + + bool + is_open() const + { + return file_.is_open(); + } + + std::uint64_t + size() const + { + return size_; + } + + void + close(); + + void + open(char const* path, file_mode mode, error_code& ec); + + void + reset(file_win32&& file, error_code& ec); + }; + + //-------------------------------------------------------------------------- + + class reader + { + template + friend class detail::write_some_win32_op; + template + friend + void + write_some( + boost::asio::basic_stream_socket& sock, + serializer, + Fields, Decorator>& sr, + error_code& ec); + + value_type& body_; // The body we are reading from + std::uint64_t pos_; // The current position in the file + char buf_[4096]; // Small buffer for reading + + public: + using const_buffers_type = + boost::asio::const_buffers_1; + + template + reader(message, Fields>& m) + : body_(m.body) + { + } + + void + init(error_code&) + { + BOOST_ASSERT(body_.file_.is_open()); + pos_ = body_.first_; + } + + boost::optional> + get(error_code& ec) + { + std::size_t const n = (std::min)(sizeof(buf_), + beast::detail::clamp(body_.last_ - pos_)); + if(n == 0) + { + ec.assign(0, ec.category()); + return boost::none; + } + auto const nread = body_.file_.read(buf_, n, ec); + if(ec) + return boost::none; + BOOST_ASSERT(nread != 0); + pos_ += nread; + ec.assign(0, ec.category()); + return {{ + {buf_, nread}, // buffer to return. + pos_ < body_.last_}}; // `true` if there are more buffers. + } + }; + + //-------------------------------------------------------------------------- + + class writer + { + value_type& body_; + + public: + template + explicit + writer(message& m) + : body_(m.body) + { + } + + void + init(boost::optional< + std::uint64_t> const& content_length, + error_code& ec) + { + // VFALCO We could reserve space in the file + boost::ignore_unused(content_length); + BOOST_ASSERT(body_.file_.is_open()); + ec.assign(0, ec.category()); + } + + template + std::size_t + put(ConstBufferSequence const& buffers, + error_code& ec) + { + std::size_t nwritten = 0; + for(boost::asio::const_buffer buffer : buffers) + { + nwritten += body_.file_.write( + boost::asio::buffer_cast(buffer), + boost::asio::buffer_size(buffer), + ec); + if(ec) + return nwritten; + } + ec.assign(0, ec.category()); + return nwritten; + } + + void + finish(error_code& ec) + { + ec.assign(0, ec.category()); + } + }; + + //-------------------------------------------------------------------------- + + static + std::uint64_t + size(value_type const& body) + { + return body.size(); + } +}; + +//------------------------------------------------------------------------------ + +inline +void +basic_file_body:: +value_type:: +close() +{ + error_code ignored; + file_.close(ignored); +} + +inline +void +basic_file_body:: +value_type:: +open(char const* path, file_mode mode, error_code& ec) +{ + file_.open(path, mode, ec); + if(ec) + return; + size_ = file_.size(ec); + if(ec) + { + close(); + return; + } + first_ = 0; + last_ = size_; +} + +inline +void +basic_file_body:: +value_type:: +reset(file_win32&& file, error_code& ec) +{ + if(file_.is_open()) + { + error_code ignored; + file_.close(ignored); + } + file_ = std::move(file); + if(file_.is_open()) + { + size_ = file_.size(ec); + if(ec) + { + close(); + return; + } + first_ = 0; + last_ = size_; + } +} + +//------------------------------------------------------------------------------ + +namespace detail { + +template +inline +boost::detail::winapi::DWORD_ +lowPart(Unsigned n) +{ + return static_cast< + boost::detail::winapi::DWORD_>( + n & 0xffffffff); +} + +template +inline +boost::detail::winapi::DWORD_ +highPart(Unsigned n, std::true_type) +{ + return static_cast< + boost::detail::winapi::DWORD_>( + (n>>32)&0xffffffff); +} + +template +inline +boost::detail::winapi::DWORD_ +highPart(Unsigned, std::false_type) +{ + return 0; +} + +template +inline +boost::detail::winapi::DWORD_ +highPart(Unsigned n) +{ + return highPart(n, std::integral_constant< + bool, (sizeof(Unsigned)>4)>{}); +} + +class null_lambda +{ +public: + template + void + operator()(error_code&, + ConstBufferSequence const&) const + { + BOOST_ASSERT(false); + } +}; + +//------------------------------------------------------------------------------ + +#if BOOST_ASIO_HAS_WINDOWS_OVERLAPPED_PTR + +template +class write_some_win32_op +{ + boost::asio::basic_stream_socket& sock_; + serializer, + Fields, Decorator>& sr_; + bool header_ = false; + Handler h_; + +public: + write_some_win32_op(write_some_win32_op&&) = default; + write_some_win32_op(write_some_win32_op const&) = default; + + template + write_some_win32_op( + DeducedHandler&& h, + boost::asio::basic_stream_socket& s, + serializer, + Fields, Decorator>& sr) + : sock_(s) + , sr_(sr) + , h_(std::forward(h)) + { + } + + void + operator()(); + + void + operator()(error_code ec, + std::size_t bytes_transferred = 0); + + friend + void* asio_handler_allocate( + std::size_t size, write_some_win32_op* op) + { + using boost::asio::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(op->h_)); + } + + friend + void asio_handler_deallocate( + void* p, std::size_t size, write_some_win32_op* op) + { + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->h_)); + } + + friend + bool asio_handler_is_continuation(write_some_win32_op* op) + { + using boost::asio::asio_handler_is_continuation; + return asio_handler_is_continuation( + std::addressof(op->h_)); + } + + template + friend + void asio_handler_invoke(Function&& f, write_some_win32_op* op) + { + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->h_)); + } +}; + +template +void +write_some_win32_op< + Protocol, Handler, isRequest, Fields, Decorator>:: +operator()() +{ + if(! sr_.is_header_done()) + { + header_ = true; + sr_.split(true); + return detail::async_write_some( + sock_, sr_, std::move(*this)); + } + if(sr_.chunked()) + { + return detail::async_write_some( + sock_, sr_, std::move(*this)); + } + auto& r = sr_.reader_impl(); + boost::detail::winapi::DWORD_ const nNumberOfBytesToWrite = + std::min( + beast::detail::clamp(std::min( + r.body_.last_ - r.pos_, sr_.limit())), + 2147483646); + boost::asio::windows::overlapped_ptr overlapped{ + sock_.get_io_service(), *this}; + auto& ov = *overlapped.get(); + ov.Offset = lowPart(r.pos_); + ov.OffsetHigh = highPart(r.pos_); + auto const bSuccess = ::TransmitFile( + sock_.native_handle(), + sr_.get().body.file_.native_handle(), + nNumberOfBytesToWrite, + 0, + overlapped.get(), + nullptr, + 0); + auto const dwError = ::GetLastError(); + if(! bSuccess && dwError != + boost::detail::winapi::ERROR_IO_PENDING_) + { + // completed immediately + overlapped.complete(error_code{static_cast( + boost::detail::winapi::GetLastError()), + system_category()}, 0); + return; + } + overlapped.release(); +} + +template +void +write_some_win32_op< + Protocol, Handler,isRequest, Fields, Decorator>:: +operator()(error_code ec, std::size_t bytes_transferred) +{ + if(! ec) + { + if(header_) + { + header_ = false; + return (*this)(); + } + auto& r = sr_.reader_impl(); + r.pos_ += bytes_transferred; + BOOST_ASSERT(r.pos_ <= r.body_.last_); + if(r.pos_ >= r.body_.last_) + { + sr_.next(ec, null_lambda{}); + BOOST_ASSERT(! ec); + BOOST_ASSERT(sr_.is_done()); + if(! sr_.keep_alive()) + ec = error::end_of_stream; + } + } + h_(ec); +} + +#endif + +} // detail + +//------------------------------------------------------------------------------ + +template +void +write_some( + boost::asio::basic_stream_socket& sock, + serializer, + Fields, Decorator>& sr, + error_code& ec) +{ + if(! sr.is_header_done()) + { + sr.split(true); + detail::write_some(sock, sr, ec); + if(ec) + return; + return; + } + if(sr.chunked()) + { + detail::write_some(sock, sr, ec); + if(ec) + return; + return; + } + auto& r = sr.reader_impl(); + r.body_.file_.seek(r.pos_, ec); + if(ec) + return; + boost::detail::winapi::DWORD_ const nNumberOfBytesToWrite = + std::min( + beast::detail::clamp(std::min( + r.body_.last_ - r.pos_, sr.limit())), + 2147483646); + auto const bSuccess = ::TransmitFile( + sock.native_handle(), + r.body_.file_.native_handle(), + nNumberOfBytesToWrite, + 0, + nullptr, + nullptr, + 0); + if(! bSuccess) + { + ec.assign(static_cast( + boost::detail::winapi::GetLastError()), + system_category()); + return; + } + r.pos_ += nNumberOfBytesToWrite; + BOOST_ASSERT(r.pos_ <= r.body_.last_); + if(r.pos_ < r.body_.last_) + { + ec.assign(0, ec.category()); + } + else + { + sr.next(ec, detail::null_lambda{}); + BOOST_ASSERT(! ec); + BOOST_ASSERT(sr.is_done()); + if(! sr.keep_alive()) + ec = error::end_of_stream; + } +} + +#if BOOST_ASIO_HAS_WINDOWS_OVERLAPPED_PTR + +template< + class Protocol, + bool isRequest, class Fields, class Decorator, + class WriteHandler> +async_return_type +async_write_some( + boost::asio::basic_stream_socket& sock, + serializer, + Fields, Decorator>& sr, + WriteHandler&& handler) +{ + async_completion init{handler}; + detail::write_some_win32_op, isRequest, Fields, + Decorator>{init.completion_handler, sock, sr}(); + return init.result.get(); +} + +#endif + +} // http +} // beast + +#endif + +#endif diff --git a/src/beast/include/beast/http/impl/message.ipp b/src/beast/include/beast/http/impl/message.ipp index b15b89d649..68af2345e9 100644 --- a/src/beast/include/beast/http/impl/message.ipp +++ b/src/beast/include/beast/http/impl/message.ipp @@ -9,43 +9,393 @@ #define BEAST_HTTP_IMPL_MESSAGE_IPP #include -#include -#include -#include #include #include -#include +#include #include namespace beast { namespace http { template -void -swap( - header& m1, - header& m2) +template +header:: +header(Arg1&& arg1, ArgN&&... argn) + : Fields(std::forward(arg1), + std::forward(argn)...) { - using std::swap; - swap(m1.version, m2.version); - swap(m1.method, m2.method); - swap(m1.url, m2.url); - swap(m1.fields, m2.fields); +} + +template +inline +verb +header:: +method() const +{ + return method_; +} + +template +void +header:: +method(verb v) +{ + if(v == verb::unknown) + BOOST_THROW_EXCEPTION( + std::invalid_argument{"unknown method"}); + method_ = v; + this->set_method_impl({}); +} + +template +string_view +header:: +method_string() const +{ + if(method_ != verb::unknown) + return to_string(method_); + return this->get_method_impl(); +} + +template +void +header:: +method_string(string_view s) +{ + method_ = string_to_verb(s); + if(method_ != verb::unknown) + this->set_method_impl({}); + else + this->set_method_impl(s); +} + +template +inline +string_view +header:: +target() const +{ + return this->get_target_impl(); +} + +template +inline +void +header:: +target(string_view s) +{ + this->set_target_impl(s); } template void swap( - header& a, - header& b) + header& h1, + header& h2) { using std::swap; - swap(a.version, b.version); - swap(a.status, b.status); - swap(a.reason, b.reason); - swap(a.fields, b.fields); + swap( + static_cast(h1), + static_cast(h2)); + swap(h1.version, h2.version); + swap(h1.method_, h2.method_); } +//------------------------------------------------------------------------------ + +template +template +header:: +header(Arg1&& arg1, ArgN&&... argn) + : Fields(std::forward(arg1), + std::forward(argn)...) +{ +} + +#if 0 +template +template +header:: +header(status result, unsigned version_, Args&&... args) + : Fields(std::forward(args)...) + , version(version_) + , result_(result) +{ +} +#endif + +template +inline +status +header:: +result() const +{ + return int_to_status( + static_cast(result_)); +} + +template +inline +void +header:: +result(status v) +{ + result_ = v; +} + +template +inline +void +header:: +result(unsigned v) +{ + if(v > 999) + BOOST_THROW_EXCEPTION( + std::invalid_argument{ + "invalid status-code"}); + result_ = static_cast(v); +} + +template +inline +unsigned +header:: +result_int() const +{ + return static_cast(result_); +} + +template +string_view +header:: +reason() const +{ + auto const s = this->get_reason_impl(); + if(! s.empty()) + return s; + return obsolete_reason(result_); +} + +template +inline +void +header:: +reason(string_view s) +{ + this->set_reason_impl(s); +} + +template +void +swap( + header& h1, + header& h2) +{ + using std::swap; + swap( + static_cast(h1), + static_cast(h2)); + swap(h1.version, h2.version); + swap(h1.result_, h2.result_); +} + +//------------------------------------------------------------------------------ + +template +template +message:: +message(header_type&& h, BodyArgs&&... body_args) + : header_type(std::move(h)) + , body(std::forward(body_args)...) +{ +} + +template +template +message:: +message(header_type const& h, BodyArgs&&... body_args) + : header_type(h) + , body(std::forward(body_args)...) +{ +} + +template +template +message:: +message(verb method, string_view target, Version version) + : header_type(method, target, version) +{ +} + +template +template +message:: +message(verb method, string_view target, + Version version, BodyArg&& body_arg) + : header_type(method, target, version) + , body(std::forward(body_arg)) +{ +} + +template +template +message:: +message( + verb method, string_view target, Version version, + BodyArg&& body_arg, + FieldsArg&& fields_arg) + : header_type(method, target, version, + std::forward(fields_arg)) + , body(std::forward(body_arg)) +{ +} + +template +template +message:: +message(status result, Version version) + : header_type(result, version) +{ +} + +template +template +message:: +message(status result, Version version, + BodyArg&& body_arg) + : header_type(result, version) + , body(std::forward(body_arg)) +{ +} + +template +template +message:: +message(status result, Version version, + BodyArg&& body_arg, FieldsArg&& fields_arg) + : header_type(result, version, + std::forward(fields_arg)) + , body(std::forward(body_arg)) +{ +} + +template +message:: +message(std::piecewise_construct_t) +{ +} + +template +template +message:: +message(std::piecewise_construct_t, + std::tuple body_args) + : message(std::piecewise_construct, + body_args, + beast::detail::make_index_sequence< + sizeof...(BodyArgs)>{}) +{ +} + +template +template +message:: +message(std::piecewise_construct_t, + std::tuple body_args, + std::tuple fields_args) + : message(std::piecewise_construct, + body_args, + fields_args, + beast::detail::make_index_sequence< + sizeof...(BodyArgs)>{}, + beast::detail::make_index_sequence< + sizeof...(FieldsArgs)>{}) +{ +} + +template +void +message:: +chunked(bool value) +{ + this->set_chunked_impl(value); + this->set_content_length_impl(boost::none); +} + +template +void +message:: +content_length( + boost::optional const& value) +{ + this->set_content_length_impl(value); + this->set_chunked_impl(false); +} + +template +boost::optional +message:: +payload_size() const +{ + return payload_size(detail::is_body_sized{}); +} + +template +void +message:: +prepare_payload(std::true_type) +{ + auto const n = payload_size(); + if(this->method() == verb::trace && (! n || *n > 0)) + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "invalid request body"}); + if(n) + { + if(*n > 0 || + this->method() == verb::options || + this->method() == verb::put || + this->method() == verb::post) + { + this->content_length(n); + } + else + { + this->chunked(false); + } + } + else if(this->version >= 11) + { + this->chunked(true); + } + else + { + this->chunked(false); + } +} + +template +void +message:: +prepare_payload(std::false_type) +{ + auto const n = payload_size(); + if((status_class(this->result()) == status_class::informational || + this->result() == status::no_content || + this->result() == status::not_modified)) + { + if(! n || *n > 0) + // The response body MUST BE empty for this case + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "invalid response body"}); + } + if(n) + this->content_length(n); + else + this->chunked(true); +} + +//------------------------------------------------------------------------------ + template void swap( @@ -53,213 +403,12 @@ swap( message& m2) { using std::swap; - swap(m1.base(), m2.base()); + swap( + static_cast&>(m1), + static_cast&>(m2)); swap(m1.body, m2.body); } -template -bool -is_keep_alive(header const& msg) -{ - BOOST_ASSERT(msg.version == 10 || msg.version == 11); - if(msg.version == 11) - { - if(token_list{msg.fields["Connection"]}.exists("close")) - return false; - return true; - } - if(token_list{msg.fields["Connection"]}.exists("keep-alive")) - return true; - return false; -} - -template -bool -is_upgrade(header const& msg) -{ - BOOST_ASSERT(msg.version == 10 || msg.version == 11); - if(msg.version == 10) - return false; - if(token_list{msg.fields["Connection"]}.exists("upgrade")) - return true; - return false; -} - -namespace detail { - -struct prepare_info -{ - boost::optional connection_value; - boost::optional content_length; -}; - -template -inline -void -prepare_options(prepare_info& pi, - message& msg) -{ - beast::detail::ignore_unused(pi, msg); -} - -template -void -prepare_option(prepare_info& pi, - message& msg, - connection value) -{ - beast::detail::ignore_unused(msg); - pi.connection_value = value; -} - -template< - bool isRequest, class Body, class Fields, - class Opt, class... Opts> -void -prepare_options(prepare_info& pi, - message& msg, - Opt&& opt, Opts&&... opts) -{ - prepare_option(pi, msg, opt); - prepare_options(pi, msg, - std::forward(opts)...); -} - -template -void -prepare_content_length(prepare_info& pi, - message const& msg, - std::true_type) -{ - typename Body::writer w(msg); - // VFALCO This is a design problem! - error_code ec; - w.init(ec); - if(ec) - throw system_error{ec}; - pi.content_length = w.content_length(); -} - -template -void -prepare_content_length(prepare_info& pi, - message const& msg, - std::false_type) -{ - beast::detail::ignore_unused(msg); - pi.content_length = boost::none; -} - -} // detail - -template< - bool isRequest, class Body, class Fields, - class... Options> -void -prepare(message& msg, - Options&&... options) -{ - using beast::detail::make_exception; - - // VFALCO TODO - static_assert(is_Body::value, - "Body requirements not met"); - static_assert(has_writer::value, - "Body has no writer"); - static_assert(is_Writer>::value, - "Writer requirements not met"); - detail::prepare_info pi; - detail::prepare_content_length(pi, msg, - detail::has_content_length{}); - detail::prepare_options(pi, msg, - std::forward(options)...); - - if(msg.fields.exists("Connection")) - throw make_exception( - "prepare called with Connection field set", __FILE__, __LINE__); - - if(msg.fields.exists("Content-Length")) - throw make_exception( - "prepare called with Content-Length field set", __FILE__, __LINE__); - - if(token_list{msg.fields["Transfer-Encoding"]}.exists("chunked")) - throw make_exception( - "prepare called with Transfer-Encoding: chunked set", __FILE__, __LINE__); - - if(pi.connection_value != connection::upgrade) - { - if(pi.content_length) - { - struct set_field - { - void - operator()(message& msg, - detail::prepare_info const& pi) const - { - using beast::detail::ci_equal; - if(*pi.content_length > 0 || - ci_equal(msg.method, "POST")) - { - msg.fields.insert( - "Content-Length", *pi.content_length); - } - } - - void - operator()(message& msg, - detail::prepare_info const& pi) const - { - if((msg.status / 100 ) != 1 && - msg.status != 204 && - msg.status != 304) - { - msg.fields.insert( - "Content-Length", *pi.content_length); - } - } - }; - set_field{}(msg, pi); - } - else if(msg.version >= 11) - { - msg.fields.insert("Transfer-Encoding", "chunked"); - } - } - - auto const content_length = - msg.fields.exists("Content-Length"); - - if(pi.connection_value) - { - switch(*pi.connection_value) - { - case connection::upgrade: - msg.fields.insert("Connection", "upgrade"); - break; - - case connection::keep_alive: - if(msg.version < 11) - { - if(content_length) - msg.fields.insert("Connection", "keep-alive"); - } - break; - - case connection::close: - if(msg.version >= 11) - msg.fields.insert("Connection", "close"); - break; - } - } - - // rfc7230 6.7. - if(msg.version < 11 && token_list{ - msg.fields["Connection"]}.exists("upgrade")) - throw make_exception( - "invalid version for Connection: upgrade", __FILE__, __LINE__); -} - } // http } // beast diff --git a/src/beast/include/beast/http/impl/parse.ipp b/src/beast/include/beast/http/impl/parse.ipp deleted file mode 100644 index 17219fc30a..0000000000 --- a/src/beast/include/beast/http/impl/parse.ipp +++ /dev/null @@ -1,302 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_HTTP_IMPL_PARSE_IPP_HPP -#define BEAST_HTTP_IMPL_PARSE_IPP_HPP - -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace http { - -namespace detail { - -template -class parse_op -{ - struct data - { - bool cont; - Stream& s; - DynamicBuffer& db; - Parser& p; - bool got_some = false; - int state = 0; - - data(Handler& handler, Stream& s_, - DynamicBuffer& sb_, Parser& p_) - : cont(beast_asio_helpers:: - is_continuation(handler)) - , s(s_) - , db(sb_) - , p(p_) - { - BOOST_ASSERT(! p.complete()); - } - }; - - handler_ptr d_; - -public: - parse_op(parse_op&&) = default; - parse_op(parse_op const&) = default; - - template - parse_op(DeducedHandler&& h, Stream& s, Args&&... args) - : d_(std::forward(h), - s, std::forward(args)...) - { - (*this)(error_code{}, 0, false); - } - - void - operator()(error_code ec, - std::size_t bytes_transferred, bool again = true); - - friend - void* asio_handler_allocate( - std::size_t size, parse_op* op) - { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); - } - - friend - void asio_handler_deallocate( - void* p, std::size_t size, parse_op* op) - { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); - } - - friend - bool asio_handler_is_continuation(parse_op* op) - { - return op->d_->cont; - } - - template - friend - void asio_handler_invoke(Function&& f, parse_op* op) - { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); - } -}; - -template -void -parse_op:: -operator()(error_code ec, std::size_t bytes_transferred, bool again) -{ - auto& d = *d_; - d.cont = d.cont || again; - while(d.state != 99) - { - switch(d.state) - { - case 0: - { - // Parse any bytes left over in the buffer - auto const used = - d.p.write(d.db.data(), ec); - if(ec) - { - // call handler - d.state = 99; - d.s.get_io_service().post( - bind_handler(std::move(*this), ec, 0)); - return; - } - if(used > 0) - { - d.got_some = true; - d.db.consume(used); - } - if(d.p.complete()) - { - // call handler - d.state = 99; - d.s.get_io_service().post( - bind_handler(std::move(*this), ec, 0)); - return; - } - // Buffer must be empty, - // otherwise parse should be complete - BOOST_ASSERT(d.db.size() == 0); - d.state = 1; - break; - } - - case 1: - { - // read - d.state = 2; - auto const size = - read_size_helper(d.db, 65536); - BOOST_ASSERT(size > 0); - d.s.async_read_some( - d.db.prepare(size), std::move(*this)); - return; - } - - // got data - case 2: - { - if(ec == boost::asio::error::eof) - { - // If we haven't processed any bytes, - // give the eof to the handler immediately. - if(! d.got_some) - { - // call handler - d.state = 99; - break; - } - // Feed the eof to the parser to complete - // the parse, and call the handler. The - // next call to parse will deliver the eof. - ec = {}; - d.p.write_eof(ec); - BOOST_ASSERT(ec || d.p.complete()); - // call handler - d.state = 99; - break; - } - if(ec) - { - // call handler - d.state = 99; - break; - } - BOOST_ASSERT(bytes_transferred > 0); - d.db.commit(bytes_transferred); - auto const used = d.p.write(d.db.data(), ec); - if(ec) - { - // call handler - d.state = 99; - break; - } - // The parser must either consume - // bytes or generate an error. - BOOST_ASSERT(used > 0); - d.got_some = true; - d.db.consume(used); - if(d.p.complete()) - { - // call handler - d.state = 99; - break; - } - // If the parse is not complete, - // all input must be consumed. - BOOST_ASSERT(used == bytes_transferred); - d.state = 1; - break; - } - } - } - d_.invoke(ec); -} - -} // detail - -//------------------------------------------------------------------------------ - -template -void -parse(SyncReadStream& stream, - DynamicBuffer& dynabuf, Parser& parser) -{ - static_assert(is_SyncReadStream::value, - "SyncReadStream requirements not met"); - static_assert(is_DynamicBuffer::value, - "DynamicBuffer requirements not met"); - static_assert(is_Parser::value, - "Parser requirements not met"); - error_code ec; - parse(stream, dynabuf, parser, ec); - if(ec) - throw system_error{ec}; -} - -template -void -parse(SyncReadStream& stream, DynamicBuffer& dynabuf, - Parser& parser, error_code& ec) -{ - static_assert(is_SyncReadStream::value, - "SyncReadStream requirements not met"); - static_assert(is_DynamicBuffer::value, - "DynamicBuffer requirements not met"); - static_assert(is_Parser::value, - "Parser requirements not met"); - bool got_some = false; - for(;;) - { - auto used = - parser.write(dynabuf.data(), ec); - if(ec) - return; - dynabuf.consume(used); - if(used > 0) - got_some = true; - if(parser.complete()) - break; - dynabuf.commit(stream.read_some( - dynabuf.prepare(read_size_helper( - dynabuf, 65536)), ec)); - if(ec && ec != boost::asio::error::eof) - return; - if(ec == boost::asio::error::eof) - { - if(! got_some) - return; - // Caller will see eof on next read. - ec = {}; - parser.write_eof(ec); - if(ec) - return; - BOOST_ASSERT(parser.complete()); - break; - } - } -} - -template -typename async_completion< - ReadHandler, void(error_code)>::result_type -async_parse(AsyncReadStream& stream, - DynamicBuffer& dynabuf, Parser& parser, ReadHandler&& handler) -{ - static_assert(is_AsyncReadStream::value, - "AsyncReadStream requirements not met"); - static_assert(is_DynamicBuffer::value, - "DynamicBuffer requirements not met"); - static_assert(is_Parser::value, - "Parser requirements not met"); - beast::async_completion completion{handler}; - detail::parse_op{ - completion.handler, stream, dynabuf, parser}; - return completion.result.get(); -} - -} // http -} // beast - -#endif diff --git a/src/beast/include/beast/http/impl/parse_error.ipp b/src/beast/include/beast/http/impl/parse_error.ipp deleted file mode 100644 index d7943a0e9b..0000000000 --- a/src/beast/include/beast/http/impl/parse_error.ipp +++ /dev/null @@ -1,105 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_HTTP_IMPL_PARSE_ERROR_IPP -#define BEAST_HTTP_IMPL_PARSE_ERROR_IPP - -namespace boost { -namespace system { -template<> -struct is_error_code_enum -{ - static bool const value = true; -}; -} // system -} // boost - -namespace beast { -namespace http { -namespace detail { - -class parse_error_category : public error_category -{ -public: - const char* - name() const noexcept override - { - return "http"; - } - - std::string - message(int ev) const override - { - switch(static_cast(ev)) - { - case parse_error::connection_closed: return "data after Connection close"; - case parse_error::bad_method: return "bad method"; - case parse_error::bad_uri: return "bad request-target"; - case parse_error::bad_version: return "bad HTTP-Version"; - case parse_error::bad_crlf: return "missing CRLF"; - case parse_error::bad_status: return "bad status-code"; - case parse_error::bad_reason: return "bad reason-phrase"; - case parse_error::bad_field: return "bad field token"; - case parse_error::bad_value: return "bad field-value"; - case parse_error::bad_content_length: return "bad Content-Length"; - case parse_error::illegal_content_length: return "illegal Content-Length with chunked Transfer-Encoding"; - case parse_error::invalid_chunk_size: return "invalid chunk size"; - case parse_error::invalid_ext_name: return "invalid ext name"; - case parse_error::invalid_ext_val: return "invalid ext val"; - case parse_error::header_too_big: return "header size limit exceeded"; - case parse_error::body_too_big: return "body size limit exceeded"; - default: - case parse_error::short_read: return "unexpected end of data"; - } - } - - error_condition - default_error_condition(int ev) const noexcept override - { - return error_condition{ev, *this}; - } - - bool - equivalent(int ev, - error_condition const& condition - ) const noexcept override - { - return condition.value() == ev && - &condition.category() == this; - } - - bool - equivalent(error_code const& error, int ev) const noexcept override - { - return error.value() == ev && - &error.category() == this; - } -}; - -inline -error_category const& -get_parse_error_category() -{ - static parse_error_category const cat{}; - return cat; -} - -} // detail - -inline -error_code -make_error_code(parse_error ev) -{ - return error_code{ - static_cast::type>(ev), - detail::get_parse_error_category()}; -} - -} // http -} // beast - -#endif diff --git a/src/beast/include/beast/http/impl/parser.ipp b/src/beast/include/beast/http/impl/parser.ipp new file mode 100644 index 0000000000..73bacd9ad7 --- /dev/null +++ b/src/beast/include/beast/http/impl/parser.ipp @@ -0,0 +1,51 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_IMPL_PARSER_IPP +#define BEAST_HTTP_IMPL_PARSER_IPP + +#include +#include + +namespace beast { +namespace http { + +template +parser:: +parser() + : wr_(m_) +{ +} + +template +template +parser:: +parser(Arg1&& arg1, ArgN&&... argn) + : m_(std::forward(arg1), + std::forward(argn)...) + , wr_(m_) +{ +} + +template +template +parser:: +parser(parser&& p, + Args&&... args) + : base_type(std::move(p)) + , m_(p.release(), std::forward(args)...) + , wr_(m_) +{ + if(wr_inited_) + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "moved-from parser has a body"}); +} + +} // http +} // beast + +#endif diff --git a/src/beast/include/beast/http/impl/read.ipp b/src/beast/include/beast/http/impl/read.ipp index 3bcf27c7b8..08e7c552d3 100644 --- a/src/beast/include/beast/http/impl/read.ipp +++ b/src/beast/include/beast/http/impl/read.ipp @@ -8,379 +8,767 @@ #ifndef BEAST_HTTP_IMPL_READ_IPP_HPP #define BEAST_HTTP_IMPL_READ_IPP_HPP -#include -#include -#include -#include +#include +#include +#include +#include #include -#include #include -#include +#include +#include +#include +#include +#include #include +#include +#include +#include namespace beast { namespace http { namespace detail { +//------------------------------------------------------------------------------ + template -class read_header_op + bool isRequest, class Derived, class Handler> +class read_some_op { - using parser_type = - header_parser_v1; - - using message_type = - header; - - struct data - { - bool cont; - Stream& s; - DynamicBuffer& db; - message_type& m; - parser_type p; - bool started = false; - int state = 0; - - data(Handler& handler, Stream& s_, - DynamicBuffer& sb_, message_type& m_) - : cont(beast_asio_helpers:: - is_continuation(handler)) - , s(s_) - , db(sb_) - , m(m_) - { - } - }; - - handler_ptr d_; + int state_ = 0; + Stream& s_; + DynamicBuffer& b_; + basic_parser& p_; + boost::optional mb_; + Handler h_; public: - read_header_op(read_header_op&&) = default; - read_header_op(read_header_op const&) = default; + read_some_op(read_some_op&&) = default; + read_some_op(read_some_op const&) = default; - template - read_header_op( - DeducedHandler&& h, Stream& s, Args&&... args) - : d_(std::forward(h), - s, std::forward(args)...) + template + read_some_op(DeducedHandler&& h, Stream& s, + DynamicBuffer& b, basic_parser& p) + : s_(s) + , b_(b) + , p_(p) + , h_(std::forward(h)) { - (*this)(error_code{}, false); } void - operator()(error_code ec, bool again = true); + operator()(error_code ec, std::size_t bytes_transferred); friend void* asio_handler_allocate( - std::size_t size, read_header_op* op) + std::size_t size, read_some_op* op) { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); + using boost::asio::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(op->h_)); } friend void asio_handler_deallocate( - void* p, std::size_t size, read_header_op* op) + void* p, std::size_t size, read_some_op* op) { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->h_)); } friend - bool asio_handler_is_continuation(read_header_op* op) + bool asio_handler_is_continuation(read_some_op* op) { - return op->d_->cont; + using boost::asio::asio_handler_is_continuation; + return op->state_ >= 2 ? true : + asio_handler_is_continuation( + std::addressof(op->h_)); } template friend - void asio_handler_invoke(Function&& f, read_header_op* op) + void asio_handler_invoke(Function&& f, read_some_op* op) { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->h_)); } }; template + bool isRequest, class Derived, class Handler> void -read_header_op:: -operator()(error_code ec, bool again) +read_some_op:: +operator()(error_code ec, std::size_t bytes_transferred) { - auto& d = *d_; - d.cont = d.cont || again; - while(! ec && d.state != 99) + switch(state_) { - switch(d.state) + case 0: + state_ = 1; + if(b_.size() == 0) + goto do_read; + goto do_parse; + + case 1: + state_ = 2; + case 2: + if(ec == boost::asio::error::eof) { - case 0: - d.state = 1; - async_parse(d.s, d.db, d.p, std::move(*this)); - return; - - case 1: - // call handler - d.state = 99; - d.m = d.p.release(); - break; + BOOST_ASSERT(bytes_transferred == 0); + if(p_.got_some()) + { + // caller sees EOF on next read + ec.assign(0, ec.category()); + p_.put_eof(ec); + if(ec) + goto upcall; + BOOST_ASSERT(p_.is_done()); + goto upcall; + } + ec = error::end_of_stream; + goto upcall; } + if(ec) + goto upcall; + b_.commit(bytes_transferred); + + do_parse: + b_.consume(p_.put(b_.data(), ec)); + if(! ec || ec != http::error::need_more) + goto do_upcall; + ec.assign(0, ec.category()); + + do_read: + try + { + mb_.emplace(b_.prepare( + read_size_or_throw(b_, 65536))); + } + catch(std::length_error const&) + { + ec = error::buffer_overflow; + goto do_upcall; + } + return s_.async_read_some(*mb_, std::move(*this)); + + do_upcall: + if(state_ >= 2) + goto upcall; + state_ = 3; + return s_.get_io_service().post( + bind_handler(std::move(*this), ec, 0)); + + case 3: + break; } - d_.invoke(ec); -} - -} // detail - -template -void -read(SyncReadStream& stream, DynamicBuffer& dynabuf, - header& msg) -{ - static_assert(is_SyncReadStream::value, - "SyncReadStream requirements not met"); - static_assert(is_DynamicBuffer::value, - "DynamicBuffer requirements not met"); - error_code ec; - beast::http::read(stream, dynabuf, msg, ec); - if(ec) - throw system_error{ec}; -} - -template -void -read(SyncReadStream& stream, DynamicBuffer& dynabuf, - header& m, - error_code& ec) -{ - static_assert(is_SyncReadStream::value, - "SyncReadStream requirements not met"); - static_assert(is_DynamicBuffer::value, - "DynamicBuffer requirements not met"); - header_parser_v1 p; - beast::http::parse(stream, dynabuf, p, ec); - if(ec) - return; - BOOST_ASSERT(p.complete()); - m = p.release(); -} - -template -typename async_completion< - ReadHandler, void(error_code)>::result_type -async_read(AsyncReadStream& stream, DynamicBuffer& dynabuf, - header& m, - ReadHandler&& handler) -{ - static_assert(is_AsyncReadStream::value, - "AsyncReadStream requirements not met"); - static_assert(is_DynamicBuffer::value, - "DynamicBuffer requirements not met"); - beast::async_completion completion{handler}; - detail::read_header_op{completion.handler, - stream, dynabuf, m}; - return completion.result.get(); +upcall: + h_(ec); } //------------------------------------------------------------------------------ -namespace detail { +struct parser_is_done +{ + template + bool + operator()(basic_parser< + isRequest, Derived> const& p) const + { + return p.is_done(); + } +}; + +struct parser_is_header_done +{ + template + bool + operator()(basic_parser< + isRequest, Derived> const& p) const + { + return p.is_header_done(); + } +}; template class read_op { - using parser_type = - parser_v1; - - using message_type = - message; - - struct data - { - bool cont; - Stream& s; - DynamicBuffer& db; - message_type& m; - parser_type p; - bool started = false; - int state = 0; - - data(Handler& handler, Stream& s_, - DynamicBuffer& sb_, message_type& m_) - : cont(beast_asio_helpers:: - is_continuation(handler)) - , s(s_) - , db(sb_) - , m(m_) - { - } - }; - - handler_ptr d_; + int state_ = 0; + Stream& s_; + DynamicBuffer& b_; + basic_parser& p_; + Handler h_; public: read_op(read_op&&) = default; read_op(read_op const&) = default; - template - read_op(DeducedHandler&& h, Stream& s, Args&&... args) - : d_(std::forward(h), - s, std::forward(args)...) + template + read_op(DeducedHandler&& h, Stream& s, + DynamicBuffer& b, basic_parser& p) + : s_(s) + , b_(b) + , p_(p) + , h_(std::forward(h)) { - (*this)(error_code{}, false); } void - operator()(error_code ec, bool again = true); + operator()(error_code ec); friend void* asio_handler_allocate( std::size_t size, read_op* op) { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); + using boost::asio::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(op->h_)); } friend void asio_handler_deallocate( void* p, std::size_t size, read_op* op) { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->h_)); } friend bool asio_handler_is_continuation(read_op* op) { - return op->d_->cont; + using boost::asio::asio_handler_is_continuation; + return op->state_ >= 3 ? true : + asio_handler_is_continuation( + std::addressof(op->h_)); } template friend void asio_handler_invoke(Function&& f, read_op* op) { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->h_)); } }; template void -read_op:: -operator()(error_code ec, bool again) +read_op:: +operator()(error_code ec) +{ + switch(state_) + { + case 0: + if(Condition{}(p_)) + { + state_ = 1; + return s_.get_io_service().post( + bind_handler(std::move(*this), ec)); + } + state_ = 2; + + do_read: + return async_read_some( + s_, b_, p_, std::move(*this)); + + case 1: + goto upcall; + + case 2: + case 3: + if(ec) + goto upcall; + if(Condition{}(p_)) + goto upcall; + state_ = 3; + goto do_read; + } +upcall: + h_(ec); +} + +//------------------------------------------------------------------------------ + +template +class read_msg_op +{ + using parser_type = + parser; + + using message_type = + typename parser_type::value_type; + + struct data + { + int state = 0; + Stream& s; + DynamicBuffer& b; + message_type& m; + parser_type p; + + data(Handler&, Stream& s_, + DynamicBuffer& b_, message_type& m_) + : s(s_) + , b(b_) + , m(m_) + , p(std::move(m)) + { + p.eager(true); + } + }; + + handler_ptr d_; + +public: + read_msg_op(read_msg_op&&) = default; + read_msg_op(read_msg_op const&) = default; + + template + read_msg_op(DeducedHandler&& h, Stream& s, Args&&... args) + : d_(std::forward(h), + s, std::forward(args)...) + { + } + + void + operator()(error_code ec); + + friend + void* asio_handler_allocate( + std::size_t size, read_msg_op* op) + { + using boost::asio::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(op->d_.handler())); + } + + friend + void asio_handler_deallocate( + void* p, std::size_t size, read_msg_op* op) + { + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->d_.handler())); + } + + friend + bool asio_handler_is_continuation(read_msg_op* op) + { + using boost::asio::asio_handler_is_continuation; + return op->d_->state >= 2 ? true : + asio_handler_is_continuation( + std::addressof(op->d_.handler())); + } + + template + friend + void asio_handler_invoke(Function&& f, read_msg_op* op) + { + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->d_.handler())); + } +}; + +template +void +read_msg_op:: +operator()(error_code ec) { auto& d = *d_; - d.cont = d.cont || again; - while(! ec && d.state != 99) + switch(d.state) { - switch(d.state) - { - case 0: - d.state = 1; - async_parse(d.s, d.db, d.p, std::move(*this)); - return; + case 0: + d.state = 1; - case 1: - // call handler - d.state = 99; + do_read: + return async_read_some( + d.s, d.b, d.p, std::move(*this)); + + case 1: + case 2: + if(ec) + goto upcall; + if(d.p.is_done()) + { d.m = d.p.release(); - break; + goto upcall; } + d.state = 2; + goto do_read; } +upcall: d_.invoke(ec); } } // detail -template +//------------------------------------------------------------------------------ + +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> void -read(SyncReadStream& stream, DynamicBuffer& dynabuf, - message& msg) +read_some( + SyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser) { - static_assert(is_SyncReadStream::value, + static_assert(is_sync_read_stream::value, "SyncReadStream requirements not met"); - static_assert(is_DynamicBuffer::value, + static_assert(is_dynamic_buffer::value, "DynamicBuffer requirements not met"); - static_assert(is_Body::value, - "Body requirements not met"); - static_assert(has_reader::value, - "Body has no reader"); - static_assert(is_Reader>::value, - "Reader requirements not met"); + BOOST_ASSERT(! parser.is_done()); error_code ec; - beast::http::read(stream, dynabuf, msg, ec); + read_some(stream, buffer, parser, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); } -template +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> void -read(SyncReadStream& stream, DynamicBuffer& dynabuf, - message& m, - error_code& ec) +read_some( + SyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser, + error_code& ec) { - static_assert(is_SyncReadStream::value, + static_assert(is_sync_read_stream::value, "SyncReadStream requirements not met"); - static_assert(is_DynamicBuffer::value, + static_assert(is_dynamic_buffer::value, "DynamicBuffer requirements not met"); - static_assert(is_Body::value, + BOOST_ASSERT(! parser.is_done()); + if(buffer.size() == 0) + goto do_read; + for(;;) + { + // invoke parser + buffer.consume(parser.put(buffer.data(), ec)); + if(! ec) + break; + if(ec != http::error::need_more) + break; + do_read: + boost::optional b; + try + { + b.emplace(buffer.prepare( + read_size_or_throw(buffer, 65536))); + } + catch(std::length_error const&) + { + ec = error::buffer_overflow; + return; + } + auto const bytes_transferred = + stream.read_some(*b, ec); + if(ec == boost::asio::error::eof) + { + BOOST_ASSERT(bytes_transferred == 0); + if(parser.got_some()) + { + // caller sees EOF on next read + parser.put_eof(ec); + if(ec) + break; + BOOST_ASSERT(parser.is_done()); + break; + } + ec = error::end_of_stream; + break; + } + if(ec) + break; + buffer.commit(bytes_transferred); + } +} + +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived, + class ReadHandler> +async_return_type< + ReadHandler, void(error_code)> +async_read_some( + AsyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser, + ReadHandler&& handler) +{ + static_assert(is_async_read_stream::value, + "AsyncReadStream requirements not met"); + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + BOOST_ASSERT(! parser.is_done()); + async_completion init{handler}; + detail::read_some_op>{ + init.completion_handler, stream, buffer, parser}( + error_code{}, 0); + return init.result.get(); +} + +//------------------------------------------------------------------------------ + +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> +void +read_header( + SyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser) +{ + static_assert(is_sync_read_stream::value, + "SyncReadStream requirements not met"); + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + error_code ec; + read_header(stream, buffer, parser, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); +} + +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> +void +read_header( + SyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser, + error_code& ec) +{ + static_assert(is_sync_read_stream::value, + "SyncReadStream requirements not met"); + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + parser.eager(false); + if(parser.is_header_done()) + { + ec.assign(0, ec.category()); + return; + } + do + { + read_some(stream, buffer, parser, ec); + if(ec) + return; + } + while(! parser.is_header_done()); +} + +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived, + class ReadHandler> +async_return_type +async_read_header( + AsyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser, + ReadHandler&& handler) +{ + static_assert(is_async_read_stream::value, + "AsyncReadStream requirements not met"); + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + parser.eager(false); + async_completion init{handler}; + detail::read_op>{ + init.completion_handler, stream, buffer, parser}( + error_code{}); + return init.result.get(); +} + +//------------------------------------------------------------------------------ + +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> +void +read( + SyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser) +{ + static_assert(is_sync_read_stream::value, + "SyncReadStream requirements not met"); + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + error_code ec; + read(stream, buffer, parser, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); +} + +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> +void +read( + SyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser, + error_code& ec) +{ + static_assert(is_sync_read_stream::value, + "SyncReadStream requirements not met"); + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + parser.eager(true); + if(parser.is_done()) + { + ec.assign(0, ec.category()); + return; + } + do + { + read_some(stream, buffer, parser, ec); + if(ec) + return; + } + while(! parser.is_done()); +} + +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived, + class ReadHandler> +async_return_type +async_read( + AsyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser, + ReadHandler&& handler) +{ + static_assert(is_async_read_stream::value, + "AsyncReadStream requirements not met"); + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + parser.eager(true); + async_completion init{handler}; + detail::read_op>{ + init.completion_handler, stream, buffer, parser}( + error_code{}); + return init.result.get(); +} + +//------------------------------------------------------------------------------ + +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Body, class Allocator> +void +read( + SyncReadStream& stream, + DynamicBuffer& buffer, + message>& msg) +{ + static_assert(is_sync_read_stream::value, + "SyncReadStream requirements not met"); + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + static_assert(is_body::value, "Body requirements not met"); - static_assert(has_reader::value, - "Body has no reader"); - static_assert(is_Reader>::value, - "Reader requirements not met"); - parser_v1 p; - beast::http::parse(stream, dynabuf, p, ec); + static_assert(is_body_writer::value, + "BodyWriter requirements not met"); + error_code ec; + read(stream, buffer, msg, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); +} + +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Body, class Allocator> +void +read( + SyncReadStream& stream, + DynamicBuffer& buffer, + message>& msg, + error_code& ec) +{ + static_assert(is_sync_read_stream::value, + "SyncReadStream requirements not met"); + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + static_assert(is_body::value, + "Body requirements not met"); + static_assert(is_body_writer::value, + "BodyWriter requirements not met"); + parser p{std::move(msg)}; + p.eager(true); + read(stream, buffer, p.base(), ec); if(ec) return; - BOOST_ASSERT(p.complete()); - m = p.release(); + msg = p.release(); } -template -typename async_completion< - ReadHandler, void(error_code)>::result_type -async_read(AsyncReadStream& stream, DynamicBuffer& dynabuf, - message& m, - ReadHandler&& handler) +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, class Body, class Allocator, + class ReadHandler> +async_return_type +async_read( + AsyncReadStream& stream, + DynamicBuffer& buffer, + message>& msg, + ReadHandler&& handler) { - static_assert(is_AsyncReadStream::value, + static_assert(is_async_read_stream::value, "AsyncReadStream requirements not met"); - static_assert(is_DynamicBuffer::value, + static_assert(is_dynamic_buffer::value, "DynamicBuffer requirements not met"); - static_assert(is_Body::value, + static_assert(is_body::value, "Body requirements not met"); - static_assert(has_reader::value, - "Body has no reader"); - static_assert(is_Reader>::value, - "Reader requirements not met"); - beast::async_completion completion{handler}; - detail::read_op{completion.handler, - stream, dynabuf, m}; - return completion.result.get(); + static_assert(is_body_writer::value, + "BodyWriter requirements not met"); + async_completion init{handler}; + detail::read_msg_op>{ + init.completion_handler, stream, buffer, msg}( + error_code{}); + return init.result.get(); } } // http diff --git a/src/beast/include/beast/http/impl/rfc7230.ipp b/src/beast/include/beast/http/impl/rfc7230.ipp index 5ad65c28d1..bce0ec856b 100644 --- a/src/beast/include/beast/http/impl/rfc7230.ipp +++ b/src/beast/include/beast/http/impl/rfc7230.ipp @@ -8,7 +8,6 @@ #ifndef BEAST_HTTP_IMPL_RFC7230_IPP #define BEAST_HTTP_IMPL_RFC7230_IPP -#include #include #include @@ -17,7 +16,7 @@ namespace http { class param_list::const_iterator { - using iter_type = boost::string_ref::const_iterator; + using iter_type = string_view::const_iterator; std::string s_; detail::param_iter pi_; @@ -87,7 +86,7 @@ private: template static std::string - unquote(boost::string_ref const& sr); + unquote(string_view sr); template void @@ -133,7 +132,7 @@ cend() const -> template std::string param_list::const_iterator:: -unquote(boost::string_ref const& sr) +unquote(string_view sr) { std::string s; s.reserve(sr.size()); @@ -165,7 +164,7 @@ increment() pi_.v.second.front() == '"') { s_ = unquote(pi_.v.second); - pi_.v.second = boost::string_ref{ + pi_.v.second = string_view{ s_.data(), s_.size()}; } } @@ -291,7 +290,7 @@ find(T const& s) -> return std::find_if(begin(), end(), [&s](value_type const& v) { - return beast::detail::ci_equal(s, v.first); + return iequals(s, v.first); }); } @@ -346,7 +345,7 @@ increment() if(! detail::is_tchar(*it_)) break; } - v_.first = boost::string_ref{&*p0, + v_.first = string_view{&*p0, static_cast(it_ - p0)}; detail::param_iter pi; pi.it = it_; @@ -358,7 +357,7 @@ increment() if(pi.empty()) break; } - v_.second = param_list{boost::string_ref{&*it_, + v_.second = param_list{string_view{&*it_, static_cast(pi.it - it_)}}; it_ = pi.it; return; @@ -518,7 +517,7 @@ increment() if(! detail::is_tchar(*it_)) break; } - v_ = boost::string_ref{&*p0, + v_ = string_view{&*p0, static_cast(it_ - p0)}; return; } @@ -537,11 +536,31 @@ exists(T const& s) return std::find_if(begin(), end(), [&s](value_type const& v) { - return beast::detail::ci_equal(s, v); + return iequals(s, v); } ) != end(); } +template +bool +validate_list(detail::basic_parsed_list< + Policy> const& list) +{ + auto const last = list.end(); + auto it = list.begin(); + if(it.error()) + return false; + while(it != last) + { + ++it; + if(it.error()) + return false; + if(it == last) + break; + } + return true; +} + } // http } // beast diff --git a/src/beast/include/beast/http/impl/serializer.ipp b/src/beast/include/beast/http/impl/serializer.ipp new file mode 100644 index 0000000000..cc288f683a --- /dev/null +++ b/src/beast/include/beast/http/impl/serializer.ipp @@ -0,0 +1,496 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_IMPL_SERIALIZER_IPP +#define BEAST_HTTP_IMPL_SERIALIZER_IPP + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +template +void +serializer:: +frdinit(std::true_type) +{ + frd_.emplace(m_, m_.version, m_.method()); +} + +template +void +serializer:: +frdinit(std::false_type) +{ + frd_.emplace(m_, m_.version, m_.result_int()); +} + +template +template +inline +void +serializer:: +do_visit(error_code& ec, Visit& visit) +{ + // VFALCO work-around for missing variant::emplace + pv_.~variant(); + new(&pv_) decltype(pv_){ + T1{limit_, boost::get(v_)}}; + visit(ec, beast::detail::make_buffers_ref( + boost::get(pv_))); +} + +//------------------------------------------------------------------------------ + +template +serializer:: +serializer(value_type& m) + : m_(m) + , rd_(m_) +{ +} + +template +serializer:: +serializer(value_type& m, ChunkDecorator const& d) + : m_(m) + , rd_(m_) + , d_(d) +{ +} + +template +template +void +serializer:: +next(error_code& ec, Visit&& visit) +{ + using boost::asio::buffer_size; + using beast::detail::make_buffers_ref; + switch(s_) + { + case do_construct: + { + frdinit(std::integral_constant{}); + keep_alive_ = m_.keep_alive(); + chunked_ = m_.chunked(); + if(chunked_) + goto go_init_c; + s_ = do_init; + BEAST_FALLTHROUGH; + } + + case do_init: + { + rd_.init(ec); + if(ec) + return; + if(split_) + goto go_header_only; + auto result = rd_.get(ec); + if(ec == error::need_more) + goto go_header_only; + if(ec) + return; + if(! result) + goto go_header_only; + more_ = result->second; + v_ = cb2_t{ + boost::in_place_init, + frd_->get(), + result->first}; + s_ = do_header; + BEAST_FALLTHROUGH; + } + + case do_header: + do_visit(ec, visit); + break; + + go_header_only: + v_ = cb1_t{frd_->get()}; + s_ = do_header_only; + case do_header_only: + do_visit(ec, visit); + break; + + case do_body: + s_ = do_body + 1; + BEAST_FALLTHROUGH; + + case do_body + 1: + { + auto result = rd_.get(ec); + if(ec) + return; + if(! result) + goto go_complete; + more_ = result->second; + v_ = cb3_t{result->first}; + s_ = do_body + 2; + BEAST_FALLTHROUGH; + } + + case do_body + 2: + do_visit(ec, visit); + break; + + //---------------------------------------------------------------------- + + go_init_c: + s_ = do_init_c; + case do_init_c: + { + rd_.init(ec); + if(ec) + return; + if(split_) + goto go_header_only_c; + auto result = rd_.get(ec); + if(ec == error::need_more) + goto go_header_only_c; + if(ec) + return; + if(! result) + goto go_header_only_c; + more_ = result->second; + #ifndef BEAST_NO_BIG_VARIANTS + if(! more_) + { + // do it all in one buffer + v_ = cb7_t{ + boost::in_place_init, + frd_->get(), + detail::chunk_header{ + buffer_size(result->first)}, + [&]() + { + auto sv = d_(result->first); + return boost::asio::const_buffers_1{ + sv.data(), sv.size()}; + + }(), + detail::chunk_crlf(), + result->first, + detail::chunk_crlf(), + detail::chunk_final(), + [&]() + { + auto sv = d_( + boost::asio::null_buffers{}); + return boost::asio::const_buffers_1{ + sv.data(), sv.size()}; + + }(), + detail::chunk_crlf()}; + goto go_all_c; + } + #endif + v_ = cb4_t{ + boost::in_place_init, + frd_->get(), + detail::chunk_header{ + buffer_size(result->first)}, + [&]() + { + auto sv = d_(result->first); + return boost::asio::const_buffers_1{ + sv.data(), sv.size()}; + + }(), + detail::chunk_crlf(), + result->first, + detail::chunk_crlf()}; + s_ = do_header_c; + BEAST_FALLTHROUGH; + } + + case do_header_c: + do_visit(ec, visit); + break; + + go_header_only_c: + v_ = cb1_t{frd_->get()}; + s_ = do_header_only_c; + case do_header_only_c: + do_visit(ec, visit); + break; + + case do_body_c: + s_ = do_body_c + 1; + BEAST_FALLTHROUGH; + + case do_body_c + 1: + { + auto result = rd_.get(ec); + if(ec) + return; + if(! result) + goto go_final_c; + more_ = result->second; + #ifndef BEAST_NO_BIG_VARIANTS + if(! more_) + { + // do it all in one buffer + v_ = cb6_t{ + boost::in_place_init, + detail::chunk_header{ + buffer_size(result->first)}, + [&]() + { + auto sv = d_(result->first); + return boost::asio::const_buffers_1{ + sv.data(), sv.size()}; + + }(), + detail::chunk_crlf(), + result->first, + detail::chunk_crlf(), + detail::chunk_final(), + [&]() + { + auto sv = d_( + boost::asio::null_buffers{}); + return boost::asio::const_buffers_1{ + sv.data(), sv.size()}; + + }(), + detail::chunk_crlf()}; + goto go_body_final_c; + } + #endif + v_ = cb5_t{ + boost::in_place_init, + detail::chunk_header{ + buffer_size(result->first)}, + [&]() + { + auto sv = d_(result->first); + return boost::asio::const_buffers_1{ + sv.data(), sv.size()}; + + }(), + detail::chunk_crlf(), + result->first, + detail::chunk_crlf()}; + s_ = do_body_c + 2; + BEAST_FALLTHROUGH; + } + + case do_body_c + 2: + do_visit(ec, visit); + break; + +#ifndef BEAST_NO_BIG_VARIANTS + go_body_final_c: + s_ = do_body_final_c; + case do_body_final_c: + do_visit(ec, visit); + break; + + go_all_c: + s_ = do_all_c; + case do_all_c: + do_visit(ec, visit); + break; +#endif + + go_final_c: + case do_final_c: + v_ = cb8_t{ + boost::in_place_init, + detail::chunk_final(), + [&]() + { + auto sv = d_( + boost::asio::null_buffers{}); + return boost::asio::const_buffers_1{ + sv.data(), sv.size()}; + + }(), + detail::chunk_crlf()}; + s_ = do_final_c + 1; + BEAST_FALLTHROUGH; + + case do_final_c + 1: + do_visit(ec, visit); + break; + + //---------------------------------------------------------------------- + + default: + case do_complete: + BOOST_ASSERT(false); + break; + + go_complete: + s_ = do_complete; + break; + } +} + +template +void +serializer:: +consume(std::size_t n) +{ + using boost::asio::buffer_size; + switch(s_) + { + case do_header: + BOOST_ASSERT(n <= buffer_size( + boost::get(v_))); + boost::get(v_).consume(n); + if(buffer_size(boost::get(v_)) > 0) + break; + header_done_ = true; + v_ = boost::blank{}; + if(! more_) + goto go_complete; + s_ = do_body + 1; + break; + + case do_header_only: + BOOST_ASSERT(n <= buffer_size( + boost::get(v_))); + boost::get(v_).consume(n); + if(buffer_size(boost::get(v_)) > 0) + break; + frd_ = boost::none; + header_done_ = true; + if(! split_) + goto go_complete; + s_ = do_body; + break; + + case do_body + 2: + { + BOOST_ASSERT(n <= buffer_size( + boost::get(v_))); + boost::get(v_).consume(n); + if(buffer_size(boost::get(v_)) > 0) + break; + v_ = boost::blank{}; + if(! more_) + goto go_complete; + s_ = do_body + 1; + break; + } + + //---------------------------------------------------------------------- + + case do_header_c: + BOOST_ASSERT(n <= buffer_size( + boost::get(v_))); + boost::get(v_).consume(n); + if(buffer_size(boost::get(v_)) > 0) + break; + header_done_ = true; + v_ = boost::blank{}; + if(more_) + s_ = do_body_c + 1; + else + s_ = do_final_c; + break; + + case do_header_only_c: + { + BOOST_ASSERT(n <= buffer_size( + boost::get(v_))); + boost::get(v_).consume(n); + if(buffer_size(boost::get(v_)) > 0) + break; + frd_ = boost::none; + header_done_ = true; + if(! split_) + { + s_ = do_final_c; + break; + } + s_ = do_body_c; + break; + } + + case do_body_c + 2: + BOOST_ASSERT(n <= buffer_size( + boost::get(v_))); + boost::get(v_).consume(n); + if(buffer_size(boost::get(v_)) > 0) + break; + v_ = boost::blank{}; + if(more_) + s_ = do_body_c + 1; + else + s_ = do_final_c; + break; + +#ifndef BEAST_NO_BIG_VARIANTS + case do_body_final_c: + { + auto& b = boost::get(v_); + BOOST_ASSERT(n <= buffer_size(b)); + b.consume(n); + if(buffer_size(b) > 0) + break; + v_ = boost::blank{}; + s_ = do_complete; + break; + } + + case do_all_c: + { + auto& b = boost::get(v_); + BOOST_ASSERT(n <= buffer_size(b)); + b.consume(n); + if(buffer_size(b) > 0) + break; + header_done_ = true; + v_ = boost::blank{}; + s_ = do_complete; + break; + } +#endif + + case do_final_c + 1: + BOOST_ASSERT(buffer_size( + boost::get(v_))); + boost::get(v_).consume(n); + if(buffer_size(boost::get(v_)) > 0) + break; + v_ = boost::blank{}; + goto go_complete; + + //---------------------------------------------------------------------- + + default: + BOOST_ASSERT(false); + case do_complete: + break; + + go_complete: + s_ = do_complete; + break; + } +} + +} // http +} // beast + +#endif diff --git a/src/beast/include/beast/http/impl/status.ipp b/src/beast/include/beast/http/impl/status.ipp new file mode 100644 index 0000000000..671547c92c --- /dev/null +++ b/src/beast/include/beast/http/impl/status.ipp @@ -0,0 +1,248 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_IMPL_STATUS_IPP +#define BEAST_HTTP_IMPL_STATUS_IPP + +#include +#include + +namespace beast { +namespace http { +namespace detail { + +template +status +int_to_status(unsigned v) +{ + switch(static_cast(v)) + { + // 1xx + case status::continue_: + case status::switching_protocols: + case status::processing: + BEAST_FALLTHROUGH; + + // 2xx + case status::ok: + case status::created: + case status::accepted: + case status::non_authoritative_information: + case status::no_content: + case status::reset_content: + case status::partial_content: + case status::multi_status: + case status::already_reported: + case status::im_used: + BEAST_FALLTHROUGH; + + // 3xx + case status::multiple_choices: + case status::moved_permanently: + case status::found: + case status::see_other: + case status::not_modified: + case status::use_proxy: + case status::temporary_redirect: + case status::permanent_redirect: + BEAST_FALLTHROUGH; + + // 4xx + case status::bad_request: + case status::unauthorized: + case status::payment_required: + case status::forbidden: + case status::not_found: + case status::method_not_allowed: + case status::not_acceptable: + case status::proxy_authentication_required: + case status::request_timeout: + case status::conflict: + case status::gone: + case status::length_required: + case status::precondition_failed: + case status::payload_too_large: + case status::uri_too_long: + case status::unsupported_media_type: + case status::range_not_satisfiable: + case status::expectation_failed: + case status::misdirected_request: + case status::unprocessable_entity: + case status::locked: + case status::failed_dependency: + case status::upgrade_required: + case status::precondition_required: + case status::too_many_requests: + case status::request_header_fields_too_large: + case status::connection_closed_without_response: + case status::unavailable_for_legal_reasons: + case status::client_closed_request: + BEAST_FALLTHROUGH; + + // 5xx + case status::internal_server_error: + case status::not_implemented: + case status::bad_gateway: + case status::service_unavailable: + case status::gateway_timeout: + case status::http_version_not_supported: + case status::variant_also_negotiates: + case status::insufficient_storage: + case status::loop_detected: + case status::not_extended: + case status::network_authentication_required: + case status::network_connect_timeout_error: + return static_cast(v); + + default: + break; + } + return status::unknown; +} + +template +string_view +status_to_string(unsigned v) +{ + switch(static_cast(v)) + { + // 1xx + case status::continue_: return "Continue"; + case status::switching_protocols: return "Switching Protocols"; + case status::processing: return "Processing"; + + // 2xx + case status::ok: return "OK"; + case status::created: return "Created"; + case status::accepted: return "Accepted"; + case status::non_authoritative_information: return "Non-Authoritative Information"; + case status::no_content: return "No Content"; + case status::reset_content: return "Reset Content"; + case status::partial_content: return "Partial Content"; + case status::multi_status: return "Multi-Status"; + case status::already_reported: return "Already Reported"; + case status::im_used: return "IM Used"; + + // 3xx + case status::multiple_choices: return "Multiple Choices"; + case status::moved_permanently: return "Moved Permanently"; + case status::found: return "Found"; + case status::see_other: return "See Other"; + case status::not_modified: return "Not Modified"; + case status::use_proxy: return "Use Proxy"; + case status::temporary_redirect: return "Temporary Redirect"; + case status::permanent_redirect: return "Permanent Redirect"; + + // 4xx + case status::bad_request: return "Bad Request"; + case status::unauthorized: return "Unauthorized"; + case status::payment_required: return "Payment Required"; + case status::forbidden: return "Forbidden"; + case status::not_found: return "Not Found"; + case status::method_not_allowed: return "Method Not Allowed"; + case status::not_acceptable: return "Not Acceptable"; + case status::proxy_authentication_required: return "Proxy Authentication Required"; + case status::request_timeout: return "Request Timeout"; + case status::conflict: return "Conflict"; + case status::gone: return "Gone"; + case status::length_required: return "Length Required"; + case status::precondition_failed: return "Precondition Failed"; + case status::payload_too_large: return "Payload Too Large"; + case status::uri_too_long: return "URI Too Long"; + case status::unsupported_media_type: return "Unsupported Media Type"; + case status::range_not_satisfiable: return "Range Not Satisfiable"; + case status::expectation_failed: return "Expectation Failed"; + case status::misdirected_request: return "Misdirected Request"; + case status::unprocessable_entity: return "Unprocessable Entity"; + case status::locked: return "Locked"; + case status::failed_dependency: return "Failed Dependency"; + case status::upgrade_required: return "Upgrade Required"; + case status::precondition_required: return "Precondition Required"; + case status::too_many_requests: return "Too Many Requests"; + case status::request_header_fields_too_large: return "Request Header Fields Too Large"; + case status::connection_closed_without_response: return "Connection Closed Without Response"; + case status::unavailable_for_legal_reasons: return "Unavailable For Legal Reasons"; + case status::client_closed_request: return "Client Closed Request"; + // 5xx + case status::internal_server_error: return "Internal Server Error"; + case status::not_implemented: return "Not Implemented"; + case status::bad_gateway: return "Bad Gateway"; + case status::service_unavailable: return "Service Unavailable"; + case status::gateway_timeout: return "Gateway Timeout"; + case status::http_version_not_supported: return "HTTP Version Not Supported"; + case status::variant_also_negotiates: return "Variant Also Negotiates"; + case status::insufficient_storage: return "Insufficient Storage"; + case status::loop_detected: return "Loop Detected"; + case status::not_extended: return "Not Extended"; + case status::network_authentication_required: return "Network Authentication Required"; + case status::network_connect_timeout_error: return "Network Connect Timeout Error"; + + default: + break; + } + return ""; +} + +template +status_class +to_status_class(unsigned v) +{ + switch(v / 100) + { + case 1: return status_class::informational; + case 2: return status_class::successful; + case 3: return status_class::redirection; + case 4: return status_class::client_error; + case 5: return status_class::server_error; + default: + break; + } + return status_class::unknown; +} + +} // detail + +inline +status +int_to_status(unsigned v) +{ + return detail::int_to_status(v); +} + +inline +status_class +to_status_class(unsigned v) +{ + return detail::to_status_class(v); +} + +inline +status_class +to_status_class(status v) +{ + return to_status_class(static_cast(v)); +} + +inline +string_view +obsolete_reason(status v) +{ + return detail::status_to_string( + static_cast(v)); +} + +inline +std::ostream& +operator<<(std::ostream& os, status v) +{ + return os << obsolete_reason(v); +} + +} // http +} // beast + +#endif diff --git a/src/beast/include/beast/http/impl/verb.ipp b/src/beast/include/beast/http/impl/verb.ipp new file mode 100644 index 0000000000..ad0f1b5ca8 --- /dev/null +++ b/src/beast/include/beast/http/impl/verb.ipp @@ -0,0 +1,333 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_IMPL_VERB_IPP +#define BEAST_HTTP_IMPL_VERB_IPP + +#include +#include +#include + +namespace beast { +namespace http { + +namespace detail { + +template +inline +string_view +verb_to_string(verb v) +{ + switch(v) + { + case verb::delete_: return "DELETE"; + case verb::get: return "GET"; + case verb::head: return "HEAD"; + case verb::post: return "POST"; + case verb::put: return "PUT"; + case verb::connect: return "CONNECT"; + case verb::options: return "OPTIONS"; + case verb::trace: return "TRACE"; + + case verb::copy: return "COPY"; + case verb::lock: return "LOCK"; + case verb::mkcol: return "MKCOL"; + case verb::move: return "MOVE"; + case verb::propfind: return "PROPFIND"; + case verb::proppatch: return "PROPPATCH"; + case verb::search: return "SEARCH"; + case verb::unlock: return "UNLOCK"; + case verb::bind: return "BIND"; + case verb::rebind: return "REBIND"; + case verb::unbind: return "UNBIND"; + case verb::acl: return "ACL"; + + case verb::report: return "REPORT"; + case verb::mkactivity: return "MKACTIVITY"; + case verb::checkout: return "CHECKOUT"; + case verb::merge: return "MERGE"; + + case verb::msearch: return "M-SEARCH"; + case verb::notify: return "NOTIFY"; + case verb::subscribe: return "SUBSCRIBE"; + case verb::unsubscribe: return "UNSUBSCRIBE"; + + case verb::patch: return "PATCH"; + case verb::purge: return "PURGE"; + + case verb::mkcalendar: return "MKCALENDAR"; + + case verb::link: return "LINK"; + case verb::unlink: return "UNLINK"; + + case verb::unknown: + return ""; + } + + BOOST_THROW_EXCEPTION(std::invalid_argument{"unknown verb"}); +} + +template +verb +string_to_verb(string_view v) +{ +/* + ACL + BIND + CHECKOUT + CONNECT + COPY + DELETE + GET + HEAD + LINK + LOCK + M-SEARCH + MERGE + MKACTIVITY + MKCALENDAR + MKCOL + MOVE + NOTIFY + OPTIONS + PATCH + POST + PROPFIND + PROPPATCH + PURGE + PUT + REBIND + REPORT + SEARCH + SUBSCRIBE + TRACE + UNBIND + UNLINK + UNLOCK + UNSUBSCRIBE +*/ + if(v.size() < 3) + return verb::unknown; + // s must be null terminated + auto const eq = + [](string_view sv, char const* s) + { + auto p = sv.data(); + for(;;) + { + if(*s != *p) + return false; + ++s; + ++p; + if(! *s) + return p == sv.end(); + } + }; + auto c = v[0]; + v.remove_prefix(1); + switch(c) + { + case 'A': + if(v == "CL") + return verb::acl; + break; + + case 'B': + if(v == "IND") + return verb::bind; + break; + + case 'C': + c = v[0]; + v.remove_prefix(1); + switch(c) + { + case 'H': + if(eq(v, "ECKOUT")) + return verb::checkout; + break; + + case 'O': + if(eq(v, "NNECT")) + return verb::connect; + if(eq(v, "PY")) + return verb::copy; + BEAST_FALLTHROUGH; + + default: + break; + } + break; + + case 'D': + if(eq(v, "ELETE")) + return verb::delete_; + break; + + case 'G': + if(eq(v, "ET")) + return verb::get; + break; + + case 'H': + if(eq(v, "EAD")) + return verb::head; + break; + + case 'L': + if(eq(v, "INK")) + return verb::link; + if(eq(v, "OCK")) + return verb::lock; + break; + + case 'M': + c = v[0]; + v.remove_prefix(1); + switch(c) + { + case '-': + if(eq(v, "SEARCH")) + return verb::msearch; + break; + + case 'E': + if(eq(v, "RGE")) + return verb::merge; + break; + + case 'K': + if(eq(v, "ACTIVITY")) + return verb::mkactivity; + if(v[0] == 'C') + { + v.remove_prefix(1); + if(eq(v, "ALENDAR")) + return verb::mkcalendar; + if(eq(v, "OL")) + return verb::mkcol; + break; + } + break; + + case 'O': + if(eq(v, "VE")) + return verb::move; + BEAST_FALLTHROUGH; + + default: + break; + } + break; + + case 'N': + if(eq(v, "OTIFY")) + return verb::notify; + break; + + case 'O': + if(eq(v, "PTIONS")) + return verb::options; + break; + + case 'P': + c = v[0]; + v.remove_prefix(1); + switch(c) + { + case 'A': + if(eq(v, "TCH")) + return verb::patch; + break; + + case 'O': + if(eq(v, "ST")) + return verb::post; + break; + + case 'R': + if(eq(v, "OPFIND")) + return verb::propfind; + if(eq(v, "OPPATCH")) + return verb::proppatch; + break; + + case 'U': + if(eq(v, "RGE")) + return verb::purge; + if(eq(v, "T")) + return verb::put; + BEAST_FALLTHROUGH; + + default: + break; + } + break; + + case 'R': + if(v[0] != 'E') + break; + v.remove_prefix(1); + if(eq(v, "BIND")) + return verb::rebind; + if(eq(v, "PORT")) + return verb::report; + break; + + case 'S': + if(eq(v, "EARCH")) + return verb::search; + if(eq(v, "UBSCRIBE")) + return verb::subscribe; + break; + + case 'T': + if(eq(v, "RACE")) + return verb::trace; + break; + + case 'U': + if(v[0] != 'N') + break; + v.remove_prefix(1); + if(eq(v, "BIND")) + return verb::unbind; + if(eq(v, "LINK")) + return verb::unlink; + if(eq(v, "LOCK")) + return verb::unlock; + if(eq(v, "SUBSCRIBE")) + return verb::unsubscribe; + break; + + default: + break; + } + + return verb::unknown; +} + +} // detail + +inline +string_view +to_string(verb v) +{ + return detail::verb_to_string(v); +} + +inline +verb +string_to_verb(string_view s) +{ + return detail::string_to_verb(s); +} + +} // http +} // beast + +#endif diff --git a/src/beast/include/beast/http/impl/write.ipp b/src/beast/include/beast/http/impl/write.ipp index a6e640254d..1060a4f081 100644 --- a/src/beast/include/beast/http/impl/write.ipp +++ b/src/beast/include/beast/http/impl/write.ipp @@ -8,582 +8,732 @@ #ifndef BEAST_HTTP_IMPL_WRITE_IPP #define BEAST_HTTP_IMPL_WRITE_IPP -#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 namespace beast { namespace http { - namespace detail { -template -void -write_start_line(DynamicBuffer& dynabuf, - header const& msg) +template +class write_some_op { - BOOST_ASSERT(msg.version == 10 || msg.version == 11); - write(dynabuf, msg.method); - write(dynabuf, " "); - write(dynabuf, msg.url); - switch(msg.version) + Stream& s_; + serializer& sr_; + Handler h_; + + class lambda { - case 10: - write(dynabuf, " HTTP/1.0\r\n"); - break; - case 11: - write(dynabuf, " HTTP/1.1\r\n"); - break; - } -} + write_some_op& op_; -template -void -write_start_line(DynamicBuffer& dynabuf, - header const& msg) -{ - BOOST_ASSERT(msg.version == 10 || msg.version == 11); - switch(msg.version) - { - case 10: - write(dynabuf, "HTTP/1.0 "); - break; - case 11: - write(dynabuf, "HTTP/1.1 "); - break; - } - write(dynabuf, msg.status); - write(dynabuf, " "); - write(dynabuf, msg.reason); - write(dynabuf, "\r\n"); -} + public: + bool invoked = false; -template -void -write_fields(DynamicBuffer& dynabuf, FieldSequence const& fields) -{ - static_assert(is_DynamicBuffer::value, - "DynamicBuffer requirements not met"); - //static_assert(is_FieldSequence::value, - // "FieldSequence requirements not met"); - for(auto const& field : fields) - { - write(dynabuf, field.name()); - write(dynabuf, ": "); - write(dynabuf, field.value()); - write(dynabuf, "\r\n"); - } -} - -} // detail - -//------------------------------------------------------------------------------ - -namespace detail { - -template -class write_streambuf_op -{ - struct data - { - bool cont; - Stream& s; - streambuf sb; - int state = 0; - - data(Handler& handler, Stream& s_, - streambuf&& sb_) - : cont(beast_asio_helpers:: - is_continuation(handler)) - , s(s_) - , sb(std::move(sb_)) + explicit + lambda(write_some_op& op) + : op_(op) { } + + template + void + operator()(error_code& ec, + ConstBufferSequence const& buffers) + { + invoked = true; + ec.assign(0, ec.category()); + return op_.s_.async_write_some( + buffers, std::move(op_)); + } }; - handler_ptr d_; - public: - write_streambuf_op(write_streambuf_op&&) = default; - write_streambuf_op(write_streambuf_op const&) = default; + write_some_op(write_some_op&&) = default; + write_some_op(write_some_op const&) = default; - template - write_streambuf_op(DeducedHandler&& h, Stream& s, - Args&&... args) - : d_(std::forward(h), - s, std::forward(args)...) + template + write_some_op(DeducedHandler&& h, + Stream& s, serializer& sr) + : s_(s) + , sr_(sr) + , h_(std::forward(h)) { - (*this)(error_code{}, 0, false); } + void + operator()(); + void operator()(error_code ec, - std::size_t bytes_transferred, bool again = true); + std::size_t bytes_transferred); friend void* asio_handler_allocate( - std::size_t size, write_streambuf_op* op) + std::size_t size, write_some_op* op) { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); + using boost::asio::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(op->h_)); } friend void asio_handler_deallocate( - void* p, std::size_t size, write_streambuf_op* op) + void* p, std::size_t size, write_some_op* op) { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->h_)); } friend - bool asio_handler_is_continuation(write_streambuf_op* op) + bool asio_handler_is_continuation(write_some_op* op) { - return op->d_->cont; + using boost::asio::asio_handler_is_continuation; + return asio_handler_is_continuation( + std::addressof(op->h_)); } template friend - void asio_handler_invoke(Function&& f, write_streambuf_op* op) + void asio_handler_invoke(Function&& f, write_some_op* op) { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); - } -}; - -template -void -write_streambuf_op:: -operator()(error_code ec, std::size_t, bool again) -{ - auto& d = *d_; - d.cont = d.cont || again; - while(! ec && d.state != 99) - { - switch(d.state) - { - case 0: - { - d.state = 99; - boost::asio::async_write(d.s, - d.sb.data(), std::move(*this)); - return; - } - } - } - d_.invoke(ec); -} - -} // detail - -template -void -write(SyncWriteStream& stream, - header const& msg) -{ - static_assert(is_SyncWriteStream::value, - "SyncWriteStream requirements not met"); - error_code ec; - write(stream, msg, ec); - if(ec) - throw system_error{ec}; -} - -template -void -write(SyncWriteStream& stream, - header const& msg, - error_code& ec) -{ - static_assert(is_SyncWriteStream::value, - "SyncWriteStream requirements not met"); - streambuf sb; - detail::write_start_line(sb, msg); - detail::write_fields(sb, msg.fields); - beast::write(sb, "\r\n"); - boost::asio::write(stream, sb.data(), ec); -} - -template -typename async_completion< - WriteHandler, void(error_code)>::result_type -async_write(AsyncWriteStream& stream, - header const& msg, - WriteHandler&& handler) -{ - static_assert(is_AsyncWriteStream::value, - "AsyncWriteStream requirements not met"); - beast::async_completion completion{handler}; - streambuf sb; - detail::write_start_line(sb, msg); - detail::write_fields(sb, msg.fields); - beast::write(sb, "\r\n"); - detail::write_streambuf_op{ - completion.handler, stream, std::move(sb)}; - return completion.result.get(); -} - -//------------------------------------------------------------------------------ - -namespace detail { - -template -struct write_preparation -{ - message const& msg; - typename Body::writer w; - streambuf sb; - bool chunked; - bool close; - - explicit - write_preparation( - message const& msg_) - : msg(msg_) - , w(msg) - , chunked(token_list{ - msg.fields["Transfer-Encoding"]}.exists("chunked")) - , close(token_list{ - msg.fields["Connection"]}.exists("close") || - (msg.version < 11 && ! msg.fields.exists( - "Content-Length"))) - { - } - - void - init(error_code& ec) - { - w.init(ec); - if(ec) - return; - - write_start_line(sb, msg); - write_fields(sb, msg.fields); - beast::write(sb, "\r\n"); + using boost::asio::asio_handler_invoke; + asio_handler_invoke(f, std::addressof(op->h_)); } }; template + bool isRequest, class Body, + class Fields, class Decorator> +void +write_some_op:: +operator()() +{ + error_code ec; + if(! sr_.is_done()) + { + lambda f{*this}; + sr_.next(ec, f); + if(ec) + { + BOOST_ASSERT(! f.invoked); + return s_.get_io_service().post( + bind_handler(std::move(*this), ec, 0)); + } + if(f.invoked) + { + // *this has been moved from, + // cannot access members here. + return; + } + // What else could it be? + BOOST_ASSERT(sr_.is_done()); + } + return s_.get_io_service().post( + bind_handler(std::move(*this), ec, 0)); +} + +template +void +write_some_op:: +operator()( + error_code ec, std::size_t bytes_transferred) +{ + if(! ec) + { + sr_.consume(bytes_transferred); + if(sr_.is_done()) + if(! sr_.keep_alive()) + ec = error::end_of_stream; + } + h_(ec); +} + +//------------------------------------------------------------------------------ + +struct serializer_is_header_done +{ + template + bool + operator()(serializer& sr) const + { + return sr.is_header_done(); + } +}; + +struct serializer_is_done +{ + template + bool + operator()(serializer& sr) const + { + return sr.is_done(); + } +}; + +//------------------------------------------------------------------------------ + +template< + class Stream, class Handler, class Predicate, + bool isRequest, class Body, + class Fields, class Decorator> class write_op { - struct data - { - bool cont; - Stream& s; - // VFALCO How do we use handler_alloc in write_preparation? - write_preparation< - isRequest, Body, Fields> wp; - int state = 0; - - data(Handler& handler, Stream& s_, - message const& m_) - : cont(beast_asio_helpers:: - is_continuation(handler)) - , s(s_) - , wp(m_) - { - } - }; - - class writef0_lambda - { - write_op& self_; - - public: - explicit - writef0_lambda(write_op& self) - : self_(self) - { - } - - template - void operator()(ConstBufferSequence const& buffers) const - { - auto& d = *self_.d_; - // write header and body - if(d.wp.chunked) - boost::asio::async_write(d.s, - buffer_cat(d.wp.sb.data(), - chunk_encode(false, buffers)), - std::move(self_)); - else - boost::asio::async_write(d.s, - buffer_cat(d.wp.sb.data(), - buffers), std::move(self_)); - } - }; - - class writef_lambda - { - write_op& self_; - - public: - explicit - writef_lambda(write_op& self) - : self_(self) - { - } - - template - void operator()(ConstBufferSequence const& buffers) const - { - auto& d = *self_.d_; - // write body - if(d.wp.chunked) - boost::asio::async_write(d.s, - chunk_encode(false, buffers), - std::move(self_)); - else - boost::asio::async_write(d.s, - buffers, std::move(self_)); - } - }; - - handler_ptr d_; + int state_ = 0; + Stream& s_; + serializer& sr_; + Handler h_; public: write_op(write_op&&) = default; write_op(write_op const&) = default; - template - write_op(DeducedHandler&& h, Stream& s, Args&&... args) - : d_(std::forward(h), - s, std::forward(args)...) + template + write_op(DeducedHandler&& h, Stream& s, + serializer& sr) + : s_(s) + , sr_(sr) + , h_(std::forward(h)) { - (*this)(error_code{}, 0, false); } void - operator()(error_code ec, - std::size_t bytes_transferred, bool again = true); + operator()(error_code ec); friend void* asio_handler_allocate( std::size_t size, write_op* op) { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); + using boost::asio::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(op->h_)); } friend void asio_handler_deallocate( void* p, std::size_t size, write_op* op) { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->h_)); } friend bool asio_handler_is_continuation(write_op* op) { - return op->d_->cont; + using boost::asio::asio_handler_is_continuation; + return op->state_ >= 3 || + asio_handler_is_continuation( + std::addressof(op->h_)); } template friend void asio_handler_invoke(Function&& f, write_op* op) { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->h_)); + } +}; + +template< + class Stream, class Handler, class Predicate, + bool isRequest, class Body, + class Fields, class Decorator> +void +write_op:: +operator()(error_code ec) +{ + if(ec) + goto upcall; + switch(state_) + { + case 0: + { + if(Predicate{}(sr_)) + { + state_ = 1; + return s_.get_io_service().post( + bind_handler(std::move(*this), ec)); + } + state_ = 2; + return beast::http::async_write_some( + s_, sr_, std::move(*this)); + } + + case 1: + goto upcall; + + case 2: + state_ = 3; + BEAST_FALLTHROUGH; + + case 3: + { + if(Predicate{}(sr_)) + goto upcall; + return beast::http::async_write_some( + s_, sr_, std::move(*this)); + } + } +upcall: + h_(ec); +} + +//------------------------------------------------------------------------------ + +template +class write_msg_op +{ + struct data + { + Stream& s; + serializer sr; + + data(Handler&, Stream& s_, message< + isRequest, Body, Fields>& m_) + : s(s_) + , sr(m_, no_chunk_decorator{}) + { + } + }; + + handler_ptr d_; + +public: + write_msg_op(write_msg_op&&) = default; + write_msg_op(write_msg_op const&) = default; + + template + write_msg_op(DeducedHandler&& h, Stream& s, Args&&... args) + : d_(std::forward(h), + s, std::forward(args)...) + { + } + + void + operator()(); + + void + operator()(error_code ec); + + friend + void* asio_handler_allocate( + std::size_t size, write_msg_op* op) + { + using boost::asio::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(op->d_.handler())); + } + + friend + void asio_handler_deallocate( + void* p, std::size_t size, write_msg_op* op) + { + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->d_.handler())); + } + + friend + bool asio_handler_is_continuation(write_msg_op* op) + { + using boost::asio::asio_handler_is_continuation; + return asio_handler_is_continuation( + std::addressof(op->d_.handler())); + } + + template + friend + void asio_handler_invoke(Function&& f, write_msg_op* op) + { + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->d_.handler())); } }; template void -write_op:: -operator()(error_code ec, std::size_t, bool again) +write_msg_op< + Stream, Handler, isRequest, Body, Fields>:: +operator()() { auto& d = *d_; - d.cont = d.cont || again; - while(! ec && d.state != 99) - { - switch(d.state) - { - case 0: - { - d.wp.init(ec); - if(ec) - { - // call handler - d.state = 99; - d.s.get_io_service().post(bind_handler( - std::move(*this), ec, 0, false)); - return; - } - d.state = 1; - break; - } + return async_write(d.s, d.sr, std::move(*this)); +} - case 1: - { - auto const result = - d.wp.w.write(ec, - writef0_lambda{*this}); - if(ec) - { - // call handler - d.state = 99; - d.s.get_io_service().post(bind_handler( - std::move(*this), ec, false)); - return; - } - if(result) - d.state = d.wp.chunked ? 4 : 5; - else - d.state = 2; - return; - } - - // sent header and body - case 2: - d.wp.sb.consume(d.wp.sb.size()); - d.state = 3; - break; - - case 3: - { - auto const result = - d.wp.w.write(ec, - writef_lambda{*this}); - if(ec) - { - // call handler - d.state = 99; - break; - } - if(result) - d.state = d.wp.chunked ? 4 : 5; - else - d.state = 2; - return; - } - - case 4: - // VFALCO Unfortunately the current interface to the - // Writer concept prevents us from coalescing the - // final body chunk with the final chunk delimiter. - // - // write final chunk - d.state = 5; - boost::asio::async_write(d.s, - chunk_encode_final(), std::move(*this)); - return; - - case 5: - if(d.wp.close) - { - // VFALCO TODO Decide on an error code - ec = boost::asio::error::eof; - } - d.state = 99; - break; - } - } +template +void +write_msg_op< + Stream, Handler, isRequest, Body, Fields>:: +operator()(error_code ec) +{ d_.invoke(ec); } -template -class writef0_lambda +//------------------------------------------------------------------------------ + +template +class write_some_lambda { - DynamicBuffer const& sb_; - SyncWriteStream& stream_; - bool chunked_; - error_code& ec_; + Stream& stream_; public: - writef0_lambda(SyncWriteStream& stream, - DynamicBuffer const& sb, bool chunked, error_code& ec) - : sb_(sb) - , stream_(stream) - , chunked_(chunked) - , ec_(ec) + bool invoked = false; + std::size_t bytes_transferred = 0; + + explicit + write_some_lambda(Stream& stream) + : stream_(stream) { } template - void operator()(ConstBufferSequence const& buffers) const + void + operator()(error_code& ec, + ConstBufferSequence const& buffers) { - // write header and body - if(chunked_) - boost::asio::write(stream_, buffer_cat( - sb_.data(), chunk_encode(false, buffers)), ec_); - else - boost::asio::write(stream_, buffer_cat( - sb_.data(), buffers), ec_); + invoked = true; + bytes_transferred = + stream_.write_some(buffers, ec); } }; -template -class writef_lambda +template +class write_lambda { - SyncWriteStream& stream_; - bool chunked_; - error_code& ec_; + Stream& stream_; public: - writef_lambda(SyncWriteStream& stream, - bool chunked, error_code& ec) + bool invoked = false; + std::size_t bytes_transferred = 0; + + explicit + write_lambda(Stream& stream) : stream_(stream) - , chunked_(chunked) - , ec_(ec) { } template - void operator()(ConstBufferSequence const& buffers) const + void + operator()(error_code& ec, + ConstBufferSequence const& buffers) { - // write body - if(chunked_) - boost::asio::write(stream_, - chunk_encode(false, buffers), ec_); - else - boost::asio::write(stream_, buffers, ec_); + invoked = true; + bytes_transferred = boost::asio::write( + stream_, buffers, ec); } }; } // detail +//------------------------------------------------------------------------------ + +namespace detail { + +template< + class SyncWriteStream, + bool isRequest, class Body, class Fields, class Decorator> +void +write_some( + SyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr, + error_code& ec) +{ + if(! sr.is_done()) + { + write_some_lambda f{stream}; + sr.next(ec, f); + if(ec) + return; + if(f.invoked) + sr.consume(f.bytes_transferred); + if(sr.is_done()) + if(! sr.keep_alive()) + ec = error::end_of_stream; + return; + } + if(! sr.keep_alive()) + ec = error::end_of_stream; + else + ec.assign(0, ec.category()); +} + +template +async_return_type +async_write_some(AsyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr, + WriteHandler&& handler) +{ + async_completion init{handler}; + detail::write_some_op, + isRequest, Body, Fields, Decorator>{ + init.completion_handler, stream, sr}(); + return init.result.get(); +} + +} // detail + +template +void +write_some(SyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr) +{ + static_assert(is_sync_write_stream::value, + "SyncWriteStream requirements not met"); + static_assert(is_body::value, + "Body requirements not met"); + static_assert(is_body_reader::value, + "BodyReader requirements not met"); + error_code ec; + write_some(stream, sr, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); +} + +template +void +write_some(SyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr, + error_code& ec) +{ + static_assert(is_sync_write_stream::value, + "SyncWriteStream requirements not met"); + static_assert(is_body::value, + "Body requirements not met"); + static_assert(is_body_reader::value, + "BodyReader requirements not met"); + detail::write_some(stream, sr, ec); +} + +template +async_return_type +async_write_some(AsyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr, + WriteHandler&& handler) +{ + static_assert(is_async_write_stream< + AsyncWriteStream>::value, + "AsyncWriteStream requirements not met"); + static_assert(is_body::value, + "Body requirements not met"); + static_assert(is_body_reader::value, + "BodyReader requirements not met"); + return detail::async_write_some(stream, sr, + std::forward(handler)); +} + +//------------------------------------------------------------------------------ + +template +void +write_header(SyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr) +{ + static_assert(is_sync_write_stream::value, + "SyncWriteStream requirements not met"); + static_assert(is_body::value, + "Body requirements not met"); + static_assert(is_body_reader::value, + "BodyReader requirements not met"); + error_code ec; + write_header(stream, sr, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); +} + +template +void +write_header(SyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr, + error_code& ec) +{ + static_assert(is_sync_write_stream::value, + "SyncWriteStream requirements not met"); + static_assert(is_body::value, + "Body requirements not met"); + static_assert(is_body_reader::value, + "BodyReader requirements not met"); + sr.split(true); + if(! sr.is_header_done()) + { + detail::write_lambda f{stream}; + do + { + sr.next(ec, f); + if(ec) + return; + BOOST_ASSERT(f.invoked); + sr.consume(f.bytes_transferred); + } + while(! sr.is_header_done()); + } + else + { + ec.assign(0, ec.category()); + } +} + +template +async_return_type +async_write_header(AsyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr, + WriteHandler&& handler) +{ + static_assert(is_async_write_stream< + AsyncWriteStream>::value, + "AsyncWriteStream requirements not met"); + static_assert(is_body::value, + "Body requirements not met"); + static_assert(is_body_reader::value, + "BodyReader requirements not met"); + sr.split(true); + async_completion init{handler}; + detail::write_op, + detail::serializer_is_header_done, + isRequest, Body, Fields, Decorator>{ + init.completion_handler, stream, sr}( + error_code{}, 0); + return init.result.get(); +} + +//------------------------------------------------------------------------------ + +template< + class SyncWriteStream, + bool isRequest, class Body, + class Fields, class Decorator> +void +write( + SyncWriteStream& stream, + serializer& sr) +{ + static_assert(is_sync_write_stream::value, + "SyncWriteStream requirements not met"); + error_code ec; + write(stream, sr, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); +} + +template< + class SyncWriteStream, + bool isRequest, class Body, + class Fields, class Decorator> +void +write( + SyncWriteStream& stream, + serializer& sr, + error_code& ec) +{ + static_assert(is_sync_write_stream::value, + "SyncWriteStream requirements not met"); + sr.split(false); + for(;;) + { + write_some(stream, sr, ec); + if(ec) + return; + if(sr.is_done()) + break; + } +} + +template +async_return_type +async_write(AsyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr, + WriteHandler&& handler) +{ + static_assert(is_async_write_stream< + AsyncWriteStream>::value, + "AsyncWriteStream requirements not met"); + static_assert(is_body::value, + "Body requirements not met"); + static_assert(is_body_reader::value, + "BodyReader requirements not met"); + sr.split(false); + async_completion init{handler}; + detail::write_op, + detail::serializer_is_done, + isRequest, Body, Fields, Decorator>{ + init.completion_handler, stream, sr}( + error_code{}); + return init.result.get(); +} + +//------------------------------------------------------------------------------ + template void write(SyncWriteStream& stream, message const& msg) { - static_assert(is_SyncWriteStream::value, + static_assert(is_sync_write_stream::value, "SyncWriteStream requirements not met"); - static_assert(is_Body::value, + static_assert(is_body::value, "Body requirements not met"); - static_assert(has_writer::value, - "Body has no writer"); - static_assert(is_Writer>::value, - "Writer requirements not met"); + static_assert(is_body_reader::value, + "BodyReader requirements not met"); error_code ec; write(stream, msg, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); } template const& msg, error_code& ec) { - static_assert(is_SyncWriteStream::value, + static_assert(is_sync_write_stream::value, "SyncWriteStream requirements not met"); - static_assert(is_Body::value, + static_assert(is_body::value, "Body requirements not met"); - static_assert(has_writer::value, - "Body has no writer"); - static_assert(is_Writer>::value, - "Writer requirements not met"); - detail::write_preparation wp(msg); - wp.init(ec); - if(ec) - return; - auto result = wp.w.write( - ec, detail::writef0_lambda< - SyncWriteStream, decltype(wp.sb)>{ - stream, wp.sb, wp.chunked, ec}); - if(ec) - return; - wp.sb.consume(wp.sb.size()); - if(! result) - { - detail::writef_lambda wf{ - stream, wp.chunked, ec}; - for(;;) - { - result = wp.w.write(ec, wf); - if(ec) - return; - if(result) - break; - } - } - if(wp.chunked) - { - // VFALCO Unfortunately the current interface to the - // Writer concept prevents us from using coalescing the - // final body chunk with the final chunk delimiter. - // - // write final chunk - boost::asio::write(stream, chunk_encode_final(), ec); - if(ec) - return; - } - if(wp.close) - { - // VFALCO TODO Decide on an error code - ec = boost::asio::error::eof; - } + static_assert(is_body_reader::value, + "BodyReader requirements not met"); + serializer sr{msg}; + write(stream, sr, ec); } template -typename async_completion< - WriteHandler, void(error_code)>::result_type +async_return_type< + WriteHandler, void(error_code)> async_write(AsyncWriteStream& stream, - message const& msg, + message& msg, WriteHandler&& handler) { - static_assert(is_AsyncWriteStream::value, + static_assert( + is_async_write_stream::value, "AsyncWriteStream requirements not met"); - static_assert(is_Body::value, + static_assert(is_body::value, "Body requirements not met"); - static_assert(has_writer::value, - "Body has no writer"); - static_assert(is_Writer>::value, - "Writer requirements not met"); - beast::async_completion completion{handler}; - detail::write_op{completion.handler, stream, msg}; - return completion.result.get(); + static_assert(is_body_reader::value, + "BodyReader requirements not met"); + async_completion init{handler}; + detail::write_msg_op, isRequest, + Body, Fields>{init.completion_handler, + stream, msg}(); + return init.result.get(); } //------------------------------------------------------------------------------ -template +namespace detail { + +template +class write_ostream_lambda +{ + std::ostream& os_; + Serializer& sr_; + +public: + write_ostream_lambda(std::ostream& os, + Serializer& sr) + : os_(os) + , sr_(sr) + { + } + + template + void + operator()(error_code& ec, + ConstBufferSequence const& buffers) const + { + ec.assign(0, ec.category()); + if(os_.fail()) + return; + std::size_t bytes_transferred = 0; + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + for(auto it = buffers.begin(); + it != buffers.end(); ++it) + { + boost::asio::const_buffer b = *it; + auto const n = buffer_size(b); + os_.write(buffer_cast(b), n); + if(os_.fail()) + return; + bytes_transferred += n; + } + sr_.consume(bytes_transferred); + } +}; + +} // detail + +template std::ostream& operator<<(std::ostream& os, - header const& msg) + header const& h) { - beast::detail::sync_ostream oss{os}; - error_code ec; - write(oss, msg, ec); - if(ec) - throw system_error{ec}; - return os; + typename Fields::reader fr{ + h, h.version, h.method()}; + return os << buffers(fr.get()); +} + +template +std::ostream& +operator<<(std::ostream& os, + header const& h) +{ + typename Fields::reader fr{ + h, h.version, h.result_int()}; + return os << buffers(fr.get()); } template @@ -689,18 +848,27 @@ std::ostream& operator<<(std::ostream& os, message const& msg) { - static_assert(is_Body::value, + static_assert(is_body::value, "Body requirements not met"); - static_assert(has_writer::value, - "Body has no writer"); - static_assert(is_Writer>::value, - "Writer requirements not met"); - beast::detail::sync_ostream oss{os}; + static_assert(is_body_reader::value, + "BodyReader requirements not met"); + serializer sr{msg}; error_code ec; - write(oss, msg, ec); - if(ec && ec != boost::asio::error::eof) - throw system_error{ec}; + detail::write_ostream_lambda f{os, sr}; + do + { + sr.next(ec, f); + if(os.fail()) + break; + if(ec == error::end_of_stream) + ec.assign(0, ec.category()); + if(ec) + { + os.setstate(std::ios::failbit); + break; + } + } + while(! sr.is_done()); return os; } diff --git a/src/beast/include/beast/http/message.hpp b/src/beast/include/beast/http/message.hpp index 70cfbf19c1..4c3fd64482 100644 --- a/src/beast/include/beast/http/message.hpp +++ b/src/beast/include/beast/http/message.hpp @@ -10,8 +10,15 @@ #include #include +#include +#include +#include +#include #include +#include +#include #include +#include #include #include #include @@ -19,66 +26,57 @@ namespace beast { namespace http { -#if GENERATING_DOCS -/** A container for a HTTP request or response header. +/** A container for an HTTP request or response header. - A header includes the Start Line and Fields. + This container is derived from the `Fields` template type. + To understand all of the members of this class it is necessary + to view the declaration for the `Fields` type. When using + the default fields container, those declarations are in + @ref fields. - Some use-cases: + Newly constructed header objects have version set to + HTTP/1.1. Newly constructed response objects also have + result code set to @ref status::ok. - @li When the message has no body, such as a response to a HEAD request. - - @li When the caller wishes to defer instantiation of the body. - - @li Invoke algorithms which operate on the header only. + A `header` includes the start-line and header-fields. */ -template -struct header +#if BEAST_DOXYGEN +template +struct header : Fields #else -template +template struct header; template -struct header +struct header : Fields #endif { - /// Indicates if the header is a request or response. -#if GENERATING_DOCS - static bool constexpr is_request = isRequest; + static_assert(is_fields::value, + "Fields requirements not met"); + /// Indicates if the header is a request or response. +#if BEAST_DOXYGEN + using is_request = std::integral_constant; #else - static bool constexpr is_request = true; + using is_request = std::true_type; #endif /// The type representing the fields. using fields_type = Fields; - /** The HTTP version. + /** The HTTP-version. This holds both the major and minor version numbers, using these formulas: @code - major = version / 10; - minor = version % 10; + unsigned major = version / 10; + unsigned minor = version % 10; @endcode + + Newly constructed headers will use HTTP/1.1 by default. */ - int version; - - /** The Request Method - - @note This field is present only if `isRequest == true`. - */ - std::string method; - - /** The Request URI - - @note This field is present only if `isRequest == true`. - */ - std::string url; - - /// The HTTP field values. - fields_type fields; + unsigned version = 11; /// Default constructor header() = default; @@ -95,16 +93,85 @@ struct header /// Copy assignment header& operator=(header const&) = default; - /** Construct the header. + /** Return the request-method verb. - All arguments are forwarded to the constructor - of the `fields` member. + If the request-method is not one of the recognized verbs, + @ref verb::unknown is returned. Callers may use @ref method_string + to retrieve the exact text. - @note This constructor participates in overload resolution - if and only if the first parameter is not convertible to - `header`. + @note This function is only available when `isRequest == true`. + + @see @ref method_string */ -#if GENERATING_DOCS + verb + method() const; + + /** Set the request-method. + + This function will set the method for requests to a known verb. + + @param v The request method verb to set. + This may not be @ref verb::unknown. + + @throws std::invalid_argument when `v == verb::unknown`. + + @note This function is only available when `isRequest == true`. + */ + void + method(verb v); + + /** Return the request-method as a string. + + @note This function is only available when `isRequest == true`. + + @see @ref method + */ + string_view + method_string() const; + + /** Set the request-method. + + This function will set the request-method a known verb + if the string matches, otherwise it will store a copy of + the passed string. + + @param s A string representing the request-method. + + @note This function is only available when `isRequest == true`. + */ + void + method_string(string_view s); + + /** Returns the request-target string. + + @note This function is only available when `isRequest == true`. + */ + string_view + target() const; + + /** Set the request-target string. + + @param s A string representing the request-target. + + @note This function is only available when `isRequest == true`. + */ + void + target(string_view s); + + // VFALCO Don't rearrange these declarations or + // ifdefs, or else the documentation will break. + + /** Constructor + + @param args Arguments forwarded to the `Fields` + base class constructor. + + @note This constructor participates in overload + resolution if and only if the first parameter is + not convertible to @ref header, @ref verb, or + @ref status. + */ +#if BEAST_DOXYGEN template explicit header(Args&&... args); @@ -112,34 +179,53 @@ struct header #else template 0) || ! std::is_convertible< - typename std::decay::type, - header>::value>::type> + ! std::is_convertible::type, header>::value && + ! std::is_convertible::type, verb>::value && + ! std::is_convertible::type, header>::value + >::type> explicit - header(Arg1&& arg1, ArgN&&... argn) - : fields(std::forward(arg1), - std::forward(argn)...) + header(Arg1&& arg1, ArgN&&... argn); + +private: + template + friend struct message; + + template + friend + void + swap(header& m1, header& m2); + + template + header( + verb method, + string_view target_, + unsigned version_, + FieldsArgs&&... fields_args) + : Fields(std::forward(fields_args)...) + , version(version_) + , method_(method) { + target(target_); } + + verb method_ = verb::unknown; }; -/** A container for a HTTP request or response header. +/** A container for an HTTP request or response header. - A header includes the Start Line and Fields. - - Some use-cases: - - @li When the message has no body, such as a response to a HEAD request. - - @li When the caller wishes to defer instantiation of the body. - - @li Invoke algorithms which operate on the header only. + A `header` includes the start-line and header-fields. */ template -struct header +struct header : Fields { + static_assert(is_fields::value, + "Fields requirements not met"); + /// Indicates if the header is a request or response. - static bool constexpr is_request = false; + using is_request = std::false_type; /// The type representing the fields. using fields_type = Fields; @@ -149,16 +235,16 @@ struct header This holds both the major and minor version numbers, using these formulas: @code - major = version / 10; - minor = version % 10; + unsigned major = version / 10; + unsigned minor = version % 10; @endcode + + Newly constructed headers will use HTTP/1.1 by default + unless otherwise specified. */ - int version; + unsigned version = 11; - /// The HTTP field values. - fields_type fields; - - /// Default constructor + /// Default constructor. header() = default; /// Move constructor @@ -173,45 +259,143 @@ struct header /// Copy assignment header& operator=(header const&) = default; - /** Construct the header. + /** Constructor - All arguments are forwarded to the constructor - of the `fields` member. + @param args Arguments forwarded to the `Fields` + base class constructor. - @note This constructor participates in overload resolution - if and only if the first parameter is not convertible to - `header`. + @note This constructor participates in overload + resolution if and only if the first parameter is + not convertible to @ref header, @ref verb, or + @ref status. */ template 0) || ! std::is_convertible< - typename std::decay::type, - header>::value>::type> + ! std::is_convertible::type, status>::value && + ! std::is_convertible::type, header>::value + >::type> explicit - header(Arg1&& arg1, ArgN&&... argn) - : fields(std::forward(arg1), - std::forward(argn)...) - { - } + header(Arg1&& arg1, ArgN&&... argn); #endif - /** The Response Status-Code. + /** The response status-code result. - @note This field is present only if `isRequest == false`. + If the actual status code is not a known code, this + function returns @ref status::unknown. Use @ref result_int + to return the raw status code as a number. + + @note This member is only available when `isRequest == false`. */ - int status; + status + result() const; - /** The Response Reason-Phrase. + /** Set the response status-code. - The Reason-Phrase is obsolete as of rfc7230. + @param v The code to set. - @note This field is present only if `isRequest == false`. + @note This member is only available when `isRequest == false`. */ - std::string reason; + void + result(status v); + + /** Set the response status-code as an integer. + + This sets the status code to the exact number passed in. + If the number does not correspond to one of the known + status codes, the function @ref result will return + @ref status::unknown. Use @ref result_int to obtain the + original raw status-code. + + @param v The status-code integer to set. + + @throws std::invalid_argument if `v > 999`. + */ + void + result(unsigned v); + + /** The response status-code expressed as an integer. + + This returns the raw status code as an integer, even + when that code is not in the list of known status codes. + + @note This member is only available when `isRequest == false`. + */ + unsigned + result_int() const; + + /** Return the response reason-phrase. + + The reason-phrase is obsolete as of rfc7230. + + @note This function is only available when `isRequest == false`. + */ + string_view + reason() const; + + /** Set the response reason-phrase (deprecated) + + This function sets a custom reason-phrase to a copy of + the string passed in. Normally it is not necessary to set + the reason phrase on an outgoing response object; the + implementation will automatically use the standard reason + text for the corresponding status code. + + To clear a previously set custom phrase, pass an empty + string. This will restore the default standard reason text + based on the status code used when serializing. + + The reason-phrase is obsolete as of rfc7230. + + @param s The string to use for the reason-phrase. + + @note This function is only available when `isRequest == false`. + */ + void + reason(string_view s); + +private: +#if ! BEAST_DOXYGEN + template + friend struct message; + + template + friend + void + swap(header& m1, header& m2); + + template + header( + status result, + unsigned version_, + FieldsArgs&&... fields_args) + : Fields(std::forward(fields_args)...) + , version(version_) + , result_(result) + { + } + + status result_ = status::ok; +#endif }; +/// A typical HTTP request header +template +using request_header = header; + +/// A typical HTTP response header +template +using response_header = header; + /** A container for a complete HTTP message. + This container is derived from the `Fields` template type. + To understand all of the members of this class it is necessary + to view the declaration for the `Fields` type. When using + the default fields container, those declarations are in + @ref fields. + A message can be a request or response, depending on the `isRequest` template argument value. Requests and responses have different types; functions may be overloaded based on @@ -220,6 +404,10 @@ struct header The `Body` template argument type determines the model used to read or write the content body of the message. + Newly constructed messages objects have version set to + HTTP/1.1. Newly constructed response objects also have + result code set to @ref status::ok. + @tparam isRequest `true` if this represents a request, or `false` if this represents a response. Some class data members are conditionally present depending on this value. @@ -229,11 +417,11 @@ struct header @tparam Fields The type of container used to hold the field value pairs. */ -template +template struct message : header { /// The base class used to hold the header portion of the message. - using base_type = header; + using header_type = header; /** The type providing the body traits. @@ -244,151 +432,384 @@ struct message : header /// A value representing the body. typename Body::value_type body; - /// Default constructor + /// Constructor message() = default; - /// Move constructor + /// Constructor message(message&&) = default; - /// Copy constructor + /// Constructor message(message const&) = default; - /// Move assignment + /// Assignment message& operator=(message&&) = default; - /// Copy assignment + /// Assignment message& operator=(message const&) = default; - /** Construct a message from a header. + /** Constructor - Additional arguments, if any, are forwarded to - the constructor of the body member. + @param h The header to move construct from. + + @param body_args Optional arguments forwarded + to the `body` constructor. */ - template + template explicit - message(base_type&& base, Args&&... args) - : base_type(std::move(base)) - , body(std::forward(args)...) - { - } + message(header_type&& h, BodyArgs&&... body_args); - /** Construct a message from a header. + /** Constructor. - Additional arguments, if any, are forwarded to - the constructor of the body member. + @param h The header to copy construct from. + + @param body_args Optional arguments forwarded + to the `body` constructor. */ - template + template explicit - message(base_type const& base, Args&&... args) - : base_type(base) - , body(std::forward(args)...) - { - } + message(header_type const& h, BodyArgs&&... body_args); - /** Construct a message. + /** Constructor - @param u An argument forwarded to the body constructor. + @param method The request-method to use - @note This constructor participates in overload resolution - only if `u` is not convertible to `base_type`. + @param target The request-target. + + @param version The HTTP-version + + @note This function is only available when `isRequest == true`. */ - template::type, base_type>::value>::type +#if BEAST_DOXYGEN + message(verb method, string_view target, unsigned version); +#else + template::value>::type> + message(verb method, string_view target, Version version); #endif - > - explicit - message(U&& u) - : body(std::forward(u)) - { - } - /** Construct a message. + /** Constructor - @param u An argument forwarded to the body constructor. + @param method The request-method to use - @param v An argument forwarded to the fields constructor. + @param target The request-target. - @note This constructor participates in overload resolution - only if `u` is not convertible to `base_type`. + @param version The HTTP-version + + @param body_arg An argument forwarded to the `body` constructor. + + @note This function is only available when `isRequest == true`. */ - template::type, base_type>::value>::type +#if BEAST_DOXYGEN + template + message(verb method, string_view target, + unsigned version, BodyArg&& body_arg); +#else + template::value>::type> + message(verb method, string_view target, + Version version, BodyArg&& body_arg); #endif - > - message(U&& u, V&& v) - : base_type(std::forward(v)) - , body(std::forward(u)) - { - } + + /** Constructor + + @param method The request-method to use + + @param target The request-target. + + @param version The HTTP-version + + @param body_arg An argument forwarded to the `body` constructor. + + @param fields_arg An argument forwarded to the `Fields` constructor. + + @note This function is only available when `isRequest == true`. + */ +#if BEAST_DOXYGEN + template + message(verb method, string_view target, unsigned version, + BodyArg&& body_arg, FieldsArg&& fields_arg); +#else + template::value>::type> + message(verb method, string_view target, Version version, + BodyArg&& body_arg, FieldsArg&& fields_arg); +#endif + + /** Constructor + + @param result The status-code for the response + + @param version The HTTP-version + + @note This member is only available when `isRequest == false`. + */ +#if BEAST_DOXYGEN + message(status result, unsigned version); +#else + template::value>::type> + message(status result, Version version); +#endif + + /** Constructor + + @param result The status-code for the response + + @param version The HTTP-version + + @param body_arg An argument forwarded to the `body` constructor. + + @note This member is only available when `isRequest == false`. + */ +#if BEAST_DOXYGEN + template + message(status result, unsigned version, BodyArg&& body_arg); +#else + template::value>::type> + message(status result, Version version, BodyArg&& body_arg); +#endif + + /** Constructor + + @param result The status-code for the response + + @param version The HTTP-version + + @param body_arg An argument forwarded to the `body` constructor. + + @param fields_arg An argument forwarded to the `Fields` base class constructor. + + @note This member is only available when `isRequest == false`. + */ +#if BEAST_DOXYGEN + template + message(status result, unsigned version, + BodyArg&& body_arg, FieldsArg&& fields_arg); +#else + template::value>::type> + message(status result, Version version, + BodyArg&& body_arg, FieldsArg&& fields_arg); +#endif + + /** Constructor + + The header and body are default-constructed. + */ + explicit + message(std::piecewise_construct_t); /** Construct a message. - @param un A tuple forwarded as a parameter pack to the body constructor. + @param body_args A tuple forwarded as a parameter + pack to the body constructor. */ - template - message(std::piecewise_construct_t, std::tuple un) - : message(std::piecewise_construct, un, - beast::detail::make_index_sequence{}) - { - } - - /** Construct a message. - - @param un A tuple forwarded as a parameter pack to the body constructor. - - @param vn A tuple forwarded as a parameter pack to the fields constructor. - */ - template + template message(std::piecewise_construct_t, - std::tuple&& un, std::tuple&& vn) - : message(std::piecewise_construct, un, vn, - beast::detail::make_index_sequence{}, - beast::detail::make_index_sequence{}) - { - } + std::tuple body_args); + + /** Construct a message. + + @param body_args A tuple forwarded as a parameter + pack to the body constructor. + + @param fields_args A tuple forwarded as a parameter + pack to the `Fields` constructor. + */ + template + message(std::piecewise_construct_t, + std::tuple body_args, + std::tuple fields_args); /// Returns the header portion of the message - base_type& - base() - { - return *this; - } - - /// Returns the header portion of the message - base_type const& + header_type const& base() const { return *this; } -private: - template - message(std::piecewise_construct_t, - std::tuple& tu, beast::detail::index_sequence) - : body(std::forward(std::get(tu))...) + /// Returns the header portion of the message + header_type& + base() { + return *this; } - template - message(std::piecewise_construct_t, - std::tuple& tu, std::tuple& tv, - beast::detail::index_sequence, - beast::detail::index_sequence) - : base_type(std::forward(std::get(tv))...) - , body(std::forward(std::get(tu))...) + /// Returns `true` if the chunked Transfer-Encoding is specified + bool + chunked() const { + return this->get_chunked_impl(); } + + /** Set or clear the chunked Transfer-Encoding + + This function will set or removed the "chunked" transfer + encoding as the last item in the list of encodings in the + field. + + If the result of removing the chunked token results in an + empty string, the field is erased. + + The Content-Length field is erased unconditionally. + */ + void + chunked(bool value); + + /** Set or clear the Content-Length field + + This function adjusts the Content-Length field as follows: + + @li If `value` specifies a value, the Content-Length field + is set to the value. Otherwise + + @li The Content-Length field is erased. + + If "chunked" token appears as the last item in the + Transfer-Encoding field it is unconditionally removed. + + @param value The value to set for Content-Length. + */ + void + content_length(boost::optional const& value); + + /** Returns `true` if the message semantics indicate keep-alive + + The value depends on the version in the message, which must + be set to the final value before this function is called or + else the return value is unreliable. + */ + bool + keep_alive() const + { + return this->get_keep_alive_impl(this->version); + } + + /** Set the keep-alive message semantic option + + This function adjusts the Connection field to indicate + whether or not the connection should be kept open after + the corresponding response. The result depends on the + version set on the message, which must be set to the + final value before making this call. + + @param value `true` if the connection should persist. + */ + void + keep_alive(bool value) + { + this->set_keep_alive_impl(this->version, value); + } + + /** Returns the payload size of the body in octets if possible. + + This function invokes the @b Body algorithm to measure + the number of octets in the serialized body container. If + there is no body, this will return zero. Otherwise, if the + body exists but is not known ahead of time, `boost::none` + is returned (usually indicating that a chunked Transfer-Encoding + will be used). + + @note The value of the Content-Length field in the message + is not inspected. + */ + boost::optional + payload_size() const; + + /** Prepare the message payload fields for the body. + + This function will adjust the Content-Length and + Transfer-Encoding field values based on the properties + of the body. + + @par Example + @code + request req{verb::post, "/"}; + req.set(field::user_agent, "Beast"); + req.body = "Hello, world!"; + req.prepare_payload(); + @endcode + */ + void + prepare_payload() + { + prepare_payload(typename header_type::is_request{}); + } + +private: + static_assert(is_body::value, + "Body requirements not met"); + + template< + class... BodyArgs, + std::size_t... IBodyArgs> + message( + std::piecewise_construct_t, + std::tuple& body_args, + beast::detail::index_sequence) + : body(std::forward( + std::get(body_args))...) + { + boost::ignore_unused(body_args); + } + + template< + class... BodyArgs, + class... FieldsArgs, + std::size_t... IBodyArgs, + std::size_t... IFieldsArgs> + message( + std::piecewise_construct_t, + std::tuple& body_args, + std::tuple& fields_args, + beast::detail::index_sequence, + beast::detail::index_sequence) + : header_type(std::forward( + std::get(fields_args))...) + , body(std::forward( + std::get(body_args))...) + { + boost::ignore_unused(body_args); + boost::ignore_unused(fields_args); + } + + boost::optional + payload_size(std::true_type) const + { + return Body::size(body); + } + + boost::optional + payload_size(std::false_type) const + { + return boost::none; + } + + void + prepare_payload(std::true_type); + + void + prepare_payload(std::false_type); }; +/// A typical HTTP request +template +using request = message; + +/// A typical HTTP response +template +using response = message; + //------------------------------------------------------------------------------ -#if GENERATING_DOCS +#if BEAST_DOXYGEN /** Swap two header objects. @par Requirements @@ -412,71 +833,6 @@ swap( message& m1, message& m2); -/// A typical HTTP request header -using request_header = header; - -/// Typical HTTP response header -using response_header = header; - -/// A typical HTTP request -template -using request = message; - -/// A typical HTTP response -template -using response = message; - -//------------------------------------------------------------------------------ - -/** Returns `true` if the HTTP/1 message indicates a keep alive. - - Undefined behavior if version is greater than 11. -*/ -template -bool -is_keep_alive(header const& msg); - -/** Returns `true` if the HTTP/1 message indicates an Upgrade request or response. - - Undefined behavior if version is greater than 11. -*/ -template -bool -is_upgrade(header const& msg); - -/** HTTP/1 connection prepare options. - - @note These values are used with @ref prepare. -*/ -enum class connection -{ - /// Specify Connection: close. - close, - - /// Specify Connection: keep-alive where possible. - keep_alive, - - /// Specify Connection: upgrade. - upgrade -}; - -/** Prepare a HTTP message. - - This function will adjust the Content-Length, Transfer-Encoding, - and Connection fields of the message based on the properties of - the body and the options passed in. - - @param msg The message to prepare. The fields may be modified. - - @param options A list of prepare options. -*/ -template< - bool isRequest, class Body, class Fields, - class... Options> -void -prepare(message& msg, - Options&&... options); - } // http } // beast diff --git a/src/beast/include/beast/http/parse.hpp b/src/beast/include/beast/http/parse.hpp deleted file mode 100644 index bcc8b0f521..0000000000 --- a/src/beast/include/beast/http/parse.hpp +++ /dev/null @@ -1,155 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_HTTP_PARSE_HPP -#define BEAST_HTTP_PARSE_HPP - -#include -#include -#include - -namespace beast { -namespace http { - -/** Parse an object from a stream. - - This function synchronously reads from a stream and passes - data to the specified parser. The call will block until one - of the following conditions are met: - - @li The parser indicates that parsing is complete. - - @li An error occurs in the stream or parser. - - This function is implemented in terms of one or more calls - to the stream's `read_some` function. The implementation may - read additional octets that lie past the end of the object - being parsed. This additional data is stored in the stream - buffer, which may be used in subsequent calls. - - @note This algorithm is generic, and not specific to HTTP - messages. It is up to the parser to determine what predicate - defines a complete operation. - - @param stream The stream from which the data is to be read. - The type must support the @b SyncReadStream concept. - - @param dynabuf A @b DynamicBuffer holding additional bytes - read by the implementation from the stream. This is both - an input and an output parameter; on entry, any data in the - stream buffer's input sequence will be given to the parser - first. - - @param parser An object meeting the requirements of @b Parser - which will receive the data. - - @throws system_error Thrown on failure. -*/ -template -void -parse(SyncReadStream& stream, - DynamicBuffer& dynabuf, Parser& parser); - -/** Parse an object from a stream. - - This function synchronously reads from a stream and passes - data to the specified parser. The call will block until one - of the following conditions are met: - - @li The parser indicates that parsing is complete. - - @li An error occurs in the stream or parser. - - This function is implemented in terms of one or more calls - to the stream's `read_some` function. The implementation may - read additional octets that lie past the end of the object - being parsed. This additional data is stored in the stream - buffer, which may be used in subsequent calls. - - @note This algorithm is generic, and not specific to HTTP - messages. It is up to the parser to determine what predicate - defines a complete operation. - - @param stream The stream from which the data is to be read. - The type must support the @b SyncReadStream concept. - - @param dynabuf A @b DynamicBuffer holding additional bytes - read by the implementation from the stream. This is both - an input and an output parameter; on entry, any data in the - stream buffer's input sequence will be given to the parser - first. - - @param parser An object meeting the requirements of @b Parser - which will receive the data. - - @param ec Set to the error, if any occurred. -*/ -template -void -parse(SyncReadStream& stream, - DynamicBuffer& dynabuf, Parser& parser, error_code& ec); - -/** Start an asynchronous operation to parse an object from a stream. - - This function is used to asynchronously read from a stream and - pass the data to the specified parser. The function call always - returns immediately. The asynchronous operation will continue - until one of the following conditions is true: - - @li The parser indicates that parsing is complete. - - @li An error occurs in the stream or parser. - - This operation is implemented in terms of one or more calls to - the next layer's `async_read_some` function, and is known as a - composed operation. The program must ensure that the - stream performs no other operations until this operation completes. - The implementation may read additional octets that lie past the - end of the object being parsed. This additional data is stored - in the stream buffer, which may be used in subsequent calls. - - @param stream The stream from which the data is to be read. - The type must support the @b AsyncReadStream concept. - - @param dynabuf A @b DynamicBuffer holding additional bytes - read by the implementation from the stream. This is both - an input and an output parameter; on entry, any data in the - stream buffer's input sequence will be given to the parser - first. - - @param parser An object meeting the requirements of @b Parser - which will receive the data. This object must remain valid - until the completion handler is invoked. - - @param handler The handler to be called when the request - completes. Copies will be made of the handler as required. - The equivalent function signature of the handler must be: - @code void handler( - error_code const& error // result of operation - ); @endcode - Regardless of whether the asynchronous operation completes - immediately or not, the handler will not be invoked from within - this function. Invocation of the handler will be performed in a - manner equivalent to using `boost::asio::io_service::post`. -*/ -template -#if GENERATING_DOCS -void_or_deduced -#else -typename async_completion< - ReadHandler, void(error_code)>::result_type -#endif -async_parse(AsyncReadStream& stream, DynamicBuffer& dynabuf, - Parser& parser, ReadHandler&& handler); - -} // http -} // beast - -#include - -#endif diff --git a/src/beast/include/beast/http/parse_error.hpp b/src/beast/include/beast/http/parse_error.hpp deleted file mode 100644 index 7b0dc08c0a..0000000000 --- a/src/beast/include/beast/http/parse_error.hpp +++ /dev/null @@ -1,48 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_HTTP_PARSE_ERROR_HPP -#define BEAST_HTTP_PARSE_ERROR_HPP - -#include -#include - -namespace beast { -namespace http { - -enum class parse_error -{ - connection_closed = 1, - - bad_method, - bad_uri, - bad_version, - bad_crlf, - - bad_status, - bad_reason, - - bad_field, - bad_value, - bad_content_length, - illegal_content_length, - - invalid_chunk_size, - invalid_ext_name, - invalid_ext_val, - - header_too_big, - body_too_big, - short_read -}; - -} // http -} // beast - -#include - -#endif diff --git a/src/beast/include/beast/http/parser.hpp b/src/beast/include/beast/http/parser.hpp new file mode 100644 index 0000000000..108223ef3c --- /dev/null +++ b/src/beast/include/beast/http/parser.hpp @@ -0,0 +1,322 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_PARSER_HPP +#define BEAST_HTTP_PARSER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +/** An HTTP/1 parser for producing a message. + + This class uses the basic HTTP/1 wire format parser to convert + a series of octets into a @ref message using the @ref basic_fields + container to represent the fields. + + @tparam isRequest Indicates whether a request or response + will be parsed. + + @tparam Body The type used to represent the body. This must + meet the requirements of @b Body. + + @tparam Allocator The type of allocator used with the + @ref basic_fields container. + + @note A new instance of the parser is required for each message. +*/ +template< + bool isRequest, + class Body, + class Allocator = std::allocator> +class parser + : public basic_parser> +{ + static_assert(is_body::value, + "Body requirements not met"); + + static_assert(is_body_writer::value, + "BodyWriter requirements not met"); + + template + friend class parser; + + using base_type = basic_parser>; + + message> m_; + typename Body::writer wr_; + std::function cb_; + bool wr_inited_ = false; + +public: + /// The type of message returned by the parser + using value_type = + message>; + + /// Constructor + parser(); + + /// Copy constructor (disallowed) + parser(parser const&) = delete; + + /// Copy assignment (disallowed) + parser& operator=(parser const&) = delete; + + /** Move constructor. + + After the move, the only valid operation + on the moved-from object is destruction. + */ + parser(parser&& other) = default; + + /** Constructor + + @param args Optional arguments forwarded to the + @ref http::header constructor. + + @note This function participates in overload + resolution only if the first argument is not a + @ref parser. + */ +#if BEAST_DOXYGEN + template + explicit + parser(Args&&... args); +#else + template::type>::value>::type> + explicit + parser(Arg1&& arg1, ArgN&&... argn); +#endif + + /** Construct a parser from another parser, changing the Body type. + + This constructs a new parser by move constructing the + header from another parser with a different body type. The + constructed-from parser must not have any parsed body octets or + initialized @b BodyWriter, otherwise an exception is generated. + + @par Example + @code + // Deferred body type commitment + request_parser req0; + ... + request_parser req{std::move(req0)}; + @endcode + + If an exception is thrown, the state of the constructed-from + parser is undefined. + + @param parser The other parser to construct from. After + this call returns, the constructed-from parser may only + be destroyed. + + @param args Optional arguments forwarded to the message + constructor. + + @throws std::invalid_argument Thrown when the constructed-from + parser has already initialized a body writer. + + @note This function participates in overload resolution only + if the other parser uses a different body type. + */ +#if BEAST_DOXYGEN + template +#else + template::value>::type> +#endif + explicit + parser(parser&& parser, Args&&... args); + + /** Returns the parsed message. + + Depending on the parser's progress, + parts of this object may be incomplete. + */ + value_type const& + get() const + { + return m_; + } + + /** Returns the parsed message. + + Depending on the parser's progress, + parts of this object may be incomplete. + */ + value_type& + get() + { + return m_; + } + + /** Returns ownership of the parsed message. + + Ownership is transferred to the caller. + Depending on the parser's progress, + parts of this object may be incomplete. + + @par Requires + + @ref value_type is @b MoveConstructible + */ + value_type + release() + { + static_assert(std::is_move_constructible::value, + "MoveConstructible requirements not met"); + return std::move(m_); + } + + /** Set the on_header callback. + + When the callback is set, it is called after the parser + receives a complete header. The function must be invocable with + this signature: + @code + void callback( + parser& p, // `*this` + error_code& ec) // Set to the error, if any + @endcode + The callback will ensure that `!ec` is `true` if there was + no error or set to the appropriate error code if there was one. + + The callback may not call @ref put or @ref put_eof, or + else the behavior is undefined. + */ + void + on_header(std::function cb) + { + cb_ = std::move(cb); + } + +private: + friend class basic_parser; + + void + on_request(verb method, string_view method_str, + string_view target, int version, error_code& ec) + { + try + { + m_.target(target); + if(method != verb::unknown) + m_.method(method); + else + m_.method_string(method_str); + ec.assign(0, ec.category()); + } + catch(std::bad_alloc const&) + { + ec = error::bad_alloc; + } + m_.version = version; + } + + void + on_response(int code, + string_view reason, + int version, error_code& ec) + { + m_.result(code); + m_.version = version; + try + { + m_.reason(reason); + ec.assign(0, ec.category()); + } + catch(std::bad_alloc const&) + { + ec = error::bad_alloc; + } + } + + void + on_field(field name, string_view name_string, + string_view value, error_code& ec) + { + try + { + m_.insert(name, name_string, value); + ec.assign(0, ec.category()); + } + catch(std::bad_alloc const&) + { + ec = error::bad_alloc; + } + } + + void + on_header(error_code& ec) + { + if(cb_) + cb_(*this, ec); + else + ec.assign(0, ec.category()); + } + + void + on_body(boost::optional< + std::uint64_t> const& content_length, + error_code& ec) + { + wr_.init(content_length, ec); + wr_inited_ = true; + } + + std::size_t + on_data(string_view s, error_code& ec) + { + return wr_.put(boost::asio::buffer( + s.data(), s.size()), ec); + } + + void + on_chunk(std::uint64_t, + string_view, error_code& ec) + { + ec.assign(0, ec.category()); + } + + void + on_complete(error_code& ec) + { + wr_.finish(ec); + } +}; + +/// An HTTP/1 parser for producing a request message. +template> +using request_parser = parser; + +/// An HTTP/1 parser for producing a response message. +template> +using response_parser = parser; + +} // http +} // beast + +#include + +#endif diff --git a/src/beast/include/beast/http/parser_v1.hpp b/src/beast/include/beast/http/parser_v1.hpp deleted file mode 100644 index be1aed1e2d..0000000000 --- a/src/beast/include/beast/http/parser_v1.hpp +++ /dev/null @@ -1,339 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_HTTP_PARSER_V1_HPP -#define BEAST_HTTP_PARSER_V1_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace http { - -/** Skip body option. - - The options controls whether or not the parser expects to see a - HTTP body, regardless of the presence or absence of certain fields - such as Content-Length. - - Depending on the request, some responses do not carry a body. - For example, a 200 response to a CONNECT request from a tunneling - proxy. In these cases, callers use the @ref skip_body option to - inform the parser that no body is expected. The parser will consider - the message complete after the header has been received. - - Example: - @code - parser_v1 p; - p.set_option(skip_body{true}); - @endcode - - @note Objects of this type are passed to @ref parser_v1::set_option. -*/ -struct skip_body -{ - bool value; - - explicit - skip_body(bool v) - : value(v) - { - } -}; - -/** A parser for producing HTTP/1 messages. - - This class uses the basic HTTP/1 wire format parser to convert - a series of octets into a `message`. - - @note A new instance of the parser is required for each message. -*/ -template -class parser_v1 - : public basic_parser_v1> - , private std::conditional::type -{ -public: - /// The type of message this parser produces. - using message_type = - message; - -private: - using reader = - typename message_type::body_type::reader; - - static_assert(is_Body::value, - "Body requirements not met"); - static_assert(has_reader::value, - "Body has no reader"); - static_assert(is_Reader::value, - "Reader requirements not met"); - - std::string field_; - std::string value_; - message_type m_; - boost::optional r_; - std::uint8_t skip_body_ = 0; - bool flush_ = false; - -public: - /// Default constructor - parser_v1() = default; - - /// Move constructor - parser_v1(parser_v1&&) = default; - - /// Copy constructor (disallowed) - parser_v1(parser_v1 const&) = delete; - - /// Move assignment (disallowed) - parser_v1& operator=(parser_v1&&) = delete; - - /// Copy assignment (disallowed) - parser_v1& operator=(parser_v1 const&) = delete; - - /** Construct the parser. - - @param args Forwarded to the message constructor. - - @note This function participates in overload resolution only - if the first argument is not a parser or fields parser. - */ -#if GENERATING_DOCS - template - explicit - parser_v1(Args&&... args); -#else - template::type, - header_parser_v1>::value && - ! std::is_same::type, parser_v1>::value - >::type> - explicit - parser_v1(Arg1&& arg1, ArgN&&... argn) - : m_(std::forward(arg1), - std::forward(argn)...) - { - } -#endif - - /** Construct the parser from a fields parser. - - @param parser The fields parser to construct from. - @param args Forwarded to the message body constructor. - */ - template - explicit - parser_v1(header_parser_v1& parser, - Args&&... args) - : m_(parser.release(), std::forward(args)...) - { - static_cast>&>(*this) = parser; - } - - /// Set the skip body option. - void - set_option(skip_body const& o) - { - skip_body_ = o.value ? 1 : 0; - } - - /** Returns the parsed message. - - Only valid if @ref complete would return `true`. - */ - message_type const& - get() const - { - return m_; - } - - /** Returns the parsed message. - - Only valid if @ref complete would return `true`. - */ - message_type& - get() - { - return m_; - } - - /** Returns ownership of the parsed message. - - Ownership is transferred to the caller. Only - valid if @ref complete would return `true`. - - Requires: - `message` is @b MoveConstructible - */ - message_type - release() - { - static_assert(std::is_move_constructible::value, - "MoveConstructible requirements not met"); - return std::move(m_); - } - -private: - friend class basic_parser_v1; - - void flush() - { - if(! flush_) - return; - flush_ = false; - BOOST_ASSERT(! field_.empty()); - m_.fields.insert(field_, value_); - field_.clear(); - value_.clear(); - } - - void on_start(error_code&) - { - } - - void on_method(boost::string_ref const& s, error_code&) - { - this->method_.append(s.data(), s.size()); - } - - void on_uri(boost::string_ref const& s, error_code&) - { - this->uri_.append(s.data(), s.size()); - } - - void on_reason(boost::string_ref const& s, error_code&) - { - this->reason_.append(s.data(), s.size()); - } - - void on_request_or_response(std::true_type) - { - m_.method = std::move(this->method_); - m_.url = std::move(this->uri_); - } - - void on_request_or_response(std::false_type) - { - m_.status = this->status_code(); - m_.reason = std::move(this->reason_); - } - - void on_request(error_code&) - { - on_request_or_response( - std::integral_constant{}); - } - - void on_response(error_code&) - { - on_request_or_response( - std::integral_constant{}); - } - - void on_field(boost::string_ref const& s, error_code&) - { - flush(); - field_.append(s.data(), s.size()); - } - - void on_value(boost::string_ref const& s, error_code&) - { - value_.append(s.data(), s.size()); - flush_ = true; - } - - void - on_header(std::uint64_t, error_code&) - { - flush(); - m_.version = 10 * this->http_major() + this->http_minor(); - } - - body_what - on_body_what(std::uint64_t, error_code& ec) - { - if(skip_body_) - return body_what::skip; - r_.emplace(m_); - r_->init(ec); - return body_what::normal; - } - - void on_body(boost::string_ref const& s, error_code& ec) - { - r_->write(s.data(), s.size(), ec); - } - - void on_complete(error_code&) - { - } -}; - -/** Create a new parser from a fields parser. - - Associates a Body type with a fields parser, and returns - a new parser which parses a complete message object - containing the original message fields and a new body - of the specified body type. - - This function allows HTTP messages to be parsed in two stages. - First, the fields are parsed and control is returned. Then, - the caller can choose at run-time, the type of Body to - associate with the message. And finally, complete the parse - in a second call. - - @param parser The fields parser to construct from. Ownership - of the message fields in the fields parser is transferred - as if by call to @ref header_parser_v1::release. - - @param args Forwarded to the body constructor of the message - in the new parser. - - @return A parser for a message with the specified @b Body type. - - @par Example - @code - headers_parser ph; - ... - auto p = with_body(ph); - ... - message m = p.release(); - @endcode -*/ -template -parser_v1 -with_body(header_parser_v1& parser, - Args&&... args) -{ - return parser_v1( - parser, std::forward(args)...); -} - -} // http -} // beast - -#endif diff --git a/src/beast/include/beast/http/read.hpp b/src/beast/include/beast/http/read.hpp index 54dd57963e..72fea92ff7 100644 --- a/src/beast/include/beast/http/read.hpp +++ b/src/beast/include/beast/http/read.hpp @@ -9,267 +9,717 @@ #define BEAST_HTTP_READ_HPP #include -#include +#include #include +#include #include namespace beast { namespace http { -/** Read a HTTP/1 header from a stream. +/** Read part of a message from a stream using a parser. - This function is used to synchronously read a header - from a stream. The call blocks until one of the following - conditions is true: + This function is used to read part of a message from a stream into a + subclass of @ref basic_parser. + The call will block until one of the following conditions is true: - @li An entire header is read in. + @li A call to @ref basic_parser::put with a non-empty buffer sequence + is successful. - @li An error occurs in the stream or parser. + @li An error occurs. - This function is implemented in terms of one or more calls - to the stream's `read_some` function. The implementation may - read additional octets that lie past the end of the message - fields being parsed. This additional data is stored in the - stream buffer, which may be used in subsequent calls. + This operation is implemented in terms of one or + more calls to the stream's `read_some` function. + The implementation may read additional octets that lie past the + end of the message being read. This additional data is stored + in the dynamic buffer, which must be retained for subsequent reads. - If the message corresponding to the header being received - contains a message body, it is the callers responsibility - to cause the body to be read in before attempting to read - the next message. + If the stream returns the error `boost::asio::error::eof` indicating the + end of file during a read, the error returned from this function will be: + + @li @ref error::end_of_stream if no octets were parsed, or + + @li @ref error::partial_message if any octets were parsed but the + message was incomplete, otherwise: + + @li A successful result. A subsequent attempt to read will + return @ref error::end_of_stream @param stream The stream from which the data is to be read. - The type must support the @b `SyncReadStream` concept. + The type must support the @b SyncReadStream concept. - @param dynabuf A @b `DynamicBuffer` holding additional bytes + @param buffer A @b DynamicBuffer holding additional bytes read by the implementation from the stream. This is both an input and an output parameter; on entry, any data in the - stream buffer's input sequence will be given to the parser + dynamic buffer's input sequence will be given to the parser first. - @param msg An object used to store the header. Any contents - will be overwritten. The type must support copy assignment - or move assignment. + @param parser The parser to use. @throws system_error Thrown on failure. */ -template +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> void -read(SyncReadStream& stream, DynamicBuffer& dynabuf, - header& msg); +read_some( + SyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser); -/** Read a HTTP/1 header from a stream. +/** Read part of a message from a stream using a parser. - This function is used to synchronously read a header - from a stream. The call blocks until one of the following - conditions is true: + This function is used to read part of a message from a stream into a + subclass of @ref basic_parser. + The call will block until one of the following conditions is true: - @li An entire header is read in. + @li A call to @ref basic_parser::put with a non-empty buffer sequence + is successful. - @li An error occurs in the stream or parser. + @li An error occurs. - This function is implemented in terms of one or more calls - to the stream's `read_some` function. The implementation may - read additional octets that lie past the end of the message - fields being parsed. This additional data is stored in the - stream buffer, which may be used in subsequent calls. + This operation is implemented in terms of one or + more calls to the stream's `read_some` function. + The implementation may read additional octets that lie past the + end of the message being read. This additional data is stored + in the dynamic buffer, which must be retained for subsequent reads. - If the message corresponding to the header being received - contains a message body, it is the callers responsibility - to cause the body to be read in before attempting to read - the next message. + If the stream returns the error `boost::asio::error::eof` indicating the + end of file during a read, the error returned from this function will be: + + @li @ref error::end_of_stream if no octets were parsed, or + + @li @ref error::partial_message if any octets were parsed but the + message was incomplete, otherwise: + + @li A successful result. A subsequent attempt to read will + return @ref error::end_of_stream + + The function returns the number of bytes processed from the dynamic + buffer. The caller should remove these bytes by calling `consume` on + the dynamic buffer, regardless of any error. @param stream The stream from which the data is to be read. - The type must support the @b `SyncReadStream` concept. + The type must support the @b SyncReadStream concept. - @param dynabuf A @b `DynamicBuffer` holding additional bytes + @param buffer A @b DynamicBuffer holding additional bytes read by the implementation from the stream. This is both an input and an output parameter; on entry, any data in the - stream buffer's input sequence will be given to the parser + dynamic buffer's input sequence will be given to the parser first. - @param msg An object used to store the header. Any contents - will be overwritten. The type must support copy assignment - or move assignment. + @param parser The parser to use. @param ec Set to the error, if any occurred. */ -template +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> void -read(SyncReadStream& stream, DynamicBuffer& dynabuf, - header& msg, - error_code& ec); +read_some( + SyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser, + error_code& ec); -/** Read a HTTP/1 header asynchronously from a stream. +/** Read part of a message asynchronously from a stream using a parser. - This function is used to asynchronously read a header from - a stream. The function call always returns immediately. The - asynchronous operation will continue until one of the following - conditions is true: + This function is used to asynchronously read part of a message from + a stream into a subclass of @ref basic_parser. + The function call always returns immediately. The asynchronous operation + will continue until one of the following conditions is true: - @li An entire header is read in. + @li A call to @ref basic_parser::put with a non-empty buffer sequence + is successful. - @li An error occurs in the stream or parser. + @li An error occurs. - This operation is implemented in terms of one or more calls to - the stream's `async_read_some` function, and is known as a + This operation is implemented in terms of zero or more calls to + the next layer's `async_read_some` function, and is known as a composed operation. The program must ensure that the stream performs no other operations until this operation completes. The implementation may read additional octets that lie past the - end of the message fields being parsed. This additional data is - stored in the stream buffer, which may be used in subsequent calls. - - If the message corresponding to the header being received - contains a message body, it is the callers responsibility - to cause the body to be read in before attempting to read - the next message. - - @param stream The stream to read the message from. - The type must support the @b `AsyncReadStream` concept. - - @param dynabuf A @b `DynamicBuffer` holding additional bytes - read by the implementation from the stream. This is both - an input and an output parameter; on entry, any data in the - stream buffer's input sequence will be given to the parser - first. - - @param msg An object used to store the header. Any contents - will be overwritten. The type must support copy assignment or - move assignment. The object must remain valid at least until - the completion handler is called; ownership is not transferred. - - @param handler The handler to be called when the operation - completes. Copies will be made of the handler as required. - The equivalent function signature of the handler must be: - @code void handler( - error_code const& error // result of operation - ); @endcode - Regardless of whether the asynchronous operation completes - immediately or not, the handler will not be invoked from within - this function. Invocation of the handler will be performed in a - manner equivalent to using `boost::asio::io_service::post`. -*/ -template -#if GENERATING_DOCS -void_or_deduced -#else -typename async_completion< - ReadHandler, void(error_code)>::result_type -#endif -async_read(AsyncReadStream& stream, DynamicBuffer& dynabuf, - header& msg, - ReadHandler&& handler); - -/** Read a HTTP/1 message from a stream. - - This function is used to synchronously read a message from - a stream. The call blocks until one of the following conditions - is true: - - @li A complete message is read in. - - @li An error occurs in the stream or parser. - - This function is implemented in terms of one or more calls - to the stream's `read_some` function. The implementation may - read additional octets that lie past the end of the message - being parsed. This additional data is stored in the stream - buffer, which may be used in subsequent calls. - - @param stream The stream from which the data is to be read. - The type must support the @b `SyncReadStream` concept. - - @param dynabuf A @b `DynamicBuffer` holding additional bytes - read by the implementation from the stream. This is both - an input and an output parameter; on entry, any data in the - stream buffer's input sequence will be given to the parser - first. - - @param msg An object used to store the message. Any - contents will be overwritten. The type must support - copy assignment or move assignment. - - @throws system_error Thrown on failure. -*/ -template -void -read(SyncReadStream& stream, DynamicBuffer& dynabuf, - message& msg); - -/** Read a HTTP/1 message from a stream. - - This function is used to synchronously read a message from - a stream. The call blocks until one of the following conditions - is true: - - @li A complete message is read in. - - @li An error occurs in the stream or parser. - - This function is implemented in terms of one or more calls - to the stream's `read_some` function. The implementation may - read additional octets that lie past the end of the message - being parsed. This additional data is stored in the stream - buffer, which may be used in subsequent calls. - - @param stream The stream from which the data is to be read. - The type must support the @b `SyncReadStream` concept. - - @param dynabuf A @b `DynamicBuffer` holding additional bytes - read by the implementation from the stream. This is both - an input and an output parameter; on entry, any data in the - stream buffer's input sequence will be given to the parser - first. - - @param msg An object used to store the message. Any - contents will be overwritten. The type must support - copy assignment or move assignment. - - @param ec Set to the error, if any occurred. -*/ -template -void -read(SyncReadStream& stream, DynamicBuffer& dynabuf, - message& msg, - error_code& ec); - -/** Read a HTTP/1 message asynchronously from a stream. - - This function is used to asynchronously read a message from - a stream. The function call always returns immediately. The - asynchronous operation will continue until one of the following - conditions is true: - - @li A complete message is read in. - - @li An error occurs in the stream or parser. - - This operation is implemented in terms of one or more calls to - the stream's `async_read_some` function, and is known as a - composed operation. The program must ensure that the - stream performs no other operations until this operation completes. - The implementation may read additional octets that lie past the - end of the message being parsed. This additional data is stored + end of the object being parsed. This additional data is stored in the stream buffer, which may be used in subsequent calls. - @param stream The stream to read the message from. - The type must support the @b `AsyncReadStream` concept. + If the stream returns the error `boost::asio::error::eof` indicating the + end of file during a read, the error returned from this function will be: - @param dynabuf A @b `DynamicBuffer` holding additional bytes + @li @ref error::end_of_stream if no octets were parsed, or + + @li @ref error::partial_message if any octets were parsed but the + message was incomplete, otherwise: + + @li A successful result. A subsequent attempt to read will + return @ref error::end_of_stream + + @param stream The stream from which the data is to be read. + The type must support the @b AsyncReadStream concept. + + @param buffer A @b DynamicBuffer holding additional bytes read by the implementation from the stream. This is both an input and an output parameter; on entry, any data in the - stream buffer's input sequence will be given to the parser + dynamic buffer's input sequence will be given to the parser first. - @param msg An object used to store the header. Any contents - will be overwritten. The type must support copy assignment or - move assignment. The object must remain valid at least until - the completion handler is called; ownership is not transferred. + @param parser The parser to use. + The object must remain valid at least until the + handler is called; ownership is not transferred. + + @param handler The handler to be called when the request + completes. Copies will be made of the handler as required. + The equivalent function signature of the handler must be: + @code void handler( + error_code const& error // result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using `boost::asio::io_service::post`. + + The completion handler will receive as a parameter the number + of octets processed from the dynamic buffer. The octets should + be removed by calling `consume` on the dynamic buffer after + the read completes, regardless of any error. +*/ +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived, + class ReadHandler> +#if BEAST_DOXYGEN + void_or_deduced +#else +async_return_type< + ReadHandler, void(error_code)> +#endif +async_read_some( + AsyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser, + ReadHandler&& handler); + +//------------------------------------------------------------------------------ + +/** Read a header from a stream using a parser. + + This function is used to read a header from a stream into a subclass + of @ref basic_parser. + The call will block until one of the following conditions is true: + + @li @ref basic_parser::is_header_done returns `true` + + @li An error occurs. + + This operation is implemented in terms of one or + more calls to the stream's `read_some` function. + The implementation may read additional octets that lie past the + end of the message being read. This additional data is stored + in the dynamic buffer, which must be retained for subsequent reads. + + If the stream returns the error `boost::asio::error::eof` indicating the + end of file during a read, the error returned from this function will be: + + @li @ref error::end_of_stream if no octets were parsed, or + + @li @ref error::partial_message if any octets were parsed but the + message was incomplete, otherwise: + + @li A successful result. A subsequent attempt to read will + return @ref error::end_of_stream + + @param stream The stream from which the data is to be read. + The type must support the @b SyncReadStream concept. + + @param buffer A @b DynamicBuffer holding additional bytes + read by the implementation from the stream. This is both + an input and an output parameter; on entry, any data in the + dynamic buffer's input sequence will be given to the parser + first. + + @param parser The parser to use. + + @throws system_error Thrown on failure. + + @note The implementation will call @ref basic_parser::eager + with the value `false` on the parser passed in. +*/ +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> +void +read_header( + SyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser); + +/** Read a header from a stream using a parser. + + This function is used to read a header from a stream into a subclass + of @ref basic_parser. + The call will block until one of the following conditions is true: + + @li @ref basic_parser::is_header_done returns `true` + + @li An error occurs. + + This operation is implemented in terms of one or + more calls to the stream's `read_some` function. + The implementation may read additional octets that lie past the + end of the message being read. This additional data is stored + in the dynamic buffer, which must be retained for subsequent reads. + + If the stream returns the error `boost::asio::error::eof` indicating the + end of file during a read, the error returned from this function will be: + + @li @ref error::end_of_stream if no octets were parsed, or + + @li @ref error::partial_message if any octets were parsed but the + message was incomplete, otherwise: + + @li A successful result. A subsequent attempt to read will + return @ref error::end_of_stream + + @param stream The stream from which the data is to be read. + The type must support the @b SyncReadStream concept. + + @param buffer A @b DynamicBuffer holding additional bytes + read by the implementation from the stream. This is both + an input and an output parameter; on entry, any data in the + dynamic buffer's input sequence will be given to the parser + first. + + @param parser The parser to use. + + @param ec Set to the error, if any occurred. + + @note The implementation will call @ref basic_parser::eager + with the value `false` on the parser passed in. +*/ +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> +void +read_header( + SyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser, + error_code& ec); + +/** Read a header from a stream asynchronously using a parser. + + This function is used to asynchronously read a header from a stream + into a subclass of @ref basic_parser. + The function call always returns immediately. The asynchronous operation + will continue until one of the following conditions is true: + + @li @ref basic_parser::is_header_done returns `true` + + @li An error occurs. + + This operation is implemented in terms of one or more calls to + the stream's `async_read_some` function, and is known as a + composed operation. The program must ensure that the + stream performs no other operations until this operation completes. + The implementation may read additional octets that lie past the + end of the message being read. This additional data is stored + in the dynamic buffer, which must be retained for subsequent reads. + + If the stream returns the error `boost::asio::error::eof` indicating the + end of file during a read, the error returned from this function will be: + + @li @ref error::end_of_stream if no octets were parsed, or + + @li @ref error::partial_message if any octets were parsed but the + message was incomplete, otherwise: + + @li A successful result. A subsequent attempt to read will + return @ref error::end_of_stream + + @param stream The stream from which the data is to be read. + The type must support the @b AsyncReadStream concept. + + @param buffer A @b DynamicBuffer holding additional bytes + read by the implementation from the stream. This is both + an input and an output parameter; on entry, any data in the + dynamic buffer's input sequence will be given to the parser + first. + + @param parser The parser to use. + The object must remain valid at least until the + handler is called; ownership is not transferred. + + @param handler The handler to be called when the request + completes. Copies will be made of the handler as required. + The equivalent function signature of the handler must be: + @code void handler( + error_code const& error // result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using `boost::asio::io_service::post`. + + @note The implementation will call @ref basic_parser::eager + with the value `false` on the parser passed in. +*/ +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived, + class ReadHandler> +#if BEAST_DOXYGEN + void_or_deduced +#else +async_return_type< + ReadHandler, void(error_code)> +#endif +async_read_header( + AsyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser, + ReadHandler&& handler); + +//------------------------------------------------------------------------------ + +/** Read a complete message from a stream using a parser. + + This function is used to read a complete message from a stream into a + subclass of @ref basic_parser. + The call will block until one of the following conditions is true: + + @li @ref basic_parser::is_done returns `true` + + @li An error occurs. + + This operation is implemented in terms of one or + more calls to the stream's `read_some` function. + The implementation may read additional octets that lie past the + end of the message being read. This additional data is stored + in the dynamic buffer, which must be retained for subsequent reads. + + If the stream returns the error `boost::asio::error::eof` indicating the + end of file during a read, the error returned from this function will be: + + @li @ref error::end_of_stream if no octets were parsed, or + + @li @ref error::partial_message if any octets were parsed but the + message was incomplete, otherwise: + + @li A successful result. A subsequent attempt to read will + return @ref error::end_of_stream + + @param stream The stream from which the data is to be read. + The type must support the @b SyncReadStream concept. + + @param buffer A @b DynamicBuffer holding additional bytes + read by the implementation from the stream. This is both + an input and an output parameter; on entry, any data in the + dynamic buffer's input sequence will be given to the parser + first. + + @param parser The parser to use. + + @throws system_error Thrown on failure. + + @note The implementation will call @ref basic_parser::eager + with the value `true` on the parser passed in. +*/ +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> +void +read( + SyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser); + +/** Read a complete message from a stream using a parser. + + This function is used to read a complete message from a stream into a + subclass of @ref basic_parser. + The call will block until one of the following conditions is true: + + @li @ref basic_parser::is_done returns `true` + + @li An error occurs. + + This operation is implemented in terms of one or + more calls to the stream's `read_some` function. + The implementation may read additional octets that lie past the + end of the message being read. This additional data is stored + in the dynamic buffer, which must be retained for subsequent reads. + + If the stream returns the error `boost::asio::error::eof` indicating the + end of file during a read, the error returned from this function will be: + + @li @ref error::end_of_stream if no octets were parsed, or + + @li @ref error::partial_message if any octets were parsed but the + message was incomplete, otherwise: + + @li A successful result. A subsequent attempt to read will + return @ref error::end_of_stream + + @param stream The stream from which the data is to be read. + The type must support the @b SyncReadStream concept. + + @param buffer A @b DynamicBuffer holding additional bytes + read by the implementation from the stream. This is both + an input and an output parameter; on entry, any data in the + dynamic buffer's input sequence will be given to the parser + first. + + @param parser The parser to use. + + @param ec Set to the error, if any occurred. + + @note The implementation will call @ref basic_parser::eager + with the value `true` on the parser passed in. +*/ +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> +void +read( + SyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser, + error_code& ec); + +/** Read a complete message from a stream asynchronously using a parser. + + This function is used to asynchronously read a complete message from a + stream into a subclass of @ref basic_parser. + The function call always returns immediately. The asynchronous operation + will continue until one of the following conditions is true: + + @li @ref basic_parser::is_done returns `true` + + @li An error occurs. + + This operation is implemented in terms of one or more calls to + the stream's `async_read_some` function, and is known as a + composed operation. The program must ensure that the + stream performs no other operations until this operation completes. + The implementation may read additional octets that lie past the + end of the message being read. This additional data is stored + in the dynamic buffer, which must be retained for subsequent reads. + + If the stream returns the error `boost::asio::error::eof` indicating the + end of file during a read, the error returned from this function will be: + + @li @ref error::end_of_stream if no octets were parsed, or + + @li @ref error::partial_message if any octets were parsed but the + message was incomplete, otherwise: + + @li A successful result. A subsequent attempt to read will + return @ref error::end_of_stream + + @param stream The stream from which the data is to be read. + The type must support the @b AsyncReadStream concept. + + @param buffer A @b DynamicBuffer holding additional bytes + read by the implementation from the stream. This is both + an input and an output parameter; on entry, any data in the + dynamic buffer's input sequence will be given to the parser + first. + + @param parser The parser to use. + The object must remain valid at least until the + handler is called; ownership is not transferred. + + @param handler The handler to be called when the request + completes. Copies will be made of the handler as required. + The equivalent function signature of the handler must be: + @code void handler( + error_code const& error // result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using `boost::asio::io_service::post`. + + @note The implementation will call @ref basic_parser::eager + with the value `true` on the parser passed in. +*/ +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived, + class ReadHandler> +#if BEAST_DOXYGEN + void_or_deduced +#else +async_return_type< + ReadHandler, void(error_code)> +#endif +async_read( + AsyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser, + ReadHandler&& handler); + +//------------------------------------------------------------------------------ + +/** Read a complete message from a stream. + + This function is used to read a complete message from a stream using HTTP/1. + The call will block until one of the following conditions is true: + + @li The entire message is read. + + @li An error occurs. + + This operation is implemented in terms of one or + more calls to the stream's `read_some` function. + The implementation may read additional octets that lie past the + end of the message being read. This additional data is stored + in the dynamic buffer, which must be retained for subsequent reads. + + If the stream returns the error `boost::asio::error::eof` indicating the + end of file during a read, the error returned from this function will be: + + @li @ref error::end_of_stream if no octets were parsed, or + + @li @ref error::partial_message if any octets were parsed but the + message was incomplete, otherwise: + + @li A successful result. A subsequent attempt to read will + return @ref error::end_of_stream + + @param stream The stream from which the data is to be read. + The type must support the @b SyncReadStream concept. + + @param buffer A @b DynamicBuffer holding additional bytes + read by the implementation from the stream. This is both + an input and an output parameter; on entry, any data in the + dynamic buffer's input sequence will be given to the parser + first. + + @param msg An object in which to store the message contents. + This object should not have previous contents, otherwise + the behavior is undefined. + The type must be @b MoveAssignable and @b MoveConstructible. + + @throws system_error Thrown on failure. +*/ +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Body, class Allocator> +void +read( + SyncReadStream& stream, + DynamicBuffer& buffer, + message>& msg); + +/** Read a complete message from a stream. + + This function is used to read a complete message from a stream using HTTP/1. + The call will block until one of the following conditions is true: + + @li The entire message is read. + + @li An error occurs. + + This operation is implemented in terms of one or + more calls to the stream's `read_some` function. + The implementation may read additional octets that lie past the + end of the message being read. This additional data is stored + in the dynamic buffer, which must be retained for subsequent reads. + + If the stream returns the error `boost::asio::error::eof` indicating the + end of file during a read, the error returned from this function will be: + + @li @ref error::end_of_stream if no octets were parsed, or + + @li @ref error::partial_message if any octets were parsed but the + message was incomplete, otherwise: + + @li A successful result. A subsequent attempt to read will + return @ref error::end_of_stream + + @param stream The stream from which the data is to be read. + The type must support the @b SyncReadStream concept. + + @param buffer A @b DynamicBuffer holding additional bytes + read by the implementation from the stream. This is both + an input and an output parameter; on entry, any data in the + dynamic buffer's input sequence will be given to the parser + first. + + @param msg An object in which to store the message contents. + This object should not have previous contents, otherwise + the behavior is undefined. + The type must be @b MoveAssignable and @b MoveConstructible. + + @param ec Set to the error, if any occurred. +*/ +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Body, class Allocator> +void +read( + SyncReadStream& stream, + DynamicBuffer& buffer, + message>& msg, + error_code& ec); + +/** Read a complete message from a stream asynchronously. + + This function is used to asynchronously read a complete message from a + stream using HTTP/1. + The function call always returns immediately. The asynchronous operation + will continue until one of the following conditions is true: + + @li The entire message is read. + + @li An error occurs. + + This operation is implemented in terms of one or more calls to + the stream's `async_read_some` function, and is known as a + composed operation. The program must ensure that the + stream performs no other operations until this operation completes. + The implementation may read additional octets that lie past the + end of the message being read. This additional data is stored + in the dynamic buffer, which must be retained for subsequent reads. + + If the stream returns the error `boost::asio::error::eof` indicating the + end of file during a read, the error returned from this function will be: + + @li @ref error::end_of_stream if no octets were parsed, or + + @li @ref error::partial_message if any octets were parsed but the + message was incomplete, otherwise: + + @li A successful result. A subsequent attempt to read will + return @ref error::end_of_stream + + @param stream The stream from which the data is to be read. + The type must support the @b AsyncReadStream concept. + + @param buffer A @b DynamicBuffer holding additional bytes + read by the implementation from the stream. This is both + an input and an output parameter; on entry, any data in the + dynamic buffer's input sequence will be given to the parser + first. + + @param msg An object in which to store the message contents. + This object should not have previous contents, otherwise + the behavior is undefined. + The type must be @b MoveAssignable and @b MoveConstructible. + + The object must remain valid at least until the + handler is called; ownership is not transferred. @param handler The handler to be called when the operation completes. Copies will be made of the handler as required. @@ -282,18 +732,22 @@ read(SyncReadStream& stream, DynamicBuffer& dynabuf, this function. Invocation of the handler will be performed in a manner equivalent to using `boost::asio::io_service::post`. */ -template -#if GENERATING_DOCS -void_or_deduced +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, class Body, class Allocator, + class ReadHandler> +#if BEAST_DOXYGEN + void_or_deduced #else -typename async_completion< - ReadHandler, void(error_code)>::result_type +async_return_type< + ReadHandler, void(error_code)> #endif -async_read(AsyncReadStream& stream, DynamicBuffer& dynabuf, - message& msg, - ReadHandler&& handler); +async_read( + AsyncReadStream& stream, + DynamicBuffer& buffer, + message>& msg, + ReadHandler&& handler); } // http } // beast diff --git a/src/beast/include/beast/http/reason.hpp b/src/beast/include/beast/http/reason.hpp deleted file mode 100644 index fce97e9d1f..0000000000 --- a/src/beast/include/beast/http/reason.hpp +++ /dev/null @@ -1,85 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_HTTP_REASON_HPP -#define BEAST_HTTP_REASON_HPP - -#include - -namespace beast { -namespace http { - -namespace detail { - -template -char const* -reason_string(int status) -{ - switch(status) - { - case 100: return "Continue"; - case 101: return "Switching Protocols"; - case 200: return "OK"; - case 201: return "Created"; - case 202: return "Accepted"; - case 203: return "Non-Authoritative Information"; - case 204: return "No Content"; - case 205: return "Reset Content"; - case 206: return "Partial Content"; - case 300: return "Multiple Choices"; - case 301: return "Moved Permanently"; - case 302: return "Found"; - case 303: return "See Other"; - case 304: return "Not Modified"; - case 305: return "Use Proxy"; - case 307: return "Temporary Redirect"; - case 400: return "Bad Request"; - case 401: return "Unauthorized"; - case 402: return "Payment Required"; - case 403: return "Forbidden"; - case 404: return "Not Found"; - case 405: return "Method Not Allowed"; - case 406: return "Not Acceptable"; - case 407: return "Proxy Authentication Required"; - case 408: return "Request Timeout"; - case 409: return "Conflict"; - case 410: return "Gone"; - case 411: return "Length Required"; - case 412: return "Precondition Failed"; - case 413: return "Request Entity Too Large"; - case 414: return "Request-URI Too Long"; - case 415: return "Unsupported Media Type"; - case 416: return "Requested Range Not Satisfiable"; - case 417: return "Expectation Failed"; - case 500: return "Internal Server Error"; - case 501: return "Not Implemented"; - case 502: return "Bad Gateway"; - case 503: return "Service Unavailable"; - case 504: return "Gateway Timeout"; - case 505: return "HTTP Version Not Supported"; - - case 306: return ""; - default: - break; - } - return ""; -} - -} // detail - -/** Returns the text for a known status code integer. */ -inline -char const* -reason_string(int status) -{ - return detail::reason_string(status); -} - -} // http -} // beast - -#endif diff --git a/src/beast/include/beast/http/rfc7230.hpp b/src/beast/include/beast/http/rfc7230.hpp index 1f73194db1..e2d3f3fb29 100644 --- a/src/beast/include/beast/http/rfc7230.hpp +++ b/src/beast/include/beast/http/rfc7230.hpp @@ -10,13 +10,14 @@ #include #include +#include namespace beast { namespace http { -/** A list of parameters in a HTTP extension field value. +/** A list of parameters in an HTTP extension field value. - This container allows iteration of the parameter list in a HTTP + This container allows iteration of the parameter list in an HTTP extension. The parameter list is a series of name/value pairs with each pair starting with a semicolon. The value is optional. @@ -48,7 +49,7 @@ namespace http { */ class param_list { - boost::string_ref s_; + string_view s_; public: /** The type of each element in the list. @@ -58,10 +59,10 @@ public: be empty). */ using value_type = - std::pair; + std::pair; /// A constant iterator to the list -#if GENERATING_DOCS +#if BEAST_DOXYGEN using const_iterator = implementation_defined; #else class const_iterator; @@ -76,7 +77,7 @@ public: must remain valid for the lifetime of the container. */ explicit - param_list(boost::string_ref const& s) + param_list(string_view s) : s_(s) { } @@ -98,7 +99,7 @@ public: /** A list of extensions in a comma separated HTTP field value. - This container allows iteration of the extensions in a HTTP + This container allows iteration of the extensions in an HTTP field value. The extension list is a comma separated list of token parameter list pairs. @@ -136,9 +137,9 @@ public: */ class ext_list { - using iter_type = boost::string_ref::const_iterator; + using iter_type = string_view::const_iterator; - boost::string_ref s_; + string_view s_; public: /** The type of each element in the list. @@ -147,10 +148,10 @@ public: second element of the pair is an iterable container holding the extension's name/value parameters. */ - using value_type = std::pair; + using value_type = std::pair; /// A constant iterator to the list -#if GENERATING_DOCS +#if BEAST_DOXYGEN using const_iterator = implementation_defined; #else class const_iterator; @@ -162,7 +163,7 @@ public: must remain valid for the lifetime of the container. */ explicit - ext_list(boost::string_ref const& s) + ext_list(string_view s) : s_(s) { } @@ -229,16 +230,16 @@ public: */ class token_list { - using iter_type = boost::string_ref::const_iterator; + using iter_type = string_view::const_iterator; - boost::string_ref s_; + string_view s_; public: /// The type of each element in the token list. - using value_type = boost::string_ref; + using value_type = string_view; /// A constant iterator to the list -#if GENERATING_DOCS +#if BEAST_DOXYGEN using const_iterator = implementation_defined; #else class const_iterator; @@ -250,7 +251,7 @@ public: must remain valid for the lifetime of the container. */ explicit - token_list(boost::string_ref const& s) + token_list(string_view s) : s_(s) { } @@ -276,6 +277,46 @@ public: exists(T const& s); }; +/** A list of tokens in a comma separated HTTP field value. + + This container allows iteration of a list of items in a + header field value. The input is a comma separated list of + tokens. + + If a parsing error is encountered while iterating the string, + the behavior of the container will be as if a string containing + only characters up to but excluding the first invalid character + was used to construct the list. + + @par BNF + @code + token-list = *( "," OWS ) token *( OWS "," [ OWS token ] ) + @endcode + + To use this class, construct with the string to be parsed and + then use `begin` and `end`, or range-for to iterate each item: + + @par Example + @code + for(auto const& token : token_list{"apple, pear, banana"}) + std::cout << token << "\n"; + @endcode +*/ +using opt_token_list = + detail::basic_parsed_list< + detail::opt_token_list_policy>; + +/** Returns `true` if a parsed list is parsed without errors. + + This function iterates a single pass through a parsed list + and returns `true` if there were no parsing errors, else + returns `false`. +*/ +template +bool +validate_list(detail::basic_parsed_list< + Policy> const& list); + } // http } // beast diff --git a/src/beast/include/beast/http/serializer.hpp b/src/beast/include/beast/http/serializer.hpp new file mode 100644 index 0000000000..34bf1d83bf --- /dev/null +++ b/src/beast/include/beast/http/serializer.hpp @@ -0,0 +1,502 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_SERIALIZER_HPP +#define BEAST_HTTP_SERIALIZER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef BEAST_NO_BIG_VARIANTS +# if defined(BOOST_GCC) && BOOST_GCC < 50000 && BOOST_VERSION < 106400 +# define BEAST_NO_BIG_VARIANTS +# endif +#endif + +namespace beast { +namespace http { + +/** A chunk decorator which does nothing. + + When selected as a chunk decorator, objects of this type + affect the output of messages specifying chunked + transfer encodings as follows: + + @li chunk headers will have empty chunk extensions, and + + @li final chunks will have an empty set of trailers. + + @see @ref serializer +*/ +struct no_chunk_decorator +{ + template + string_view + operator()(ConstBufferSequence const&) const + { + return {}; + } + + string_view + operator()(boost::asio::null_buffers) const + { + return {}; + } +}; + +/** Provides buffer oriented HTTP message serialization functionality. + + An object of this type is used to serialize a complete + HTTP message into a sequence of octets. To use this class, + construct an instance with the message to be serialized. + + The implementation will automatically perform chunk encoding + if the contents of the message indicate that chunk encoding + is required. If the semantics of the message indicate that + the connection should be closed after the message is sent, the + function @ref keep_alive will return `true`. + + Upon construction, an optional chunk decorator may be + specified. This decorator is a function object called with + each buffer sequence of the body when the chunked transfer + encoding is indicate in the message header. The decorator + will be called with an empty buffer sequence (actually + the type `boost::asio::null_buffers`) to indicate the + final chunk. The decorator may return a string which forms + the chunk extension for chunks, and the field trailers + for the final chunk. + + In C++11 the decorator must be declared as a class or + struct with a templated operator() thusly: + + @code + // The implementation guarantees that operator() + // will be called only after the view returned by + // any previous calls to operator() are no longer + // needed. The decorator instance is intended to + // manage the lifetime of the storage for all returned + // views. + // + struct decorator + { + // Returns the chunk-extension for each chunk, + // or an empty string for no chunk extension. The + // buffer must include the leading semicolon (";") + // and follow the format for chunk extensions defined + // in rfc7230. + // + template + string_view + operator()(ConstBufferSequence const&) const; + + // Returns a set of field trailers for the final chunk. + // Each field should be formatted according to rfc7230 + // including the trailing "\r\n" for each field. If + // no trailers are indicated, an empty string is returned. + // + string_view + operator()(boost::asio::null_buffers) const; + }; + @endcode + + @tparam isRequest `true` if the message is a request. + + @tparam Body The body type of the message. + + @tparam Fields The type of fields in the message. + + @tparam ChunkDecorator The type of chunk decorator to use. +*/ +template< + bool isRequest, + class Body, + class Fields = fields, + class ChunkDecorator = no_chunk_decorator> +class serializer +{ +public: + static_assert(is_body::value, + "Body requirements not met"); + + static_assert(is_body_reader::value, + "BodyReader requirements not met"); + + /** The type of message this serializer uses + + This may be const or non-const depending on the + implementation of the corresponding @b BodyReader. + */ +#if BEAST_DOXYGEN + using value_type = implementation_defined; +#else + using value_type = + typename std::conditional< + std::is_constructible&>::value && + ! std::is_constructible const&>::value, + message, + message const>::type; +#endif + +private: + enum + { + do_construct = 0, + + do_init = 10, + do_header_only = 20, + do_header = 30, + do_body = 40, + + do_init_c = 50, + do_header_only_c = 60, + do_header_c = 70, + do_body_c = 80, + do_final_c = 90, + #ifndef BEAST_NO_BIG_VARIANTS + do_body_final_c = 100, + do_all_c = 110, + #endif + + do_complete = 120 + }; + + void frdinit(std::true_type); + void frdinit(std::false_type); + + template + void + do_visit(error_code& ec, Visit& visit); + + using reader = typename Body::reader; + + using cb1_t = consuming_buffers; // header + using pcb1_t = buffer_prefix_view; + + using cb2_t = consuming_buffers>; // body + using pcb2_t = buffer_prefix_view; + + using cb3_t = consuming_buffers< + typename reader::const_buffers_type>; // body + using pcb3_t = buffer_prefix_view; + + using cb4_t = consuming_buffers>; // crlf + using pcb4_t = buffer_prefix_view; + + using cb5_t = consuming_buffers>; // crlf + using pcb5_t = buffer_prefix_view; + +#ifndef BEAST_NO_BIG_VARIANTS + using cb6_t = consuming_buffers>; // crlf + using pcb6_t = buffer_prefix_view; + + using cb7_t = consuming_buffers>; // crlf + using pcb7_t = buffer_prefix_view; +#endif + + using cb8_t = consuming_buffers>; // crlf + using pcb8_t = buffer_prefix_view; + + value_type& m_; + reader rd_; + boost::optional frd_; + boost::variant v_; + boost::variant pv_; + std::size_t limit_ = + (std::numeric_limits::max)(); + int s_ = do_construct; + bool split_ = false; + bool header_done_ = false; + bool chunked_; + bool keep_alive_; + bool more_; + ChunkDecorator d_; + +public: + /** Constructor + + The implementation guarantees that the message passed on + construction will not be accessed until the first call to + @ref next. This allows the message to be lazily created. + For example, if the header is filled in before serialization. + + @param msg A reference to the message to serialize, which must + remain valid for the lifetime of the serializer. Depending on + the type of Body used, this may or may not be a `const` reference. + + @note This function participates in overload resolution only if + Body::reader is constructible from a `const` message reference. + */ + explicit + serializer(value_type& msg); + + /** Constructor + + The implementation guarantees that the message passed on + construction will not be accessed until the first call to + @ref next. This allows the message to be lazily created. + For example, if the header is filled in before serialization. + + @param msg A reference to the message to serialize, which must + remain valid for the lifetime of the serializer. Depending on + the type of Body used, this may or may not be a `const` reference. + + @param decorator The decorator to use. + + @note This function participates in overload resolution only if + Body::reader is constructible from a `const` message reference. + */ + explicit + serializer(value_type& msg, ChunkDecorator const& decorator); + + /// Returns the message being serialized + value_type& + get() + { + return m_; + } + + /** Provides access to the associated @b BodyReader + + This function provides access to the instance of the reader + associated with the body and created by the serializer + upon construction. The behavior of accessing this object + is defined by the specification of the particular reader + and its associated body. + + @return A reference to the reader. + */ + reader& + reader_impl() + { + return rd_; + } + + /// Returns the serialized buffer size limit + std::size_t + limit() + { + return limit_; + } + + /** Set the serialized buffer size limit + + This function adjusts the limit on the maximum size of the + buffers passed to the visitor. The new size limit takes effect + in the following call to @ref next. + + The default is no buffer size limit. + + @param limit The new buffer size limit. If this number + is zero, the size limit is removed. + */ + void + limit(std::size_t limit) + { + limit_ = limit > 0 ? limit : + (std::numeric_limits::max)(); + } + + /** Returns `true` if we will pause after writing the complete header. + */ + bool + split() + { + return split_; + } + + /** Set whether the header and body are written separately. + + When the split feature is enabled, the implementation will + write only the octets corresponding to the serialized header + first. If the header has already been written, this function + will have no effect on output. + */ + void + split(bool v) + { + split_ = v; + } + + /** Return `true` if serialization of the header is complete. + + This function indicates whether or not all buffers containing + serialized header octets have been retrieved. + */ + bool + is_header_done() + { + return header_done_; + } + + /** Return `true` if serialization is complete. + + The operation is complete when all octets corresponding + to the serialized representation of the message have been + successfully retrieved. + */ + bool + is_done() + { + return s_ == do_complete; + } + + /** Return `true` if the serializer will apply chunk-encoding. + + This function may only be called if @ref is_header_done + would return `true`. + */ + bool + chunked() + { + return chunked_; + } + + /** Return `true` if Connection: keep-alive semantic is indicated. + + This function returns `true` if the semantics of the + message indicate that the connection should be kept open + after the serialized message has been transmitted. The + value depends on the HTTP version of the message, + the tokens in the Connection header, and the metadata + describing the payload body. + + Depending on the payload body, the end of the message may + be indicated by connection closuire. In order for the + recipient (if any) to receive a complete message, the + underlying stream or network connection must be closed + when this function returns `false`. + + This function may only be called if @ref is_header_done + would return `true`. + */ + bool + keep_alive() + { + return keep_alive_; + } + + /** Returns the next set of buffers in the serialization. + + This function will attempt to call the `visit` function + object with a @b ConstBufferSequence of unspecified type + representing the next set of buffers in the serialization + of the message represented by this object. + + If there are no more buffers in the serialization, the + visit function will not be called. In this case, no error + will be indicated, and the function @ref is_done will + return `true`. + + @param ec Set to the error, if any occurred. + + @param visit The function to call. The equivalent function + signature of this object must be: + @code + template + void visit(error_code&, ConstBufferSequence const&); + @endcode + The function is not copied, if no error occurs it will be + invoked before the call to @ref next returns. + + */ + template + void + next(error_code& ec, Visit&& visit); + + /** Consume buffer octets in the serialization. + + This function should be called after one or more octets + contained in the buffers provided in the prior call + to @ref next have been used. + + After a call to @ref consume, callers should check the + return value of @ref is_done to determine if the entire + message has been serialized. + + @param n The number of octets to consume. This number must + be greater than zero and no greater than the number of + octets in the buffers provided in the prior call to @ref next. + */ + void + consume(std::size_t n); +}; + +/// A serializer for HTTP/1 requests +template< + class Body, + class Fields = fields, + class ChunkDecorator = no_chunk_decorator> +using request_serializer = serializer; + +/// A serializer for HTTP/1 responses +template< + class Body, + class Fields = fields, + class ChunkDecorator = no_chunk_decorator> +using response_serializer = serializer; + +} // http +} // beast + +#include + +#endif diff --git a/src/beast/include/beast/http/span_body.hpp b/src/beast/include/beast/http/span_body.hpp new file mode 100644 index 0000000000..8e83cde201 --- /dev/null +++ b/src/beast/include/beast/http/span_body.hpp @@ -0,0 +1,167 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_SPAN_BODY_HPP +#define BEAST_HTTP_SPAN_BODY_HPP + +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +/** A @b Body using @ref span + + This body uses @ref span as a memory-based container for + holding message payloads. The container represents a + non-owning reference to a continguous area of memory. + Messages using this body type may be serialized and + parsed. + + Unlike @ref buffer_body, only one buffer may be provided + during a parse or serialize operation. +*/ +template +struct span_body +{ +private: + static_assert(std::is_pod::value, + "POD requirements not met"); + +public: + /** The type of container used for the body + + This determines the type of @ref message::body + when this body type is used with a message container. + */ + using value_type = span; + + /** Returns the payload size of the body + + When this body is used with @ref message::prepare_payload, + the Content-Length will be set to the payload size, and + any chunked Transfer-Encoding will be removed. + */ + static + std::uint64_t + size(value_type const& body) + { + return body.size(); + } + + /** The algorithm for serializing the body + + Meets the requirements of @b BodyReader. + */ +#if BEAST_DOXYGEN + using reader = implementation_defined; +#else + class reader + { + value_type const& body_; + + public: + using const_buffers_type = + boost::asio::const_buffers_1; + + template + explicit + reader(message const& msg) + : body_(msg.body) + { + } + + void + init(error_code& ec) + { + ec.assign(0, ec.category()); + } + + boost::optional> + get(error_code& ec) + { + ec.assign(0, ec.category()); + return {{ + { body_.data(), + body_.size() * sizeof(typename + value_type::value_type)}, + false}}; + } + }; +#endif + + /** The algorithm for parsing the body + + Meets the requirements of @b BodyReader. + */ +#if BEAST_DOXYGEN + using writer = implementation_defined; +#else + class writer + { + value_type& body_; + + public: + template + explicit + writer(message& m) + : body_(m.body) + { + } + + void + init(boost::optional< + std::uint64_t> const& length, error_code& ec) + { + if(length && *length > body_.size()) + { + ec = error::buffer_overflow; + return; + } + ec.assign(0, ec.category()); + } + + template + std::size_t + put(ConstBufferSequence const& buffers, + error_code& ec) + { + using boost::asio::buffer_size; + using boost::asio::buffer_copy; + auto const n = buffer_size(buffers); + auto const len = body_.size(); + if(n > len) + { + ec = error::buffer_overflow; + return 0; + } + ec.assign(0, ec.category()); + buffer_copy(boost::asio::buffer( + body_.data(), n), buffers); + body_ = value_type{ + body_.data() + n, body_.size() - n}; + return n; + } + + void + finish(error_code& ec) + { + ec.assign(0, ec.category()); + } + }; +#endif +}; + +} // http +} // beast + +#endif diff --git a/src/beast/include/beast/http/status.hpp b/src/beast/include/beast/http/status.hpp new file mode 100644 index 0000000000..b202aa7bee --- /dev/null +++ b/src/beast/include/beast/http/status.hpp @@ -0,0 +1,163 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_STATUS_HPP +#define BEAST_HTTP_STATUS_HPP + +#include +#include +#include + +namespace beast { +namespace http { + +enum class status : unsigned +{ + /** An unknown status-code. + + This value indicates that the value for the status code + is not in the list of commonly recognized status codes. + Callers interested in the exactly value should use the + interface which provides the raw integer. + */ + unknown = 0, + + continue_ = 100, + switching_protocols = 101, + processing = 102, + + ok = 200, + created = 201, + accepted = 202, + non_authoritative_information = 203, + no_content = 204, + reset_content = 205, + partial_content = 206, + multi_status = 207, + already_reported = 208, + im_used = 226, + + multiple_choices = 300, + moved_permanently = 301, + found = 302, + see_other = 303, + not_modified = 304, + use_proxy = 305, + temporary_redirect = 307, + permanent_redirect = 308, + + bad_request = 400, + unauthorized = 401, + payment_required = 402, + forbidden = 403, + not_found = 404, + method_not_allowed = 405, + not_acceptable = 406, + proxy_authentication_required = 407, + request_timeout = 408, + conflict = 409, + gone = 410, + length_required = 411, + precondition_failed = 412, + payload_too_large = 413, + uri_too_long = 414, + unsupported_media_type = 415, + range_not_satisfiable = 416, + expectation_failed = 417, + misdirected_request = 421, + unprocessable_entity = 422, + locked = 423, + failed_dependency = 424, + upgrade_required = 426, + precondition_required = 428, + too_many_requests = 429, + request_header_fields_too_large = 431, + connection_closed_without_response = 444, + unavailable_for_legal_reasons = 451, + client_closed_request = 499, + + internal_server_error = 500, + not_implemented = 501, + bad_gateway = 502, + service_unavailable = 503, + gateway_timeout = 504, + http_version_not_supported = 505, + variant_also_negotiates = 506, + insufficient_storage = 507, + loop_detected = 508, + not_extended = 510, + network_authentication_required = 511, + network_connect_timeout_error = 599 +}; + +/** Represents the class of a status-code. +*/ +enum class status_class : unsigned +{ + /// Unknown status-class + unknown = 0, + + /// The request was received, continuing processing. + informational = 1, + + /// The request was successfully received, understood, and accepted. + successful = 2, + + /// Further action needs to be taken in order to complete the request. + redirection = 3, + + /// The request contains bad syntax or cannot be fulfilled. + client_error = 4, + + /// The server failed to fulfill an apparently valid request. + server_error = 5, +}; + +/** Converts the integer to a known status-code. + + If the integer does not match a known status code, + @ref status::unknown is returned. +*/ +status +int_to_status(unsigned v); + +/** Convert an integer to a status_class. + + @param v The integer representing a status code. + + @return The status class. If the integer does not match + a known status class, @ref status_class::unknown is returned. +*/ +status_class +to_status_class(unsigned v); + +/** Convert a status_code to a status_class. + + @param v The status code to convert. + + @return The status class. +*/ +status_class +to_status_class(status v); + +/** Returns the obsolete reason-phrase text for a status code. + + @param v The status code to use. +*/ +string_view +obsolete_reason(status v); + +/// Outputs the standard reason phrase of a status code to a stream. +std::ostream& +operator<<(std::ostream&, status); + +} // http +} // beast + +#include + +#endif diff --git a/src/beast/include/beast/http/streambuf_body.hpp b/src/beast/include/beast/http/streambuf_body.hpp deleted file mode 100644 index 974a3f3caf..0000000000 --- a/src/beast/include/beast/http/streambuf_body.hpp +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_HTTP_STREAMBUF_BODY_HPP -#define BEAST_HTTP_STREAMBUF_BODY_HPP - -#include -#include -#include - -namespace beast { -namespace http { - -/** A message body represented by a @ref streambuf - - Meets the requirements of @b `Body`. -*/ -using streambuf_body = basic_dynabuf_body; - -} // http -} // beast - -#endif diff --git a/src/beast/include/beast/http/string_body.hpp b/src/beast/include/beast/http/string_body.hpp index 707709c113..cdeaec7c76 100644 --- a/src/beast/include/beast/http/string_body.hpp +++ b/src/beast/include/beast/http/string_body.hpp @@ -9,92 +9,181 @@ #define BEAST_HTTP_STRING_BODY_HPP #include -#include +#include #include #include #include +#include +#include +#include #include +#include #include +#include namespace beast { namespace http { -/** A Body represented by a std::string. +/** A @b Body using `std::basic_string` - Meets the requirements of @b `Body`. + This body uses `std::basic_string` as a memory-based container + for holding message payloads. Messages using this body type + may be serialized and parsed. */ -struct string_body +template< + class CharT, + class Traits = std::char_traits, + class Allocator = std::allocator> +struct basic_string_body { - /// The type of the `message::body` member - using value_type = std::string; - -#if GENERATING_DOCS private: -#endif + static_assert( + std::is_integral::value && + sizeof(CharT) == 1, + "CharT requirements not met"); - class reader +public: + /** The type of container used for the body + + This determines the type of @ref message::body + when this body type is used with a message container. + */ + using value_type = + std::basic_string; + + /** Returns the payload size of the body + + When this body is used with @ref message::prepare_payload, + the Content-Length will be set to the payload size, and + any chunked Transfer-Encoding will be removed. + */ + static + std::uint64_t + size(value_type const& body) { - value_type& s_; + return body.size(); + } - public: - template - explicit - reader(message& m) noexcept - : s_(m.body) - { - } + /** The algorithm for serializing the body - void - init(error_code&) noexcept - { - } - - void - write(void const* data, - std::size_t size, error_code&) noexcept - { - auto const n = s_.size(); - s_.resize(n + size); - std::memcpy(&s_[n], data, size); - } - }; - - class writer + Meets the requirements of @b BodyReader. + */ +#if BEAST_DOXYGEN + using reader = implementation_defined; +#else + class reader { value_type const& body_; public: + using const_buffers_type = + boost::asio::const_buffers_1; + template explicit - writer(message< - isRequest, string_body, Fields> const& msg) noexcept + reader(message const& msg) : body_(msg.body) { } void - init(error_code& ec) noexcept + init(error_code& ec) { - beast::detail::ignore_unused(ec); + ec.assign(0, ec.category()); } - std::uint64_t - content_length() const noexcept + boost::optional> + get(error_code& ec) { - return body_.size(); - } - - template - bool - write(error_code&, WriteFunction&& wf) noexcept - { - wf(boost::asio::buffer(body_)); - return true; + ec.assign(0, ec.category()); + return {{const_buffers_type{ + body_.data(), body_.size()}, false}}; } }; +#endif + + /** The algorithm for parsing the body + + Meets the requirements of @b BodyReader. + */ +#if BEAST_DOXYGEN + using writer = implementation_defined; +#else + class writer + { + value_type& body_; + + public: + template + explicit + writer(message& m) + : body_(m.body) + { + } + + void + init(boost::optional< + std::uint64_t> const& length, error_code& ec) + { + if(length) + { + if(*length > ( + std::numeric_limits::max)()) + { + ec = error::buffer_overflow; + return; + } + try + { + body_.reserve( + static_cast(*length)); + } + catch(std::exception const&) + { + ec = error::buffer_overflow; + return; + } + } + ec.assign(0, ec.category()); + } + + template + std::size_t + put(ConstBufferSequence const& buffers, + error_code& ec) + { + using boost::asio::buffer_size; + using boost::asio::buffer_copy; + auto const n = buffer_size(buffers); + auto const len = body_.size(); + try + { + body_.resize(len + n); + } + catch(std::exception const&) + { + ec = error::buffer_overflow; + return 0; + } + ec.assign(0, ec.category()); + return buffer_copy(boost::asio::buffer( + &body_[0] + len, n), buffers); + } + + void + finish(error_code& ec) + { + ec.assign(0, ec.category()); + } + }; +#endif }; +/// A @b Body using `std::string` +using string_body = basic_string_body; + } // http } // beast diff --git a/src/beast/include/beast/http/type_traits.hpp b/src/beast/include/beast/http/type_traits.hpp new file mode 100644 index 0000000000..46f5674a96 --- /dev/null +++ b/src/beast/include/beast/http/type_traits.hpp @@ -0,0 +1,181 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_TYPE_TRAITS_HPP +#define BEAST_HTTP_TYPE_TRAITS_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +template +struct message; + +/** Determine if `T` meets the requirements of @b Body. + + This metafunction is equivalent to `std::true_type` + if `T` has a nested type named `value_type`. + + @tparam T The body type to test. + + @par Example + @code + template + void check_body(message const&) + { + static_assert(is_body::value, + "Body requirements not met"); + } + @endcode +*/ +template +#if BEAST_DOXYGEN +struct is_body : std::integral_constant{}; +#else +using is_body = detail::has_value_type; +#endif + +/** Determine if a @b Body type has a reader. + + This metafunction is equivalent to `std::true_type` if: + + @li `T` has a nested type named `reader` + + @li The nested type meets the requirements of @b BodyReader. + + @tparam T The body type to test. + + @par Example + @code + template + void check_can_serialize(message const&) + { + static_assert(is_body_reader::value, + "Cannot serialize Body, no reader"); + } + @endcode +*/ +#if BEAST_DOXYGEN +template +struct is_body_reader : std::integral_constant {}; +#else +template +struct is_body_reader : std::false_type {}; + +template +struct is_body_reader().init(std::declval()), + std::declval>&>() = + std::declval().get(std::declval()), + (void)0)>> : std::integral_constant::value && + std::is_constructible&>::value && + std::is_constructible&>::value + > {}; +#endif + +/** Determine if a @b Body type has a writer. + + This metafunction is equivalent to `std::true_type` if: + + @li `T` has a nested type named `writer` + + @li The nested type meets the requirements of @b BodyWriter. + + @tparam T The body type to test. + + @par Example + @code + template + void check_can_parse(message&) + { + static_assert(is_body_writer::value, + "Cannot parse Body, no writer"); + } + @endcode +*/ +#if BEAST_DOXYGEN +template +struct is_body_writer : std::integral_constant {}; +#else +template +struct is_body_writer : std::false_type {}; + +template +struct is_body_writer().init( + boost::optional(), + std::declval()), + std::declval() = + std::declval().put( + std::declval(), + std::declval()), + std::declval().finish( + std::declval()), + (void)0)>> : std::integral_constant&>::value && + std::is_constructible&>::value + > +{ +}; +#endif + +/** Determine if `T` meets the requirements of @b Fields + + @tparam T The body type to test. + + @par Example + + Use with `static_assert`: + + @code + template + void f(message const&) + { + static_assert(is_fields::value, + "Fields requirements not met"); + ... + @endcode + + Use with `std::enable_if` (SFINAE): + + @code + template + typename std::enable_if::value>::type + f(message const&); + @endcode +*/ +#if BEAST_DOXYGEN +template +struct is_fields : std::integral_constant {}; +#else +template +using is_fields = typename detail::is_fields_helper::type; +#endif + +} // http +} // beast + +#endif diff --git a/src/beast/include/beast/http/vector_body.hpp b/src/beast/include/beast/http/vector_body.hpp new file mode 100644 index 0000000000..36a19c4255 --- /dev/null +++ b/src/beast/include/beast/http/vector_body.hpp @@ -0,0 +1,182 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_VECTOR_BODY_HPP +#define BEAST_HTTP_VECTOR_BODY_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +/** A @b Body using `std::vector` + + This body uses `std::vector` as a memory-based container + for holding message payloads. Messages using this body type + may be serialized and parsed. +*/ +template> +struct vector_body +{ +private: + static_assert(sizeof(T) == 1 && + std::is_integral::value, + "T requirements not met"); + +public: + /** The type of container used for the body + + This determines the type of @ref message::body + when this body type is used with a message container. + */ + using value_type = std::vector; + + /** Returns the payload size of the body + + When this body is used with @ref message::prepare_payload, + the Content-Length will be set to the payload size, and + any chunked Transfer-Encoding will be removed. + */ + static + std::uint64_t + size(value_type const& body) + { + return body.size(); + } + + /** The algorithm for serializing the body + + Meets the requirements of @b BodyReader. + */ +#if BEAST_DOXYGEN + using reader = implementation_defined; +#else + class reader + { + value_type const& body_; + + public: + using const_buffers_type = + boost::asio::const_buffers_1; + + template + explicit + reader(message const& msg) + : body_(msg.body) + { + } + + void + init(error_code& ec) + { + ec.assign(0, ec.category()); + } + + boost::optional> + get(error_code& ec) + { + ec.assign(0, ec.category()); + return {{const_buffers_type{ + body_.data(), body_.size()}, false}}; + } + }; +#endif + + /** The algorithm for parsing the body + + Meets the requirements of @b BodyReader. + */ +#if BEAST_DOXYGEN + using writer = implementation_defined; +#else + class writer + { + value_type& body_; + + public: + template + explicit + writer(message& m) + : body_(m.body) + { + } + + void + init(boost::optional< + std::uint64_t> const& length, error_code& ec) + { + if(length) + { + if(*length > ( + std::numeric_limits::max)()) + { + ec = error::buffer_overflow; + return; + } + try + { + body_.reserve( + static_cast(*length)); + } + catch(std::exception const&) + { + ec = error::buffer_overflow; + return; + } + } + ec.assign(0, ec.category()); + } + + template + std::size_t + put(ConstBufferSequence const& buffers, + error_code& ec) + { + using boost::asio::buffer_size; + using boost::asio::buffer_copy; + auto const n = buffer_size(buffers); + auto const len = body_.size(); + try + { + body_.resize(len + n); + } + catch(std::exception const&) + { + ec = error::buffer_overflow; + return 0; + } + ec.assign(0, ec.category()); + return buffer_copy(boost::asio::buffer( + &body_[0] + len, n), buffers); + } + + void + finish(error_code& ec) + { + ec.assign(0, ec.category()); + } + }; +#endif +}; + +} // http +} // beast + +#endif diff --git a/src/beast/include/beast/http/verb.hpp b/src/beast/include/beast/http/verb.hpp new file mode 100644 index 0000000000..679af1a65e --- /dev/null +++ b/src/beast/include/beast/http/verb.hpp @@ -0,0 +1,153 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_VERB_HPP +#define BEAST_HTTP_VERB_HPP + +#include +#include +#include + +namespace beast { +namespace http { + +/** HTTP request method verbs + + Each verb corresponds to a particular method string + used in HTTP request messages. +*/ +enum class verb +{ + /** An unknown method. + + This value indicates that the request method string is not + one of the recognized verbs. Callers interested in the method + should use an interface which returns the original string. + */ + unknown = 0, + + /// The DELETE method deletes the specified resource + delete_, + + /** The GET method requests a representation of the specified resource. + + Requests using GET should only retrieve data and should have no other effect. + */ + get, + + /** The HEAD method asks for a response identical to that of a GET request, but without the response body. + + This is useful for retrieving meta-information written in response + headers, without having to transport the entire content. + */ + head, + + /** The POST method requests that the server accept the entity enclosed in the request as a new subordinate of the web resource identified by the URI. + + The data POSTed might be, for example, an annotation for existing + resources; a message for a bulletin board, newsgroup, mailing list, + or comment thread; a block of data that is the result of submitting + a web form to a data-handling process; or an item to add to a database + */ + post, + + /** The PUT method requests that the enclosed entity be stored under the supplied URI. + + If the URI refers to an already existing resource, it is modified; + if the URI does not point to an existing resource, then the server + can create the resource with that URI. + */ + put, + + /** The CONNECT method converts the request connection to a transparent TCP/IP tunnel. + + This is usually to facilitate SSL-encrypted communication (HTTPS) + through an unencrypted HTTP proxy. + */ + connect, + + /** The OPTIONS method returns the HTTP methods that the server supports for the specified URL. + + This can be used to check the functionality of a web server by requesting + '*' instead of a specific resource. + */ + options, + + /** The TRACE method echoes the received request so that a client can see what (if any) changes or additions have been made by intermediate servers. + */ + trace, + + // WebDAV + + copy, + lock, + mkcol, + move, + propfind, + proppatch, + search, + unlock, + bind, + rebind, + unbind, + acl, + + // subversion + + report, + mkactivity, + checkout, + merge, + + // upnp + + msearch, + notify, + subscribe, + unsubscribe, + + // RFC-5789 + + patch, + purge, + + // CalDAV + + mkcalendar, + + // RFC-2068, section 19.6.1.2 + + link, + unlink +}; + +/** Converts a string to the request method verb. + + If the string does not match a known request method, + @ref verb::unknown is returned. +*/ +verb +string_to_verb(string_view s); + +/// Returns the text representation of a request method verb. +string_view +to_string(verb v); + +/// Write the text for a request method verb to an output stream. +inline +std::ostream& +operator<<(std::ostream& os, verb v) +{ + return os << to_string(v); +} + +} // http +} // beast + +#include + +#endif diff --git a/src/beast/include/beast/http/write.hpp b/src/beast/include/beast/http/write.hpp index b190e2e627..36fe0c00fb 100644 --- a/src/beast/include/beast/http/write.hpp +++ b/src/beast/include/beast/http/write.hpp @@ -9,103 +9,134 @@ #define BEAST_HTTP_WRITE_HPP #include +#include +#include +#include #include +#include +#include #include -#include -#include +#include +#include +#include +#include +#include +#include #include +#include namespace beast { namespace http { -/** Write a HTTP/1 header to a stream. +/** Write part of a message to a stream using a serializer. - This function is used to synchronously write a header to - a stream. The call will block until one of the following - conditions is true: + This function is used to write part of a message to a stream using + a caller-provided HTTP/1 serializer. The call will block until one + of the following conditions is true: + + @li One or more bytes have been transferred. - @li The entire header is written. + @li The function @ref serializer::is_done returns `true` - @li An error occurs. + @li An error occurs on the stream. This operation is implemented in terms of one or more calls to the stream's `write_some` function. - Regardless of the semantic meaning of the header (for example, - specifying "Content-Length: 0" and "Connection: close"), - this function will not return `boost::asio::error::eof`. + The amount of data actually transferred is controlled by the behavior + of the underlying stream, subject to the buffer size limit of the + serializer obtained or set through a call to @ref serializer::limit. + Setting a limit and performing bounded work helps applications set + reasonable timeouts. It also allows application-level flow control + to function correctly. For example when using a TCP/IP based + stream. @param stream The stream to which the data is to be written. - The type must support the @b `SyncWriteStream` concept. + The type must support the @b SyncWriteStream concept. - @param msg The header to write. + @param sr The serializer to use. @throws system_error Thrown on failure. + + @see serializer */ -template +template void -write(SyncWriteStream& stream, - header const& msg); +write_some(SyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr); -/** Write a HTTP/1 header to a stream. +/** Write part of a message to a stream using a serializer. - This function is used to synchronously write a header to - a stream. The call will block until one of the following - conditions is true: + This function is used to write part of a message to a stream using + a caller-provided HTTP/1 serializer. The call will block until one + of the following conditions is true: + + @li One or more bytes have been transferred. - @li The entire header is written. + @li The function @ref serializer::is_done returns `true` - @li An error occurs. + @li An error occurs on the stream. This operation is implemented in terms of one or more calls to the stream's `write_some` function. - Regardless of the semantic meaning of the header (for example, - specifying "Content-Length: 0" and "Connection: close"), - this function will not return `boost::asio::error::eof`. - + The amount of data actually transferred is controlled by the behavior + of the underlying stream, subject to the buffer size limit of the + serializer obtained or set through a call to @ref serializer::limit. + Setting a limit and performing bounded work helps applications set + reasonable timeouts. It also allows application-level flow control + to function correctly. For example when using a TCP/IP based + stream. + @param stream The stream to which the data is to be written. - The type must support the @b `SyncWriteStream` concept. + The type must support the @b SyncWriteStream concept. - @param msg The header to write. + @param sr The serializer to use. - @param ec Set to the error, if any occurred. + @param ec Set to indicate what error occurred, if any. + + @see @ref async_write_some, @ref serializer */ -template +template void -write(SyncWriteStream& stream, - header const& msg, +write_some(SyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr, error_code& ec); -/** Write a HTTP/1 header asynchronously to a stream. +/** Write part of a message to a stream asynchronously using a serializer. - This function is used to asynchronously write a header to - a stream. The function call always returns immediately. The - asynchronous operation will continue until one of the following - conditions is true: + This function is used to write part of a message to a stream + asynchronously using a caller-provided HTTP/1 serializer. The function + call always returns immediately. The asynchronous operation will continue + until one of the following conditions is true: - @li The entire header is written. + @li One or more bytes have been transferred. - @li An error occurs. + @li The function @ref serializer::is_done returns `true` - This operation is implemented in terms of one or more calls to - the stream's `async_write_some` functions, and is known as a - composed operation. The program must ensure that the - stream performs no other write operations until this operation - completes. + @li An error occurs on the stream. - Regardless of the semantic meaning of the header (for example, - specifying "Content-Length: 0" and "Connection: close"), - this function will not return `boost::asio::error::eof`. + This operation is implemented in terms of zero or more calls to the stream's + `async_write_some` function, and is known as a composed operation. + The program must ensure that the stream performs no other write operations + until this operation completes. + The amount of data actually transferred is controlled by the behavior + of the underlying stream, subject to the buffer size limit of the + serializer obtained or set through a call to @ref serializer::limit. + Setting a limit and performing bounded work helps applications set + reasonable timeouts. It also allows application-level flow control + to function correctly. For example when using a TCP/IP based + stream. + @param stream The stream to which the data is to be written. - The type must support the @b `AsyncWriteStream` concept. + The type must support the @b AsyncWriteStream concept. - @param msg The header to write. The object must remain valid - at least until the completion handler is called; ownership is - not transferred. + @param sr The serializer to use. + The object must remain valid at least until the + handler is called; ownership is not transferred. @param handler The handler to be called when the operation completes. Copies will be made of the handler as required. @@ -117,46 +148,269 @@ write(SyncWriteStream& stream, immediately or not, the handler will not be invoked from within this function. Invocation of the handler will be performed in a manner equivalent to using `boost::asio::io_service::post`. + + @see @ref serializer */ template -#if GENERATING_DOCS -void_or_deduced + bool isRequest, class Body, class Fields, + class Decorator, class WriteHandler> +#if BEAST_DOXYGEN + void_or_deduced #else -typename async_completion< - WriteHandler, void(error_code)>::result_type +async_return_type #endif -async_write(AsyncWriteStream& stream, - header const& msg, +async_write_some(AsyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr, WriteHandler&& handler); //------------------------------------------------------------------------------ -/** Write a HTTP/1 message to a stream. +/** Write a header to a stream using a serializer. - This function is used to write a message to a stream. The call - will block until one of the following conditions is true: + This function is used to write a header to a stream using a + caller-provided HTTP/1 serializer. The call will block until one + of the following conditions is true: - @li The entire message is written. + @li The function @ref serializer::is_header_done returns `true` @li An error occurs. This operation is implemented in terms of one or more calls to the stream's `write_some` function. - The implementation will automatically perform chunk encoding if - the contents of the message indicate that chunk encoding is required. - If the semantics of the message indicate that the connection should - be closed after the message is sent, the error thrown from this - function will be `boost::asio::error::eof`. + @param stream The stream to which the data is to be written. + The type must support the @b SyncWriteStream concept. + + @param sr The serializer to use. + + @throws system_error Thrown on failure. + + @note The implementation will call @ref serializer::split with + the value `true` on the serializer passed in. + + @see @ref serializer +*/ +template +void +write_header(SyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr); + +/** Write a header to a stream using a serializer. + + This function is used to write a header to a stream using a + caller-provided HTTP/1 serializer. The call will block until one + of the following conditions is true: + + @li The function @ref serializer::is_header_done returns `true` + + @li An error occurs. + + This operation is implemented in terms of one or more calls + to the stream's `write_some` function. @param stream The stream to which the data is to be written. - The type must support the @b `SyncWriteStream` concept. + The type must support the @b SyncWriteStream concept. + + @param sr The serializer to use. + + @param ec Set to indicate what error occurred, if any. + + @note The implementation will call @ref serializer::split with + the value `true` on the serializer passed in. + + @see @ref serializer +*/ +template +void +write_header(SyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr, + error_code& ec); + +/** Write a header to a stream asynchronously using a serializer. + + This function is used to write a header to a stream asynchronously + using a caller-provided HTTP/1 serializer. The function call always + returns immediately. The asynchronous operation will continue until + one of the following conditions is true: + + @li The function @ref serializer::is_header_done returns `true` + + @li An error occurs. + + This operation is implemented in terms of zero or more calls to the stream's + `async_write_some` function, and is known as a composed operation. + The program must ensure that the stream performs no other write operations + until this operation completes. + + @param stream The stream to which the data is to be written. + The type must support the @b AsyncWriteStream concept. + + @param sr The serializer to use. + The object must remain valid at least until the + handler is called; ownership is not transferred. + + @param handler The handler to be called when the operation + completes. Copies will be made of the handler as required. + The equivalent function signature of the handler must be: + @code void handler( + error_code const& error // result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using `boost::asio::io_service::post`. + + @note The implementation will call @ref serializer::split with + the value `true` on the serializer passed in. + + @see @ref serializer +*/ +template +#if BEAST_DOXYGEN + void_or_deduced +#else +async_return_type +#endif +async_write_header(AsyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr, + WriteHandler&& handler); + +//------------------------------------------------------------------------------ + +/** Write a complete message to a stream using a serializer. + + This function is used to write a complete message to a stream using + a caller-provided HTTP/1 serializer. The call will block until one + of the following conditions is true: + + @li The function @ref serializer::is_done returns `true` + + @li An error occurs. + + This operation is implemented in terms of one or more calls + to the stream's `write_some` function. + + @param stream The stream to which the data is to be written. + The type must support the @b SyncWriteStream concept. + + @param sr The serializer to use. + + @throws system_error Thrown on failure. + + @see @ref serializer +*/ +template +void +write(SyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr); + +/** Write a complete message to a stream using a serializer. + + This function is used to write a complete message to a stream using + a caller-provided HTTP/1 serializer. The call will block until one + of the following conditions is true: + + @li The function @ref serializer::is_done returns `true` + + @li An error occurs. + + This operation is implemented in terms of one or more calls + to the stream's `write_some` function. + + @param stream The stream to which the data is to be written. + The type must support the @b SyncWriteStream concept. + + @param sr The serializer to use. + + @param ec Set to the error, if any occurred. + + @see @ref serializer +*/ +template +void +write(SyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr, + error_code& ec); + +/** Write a complete message to a stream asynchronously using a serializer. + + This function is used to write a complete message to a stream + asynchronously using a caller-provided HTTP/1 serializer. The + function call always returns immediately. The asynchronous + operation will continue until one of the following conditions is true: + + @li The function @ref serializer::is_done returns `true` + + @li An error occurs. + + This operation is implemented in terms of zero or more calls to the stream's + `async_write_some` function, and is known as a composed operation. + The program must ensure that the stream performs no other write operations + until this operation completes. + + @param stream The stream to which the data is to be written. + The type must support the @b AsyncWriteStream concept. + + @param sr The serializer to use. + The object must remain valid at least until the + handler is called; ownership is not transferred. + + @param handler The handler to be called when the operation + completes. Copies will be made of the handler as required. + The equivalent function signature of the handler must be: + @code void handler( + error_code const& error // result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using `boost::asio::io_service::post`. + + @see @ref serializer +*/ +template +#if BEAST_DOXYGEN + void_or_deduced +#else +async_return_type +#endif +async_write(AsyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr, + WriteHandler&& handler); + +//------------------------------------------------------------------------------ + +/** Write a complete message to a stream. + + This function is used to write a complete message to a stream using + HTTP/1. The call will block until one of the following conditions is true: + + @li The entire message is written. + + @li An error occurs. + + This operation is implemented in terms of one or more calls to the stream's + `write_some` function. The algorithm will use a temporary @ref serializer + with an empty chunk decorator to produce buffers. If the semantics of the + message indicate that the connection should be closed after the message is + sent, the error delivered by this function will be @ref error::end_of_stream + + @param stream The stream to which the data is to be written. + The type must support the @b SyncWriteStream concept. @param msg The message to write. @throws system_error Thrown on failure. + + @see @ref message */ template @@ -164,30 +418,29 @@ void write(SyncWriteStream& stream, message const& msg); -/** Write a HTTP/1 message on a stream. +/** Write a complete message to a stream. - This function is used to write a message to a stream. The call - will block until one of the following conditions is true: + This function is used to write a complete message to a stream using + HTTP/1. The call will block until one of the following conditions is true: @li The entire message is written. @li An error occurs. - This operation is implemented in terms of one or more calls - to the stream's `write_some` function. - - The implementation will automatically perform chunk encoding if - the contents of the message indicate that chunk encoding is required. - If the semantics of the message indicate that the connection should - be closed after the message is sent, the error returned from this - function will be `boost::asio::error::eof`. + This operation is implemented in terms of one or more calls to the stream's + `write_some` function. The algorithm will use a temporary @ref serializer + with an empty chunk decorator to produce buffers. If the semantics of the + message indicate that the connection should be closed after the message is + sent, the error delivered by this function will be @ref error::end_of_stream @param stream The stream to which the data is to be written. - The type must support the @b `SyncWriteStream` concept. + The type must support the @b SyncWriteStream concept. @param msg The message to write. @param ec Set to the error, if any occurred. + + @see @ref message */ template @@ -196,35 +449,31 @@ write(SyncWriteStream& stream, message const& msg, error_code& ec); -/** Write a HTTP/1 message asynchronously to a stream. +/** Write a complete message to a stream asynchronously. - This function is used to asynchronously write a message to - a stream. The function call always returns immediately. The - asynchronous operation will continue until one of the following - conditions is true: + This function is used to write a complete message to a stream asynchronously + using HTTP/1. The function call always returns immediately. The asynchronous + operation will continue until one of the following conditions is true: @li The entire message is written. @li An error occurs. - This operation is implemented in terms of one or more calls to - the stream's `async_write_some` functions, and is known as a - composed operation. The program must ensure that the - stream performs no other write operations until this operation - completes. - - The implementation will automatically perform chunk encoding if - the contents of the message indicate that chunk encoding is required. - If the semantics of the message indicate that the connection should - be closed after the message is sent, the operation will complete with - the error set to `boost::asio::error::eof`. + This operation is implemented in terms of zero or more calls to the stream's + `async_write_some` function, and is known as a composed operation. + The program must ensure that the stream performs no other write operations + until this operation completes. The algorithm will use a temporary + @ref serializer with an empty chunk decorator to produce buffers. If + the semantics of the message indicate that the connection should be + closed after the message is sent, the error delivered by this function + will be @ref error::end_of_stream @param stream The stream to which the data is to be written. - The type must support the @b `AsyncWriteStream` concept. + The type must support the @b AsyncWriteStream concept. - @param msg The message to write. The object must remain valid - at least until the completion handler is called; ownership is - not transferred. + @param msg The message to write. + The object must remain valid at least until the + handler is called; ownership is not transferred. @param handler The handler to be called when the operation completes. Copies will be made of the handler as required. @@ -236,23 +485,21 @@ write(SyncWriteStream& stream, immediately or not, the handler will not be invoked from within this function. Invocation of the handler will be performed in a manner equivalent to using `boost::asio::io_service::post`. + + @see @ref message */ template -#if GENERATING_DOCS -void_or_deduced -#else -typename async_completion< - WriteHandler, void(error_code)>::result_type -#endif +async_return_type< + WriteHandler, void(error_code)> async_write(AsyncWriteStream& stream, - message const& msg, + message& msg, WriteHandler&& handler); //------------------------------------------------------------------------------ -/** Serialize a HTTP/1 header to a `std::ostream`. +/** Serialize an HTTP/1 header to a `std::ostream`. The function converts the header to its HTTP/1 serialized representation and stores the result in the output stream. @@ -266,7 +513,7 @@ std::ostream& operator<<(std::ostream& os, header const& msg); -/** Serialize a HTTP/1 message to a `std::ostream`. +/** Serialize an HTTP/1 message to a `std::ostream`. The function converts the message to its HTTP/1 serialized representation and stores the result in the output stream. diff --git a/src/beast/include/beast/version.hpp b/src/beast/include/beast/version.hpp index 2ba20a08a2..d827f89f3b 100644 --- a/src/beast/include/beast/version.hpp +++ b/src/beast/include/beast/version.hpp @@ -9,15 +9,18 @@ #define BEAST_VERSION_HPP #include +#include -// follows http://semver.org +/** @def BEAST_API_VERSION -// BEAST_VERSION % 100 is the patch level -// BEAST_VERSION / 100 % 1000 is the minor version -// BEAST_VERSION / 100000 is the major version -// -#define BEAST_VERSION 100000 + Identifies the API version of Beast. -#define BEAST_VERSION_STRING "1.0.0-b34" + This is a simple integer that is incremented by one every time + a set of code changes is merged to the master or develop branch. +*/ +#define BEAST_VERSION 79 + +#define BEAST_VERSION_STRING "Beast/" BOOST_STRINGIZE(BEAST_VERSION) #endif + diff --git a/src/beast/include/beast/websocket/detail/debug.hpp b/src/beast/include/beast/websocket/detail/debug.hpp deleted file mode 100644 index 2437993eb0..0000000000 --- a/src/beast/include/beast/websocket/detail/debug.hpp +++ /dev/null @@ -1,75 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_WEBSOCKET_DETAIL_DEBUG_HPP -#define BEAST_WEBSOCKET_DETAIL_DEBUG_HPP - -#include -#include -#include -#include - -namespace beast { -namespace websocket { -namespace detail { - -template -std::string -to_hex(boost::asio::const_buffer b) -{ - using namespace boost::asio; - std::stringstream ss; - auto p = buffer_cast(b); - auto n = buffer_size(b); - while(n--) - { - ss << - std::setfill('0') << - std::setw(2) << - std::hex << int(*p++) << " "; - } - return ss.str(); -} - -template -std::string -to_hex(Buffers const& bs) -{ - std::string s; - for(auto const& b : bs) - s.append(to_hex(boost::asio::const_buffer(b))); - return s; -} - -template -std::string -buffers_to_string(Buffers const& bs) -{ - using namespace boost::asio; - std::string s; - s.reserve(buffer_size(bs)); - for(auto const& b : bs) - s.append(buffer_cast(b), - buffer_size(b)); - return s; -} - -template -std::string -format(std::string s) -{ - auto const w = 84; - for(int n = w*(s.size()/w); n>0; n-=w) - s.insert(n, 1, '\n'); - return s; -} - -} // detail -} // websocket -} // beast - -#endif diff --git a/src/beast/include/beast/websocket/detail/decorator.hpp b/src/beast/include/beast/websocket/detail/decorator.hpp deleted file mode 100644 index 558ff9e354..0000000000 --- a/src/beast/include/beast/websocket/detail/decorator.hpp +++ /dev/null @@ -1,167 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_WEBSOCKET_DETAIL_DECORATOR_HPP -#define BEAST_WEBSOCKET_DETAIL_DECORATOR_HPP - -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace websocket { -namespace detail { - -using request_type = http::request; - -using response_type = http::response; - -struct abstract_decorator -{ - virtual - ~abstract_decorator() = default; - - virtual - void - operator()(request_type& req) const = 0; - - virtual - void - operator()(response_type& res) const = 0; -}; - -template -class decorator : public abstract_decorator -{ - F f_; - - class call_req_possible - { - template().operator()( - std::declval()), - std::true_type{})> - static R check(int); - template - static std::false_type check(...); - public: - using type = decltype(check(0)); - }; - - class call_res_possible - { - template().operator()( - std::declval()), - std::true_type{})> - static R check(int); - template - static std::false_type check(...); - public: - using type = decltype(check(0)); - }; - -public: - decorator(F&& t) - : f_(std::move(t)) - { - } - - decorator(F const& t) - : f_(t) - { - } - - void - operator()(request_type& req) const override - { - (*this)(req, typename call_req_possible::type{}); - } - - void - operator()(response_type& res) const override - { - (*this)(res, typename call_res_possible::type{}); - } - -private: - void - operator()(request_type& req, std::true_type) const - { - f_(req); - } - - void - operator()(request_type& req, std::false_type) const - { - req.fields.replace("User-Agent", - std::string{"Beast/"} + BEAST_VERSION_STRING); - } - - void - operator()(response_type& res, std::true_type) const - { - f_(res); - } - - void - operator()(response_type& res, std::false_type) const - { - res.fields.replace("Server", - std::string{"Beast/"} + BEAST_VERSION_STRING); - } -}; - -class decorator_type -{ - std::shared_ptr p_; - -public: - decorator_type() = delete; - decorator_type(decorator_type&&) = default; - decorator_type(decorator_type const&) = default; - decorator_type& operator=(decorator_type&&) = default; - decorator_type& operator=(decorator_type const&) = default; - - template::type, - decorator_type>::value>> - decorator_type(F&& f) - : p_(std::make_shared>( - std::forward(f))) - { - BOOST_ASSERT(p_); - } - - void - operator()(request_type& req) - { - (*p_)(req); - BOOST_ASSERT(p_); - } - - void - operator()(response_type& res) - { - (*p_)(res); - BOOST_ASSERT(p_); - } -}; - -struct default_decorator -{ -}; - -} // detail -} // websocket -} // beast - -#endif diff --git a/src/beast/include/beast/websocket/detail/endian.hpp b/src/beast/include/beast/websocket/detail/endian.hpp deleted file mode 100644 index e9276089e9..0000000000 --- a/src/beast/include/beast/websocket/detail/endian.hpp +++ /dev/null @@ -1,71 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_WEBSOCKET_DETAIL_ENDIAN_HPP -#define BEAST_WEBSOCKET_DETAIL_ENDIAN_HPP - -#include - -namespace beast { -namespace websocket { -namespace detail { - -inline -std::uint16_t -big_uint16_to_native(void const* buf) -{ - auto const p = reinterpret_cast< - std::uint8_t const*>(buf); - return (p[0]<<8) + p[1]; -} - -inline -std::uint64_t -big_uint64_to_native(void const* buf) -{ - auto const p = reinterpret_cast< - std::uint8_t const*>(buf); - return - (static_cast(p[0])<<56) + - (static_cast(p[1])<<48) + - (static_cast(p[2])<<40) + - (static_cast(p[3])<<32) + - (static_cast(p[4])<<24) + - (static_cast(p[5])<<16) + - (static_cast(p[6])<< 8) + - p[7]; -} - -inline -std::uint32_t -little_uint32_to_native(void const* buf) -{ - auto const p = reinterpret_cast< - std::uint8_t const*>(buf); - return - p[0] + - (static_cast(p[1])<< 8) + - (static_cast(p[2])<<16) + - (static_cast(p[3])<<24); -} - -inline -void -native_to_little_uint32(std::uint32_t v, void* buf) -{ - auto p = reinterpret_cast(buf); - p[0] = v & 0xff; - p[1] = (v >> 8) & 0xff; - p[2] = (v >> 16) & 0xff; - p[3] = (v >> 24) & 0xff; -} - -} // detail -} // websocket -} // beast - -#endif diff --git a/src/beast/include/beast/websocket/detail/frame.hpp b/src/beast/include/beast/websocket/detail/frame.hpp index c3def4f3af..512f817a79 100644 --- a/src/beast/include/beast/websocket/detail/frame.hpp +++ b/src/beast/include/beast/websocket/detail/frame.hpp @@ -9,10 +9,9 @@ #define BEAST_WEBSOCKET_DETAIL_FRAME_HPP #include -#include #include #include -#include +#include #include #include #include @@ -23,6 +22,77 @@ namespace beast { namespace websocket { namespace detail { +inline +std::uint16_t +big_uint16_to_native(void const* buf) +{ + auto const p = reinterpret_cast< + std::uint8_t const*>(buf); + return (p[0]<<8) + p[1]; +} + +inline +std::uint64_t +big_uint64_to_native(void const* buf) +{ + auto const p = reinterpret_cast< + std::uint8_t const*>(buf); + return + (static_cast(p[0])<<56) + + (static_cast(p[1])<<48) + + (static_cast(p[2])<<40) + + (static_cast(p[3])<<32) + + (static_cast(p[4])<<24) + + (static_cast(p[5])<<16) + + (static_cast(p[6])<< 8) + + p[7]; +} + +inline +std::uint32_t +little_uint32_to_native(void const* buf) +{ + auto const p = reinterpret_cast< + std::uint8_t const*>(buf); + return + p[0] + + (static_cast(p[1])<< 8) + + (static_cast(p[2])<<16) + + (static_cast(p[3])<<24); +} + +inline +void +native_to_little_uint32(std::uint32_t v, void* buf) +{ + auto p = reinterpret_cast(buf); + p[0] = v & 0xff; + p[1] = (v >> 8) & 0xff; + p[2] = (v >> 16) & 0xff; + p[3] = (v >> 24) & 0xff; +} + +/** WebSocket frame header opcodes. */ +enum class opcode : std::uint8_t +{ + cont = 0, + text = 1, + binary = 2, + rsv3 = 3, + rsv4 = 4, + rsv5 = 5, + rsv6 = 6, + rsv7 = 7, + close = 8, + ping = 9, + pong = 10, + crsvb = 11, + crsvc = 12, + crsvd = 13, + crsve = 14, + crsvf = 15 +}; + // Contents of a WebSocket frame header struct frame_header { @@ -38,11 +108,11 @@ struct frame_header // holds the largest possible frame header using fh_streambuf = - static_streambuf_n<14>; + static_buffer_n<14>; // holds the largest possible control frame using frame_streambuf = - static_streambuf_n< 2 + 8 + 4 + 125 >; + static_buffer_n< 2 + 8 + 4 + 125 >; inline bool constexpr @@ -67,33 +137,31 @@ is_control(opcode op) return op >= opcode::close; } -// Returns `true` if a close code is valid inline bool -is_valid(close_code::value code) +is_valid_close_code(std::uint16_t v) { - auto const v = code; switch(v) { - case 1000: - case 1001: - case 1002: - case 1003: - case 1007: - case 1008: - case 1009: - case 1010: - case 1011: - case 1012: - case 1013: + case close_code::normal: // 1000 + case close_code::going_away: // 1001 + case close_code::protocol_error: // 1002 + case close_code::unknown_data: // 1003 + case close_code::bad_payload: // 1007 + case close_code::policy_error: // 1008 + case close_code::too_big: // 1009 + case close_code::needs_extension: // 1010 + case close_code::internal_error: // 1011 + case close_code::service_restart: // 1012 + case close_code::try_again_later: // 1013 return true; // explicitly reserved - case 1004: - case 1005: - case 1006: - case 1014: - case 1015: + case close_code::reserved1: // 1004 + case close_code::no_status: // 1005 + case close_code::abnormal: // 1006 + case close_code::reserved2: // 1014 + case close_code::reserved3: // 1015 return false; } // reserved @@ -175,7 +243,7 @@ read(ping_data& data, Buffers const& bs) template void read(close_reason& cr, - Buffers const& bs, close_code::value& code) + Buffers const& bs, close_code& code) { using boost::asio::buffer; using boost::asio::buffer_copy; @@ -201,7 +269,7 @@ read(close_reason& cr, cr.code = big_uint16_to_native(&b[0]); cb.consume(2); n -= 2; - if(! is_valid(cr.code)) + if(! is_valid_close_code(cr.code)) { code = close_code::protocol_error; return; diff --git a/src/beast/include/beast/websocket/detail/hybi13.hpp b/src/beast/include/beast/websocket/detail/hybi13.hpp index ec09918b1a..ccfd8854f5 100644 --- a/src/beast/include/beast/websocket/detail/hybi13.hpp +++ b/src/beast/include/beast/websocket/detail/hybi13.hpp @@ -8,9 +8,11 @@ #ifndef BEAST_WEBSOCKET_DETAIL_HYBI13_HPP #define BEAST_WEBSOCKET_DETAIL_HYBI13_HPP +#include +#include #include #include -#include +#include #include #include #include @@ -20,11 +22,17 @@ namespace beast { namespace websocket { namespace detail { +using sec_ws_key_type = static_string< + beast::detail::base64::encoded_size(16)>; + +using sec_ws_accept_type = static_string< + beast::detail::base64::encoded_size(20)>; + template -std::string -make_sec_ws_key(Gen& g) +void +make_sec_ws_key(sec_ws_key_type& key, Gen& g) { - std::array a; + char a[16]; for(int i = 0; i < 16; i += 4) { auto const v = g(); @@ -33,24 +41,27 @@ make_sec_ws_key(Gen& g) a[i+2] = (v >> 16) & 0xff; a[i+3] = (v >> 24) & 0xff; } - return beast::detail::base64_encode( - a.data(), a.size()); + key.resize(key.max_size()); + key.resize(beast::detail::base64::encode( + key.data(), &a[0], 16)); } template -std::string -make_sec_ws_accept(boost::string_ref const& key) +void +make_sec_ws_accept(sec_ws_accept_type& accept, + string_view key) { - std::string s(key.data(), key.size()); - s += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + BOOST_ASSERT(key.size() <= sec_ws_key_type::max_size_n); + static_string m(key); + m.append("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); beast::detail::sha1_context ctx; beast::detail::init(ctx); - beast::detail::update(ctx, s.data(), s.size()); - std::array digest; - beast::detail::finish(ctx, digest.data()); - return beast::detail::base64_encode( - digest.data(), digest.size()); + beast::detail::update(ctx, m.data(), m.size()); + char digest[beast::detail::sha1_context::digest_size]; + beast::detail::finish(ctx, &digest[0]); + accept.resize(accept.max_size()); + accept.resize(beast::detail::base64::encode( + accept.data(), &digest[0], sizeof(digest))); } } // detail diff --git a/src/beast/include/beast/websocket/detail/mask.hpp b/src/beast/include/beast/websocket/detail/mask.hpp index fdb8e86f49..27a3a155a2 100644 --- a/src/beast/include/beast/websocket/detail/mask.hpp +++ b/src/beast/include/beast/websocket/detail/mask.hpp @@ -254,7 +254,7 @@ void mask_inplace( MutableBuffers const& bs, KeyType& key) { - for(auto const& b : bs) + for(boost::asio::mutable_buffer b : bs) mask_inplace(b, key); } diff --git a/src/beast/include/beast/websocket/detail/invokable.hpp b/src/beast/include/beast/websocket/detail/pausation.hpp similarity index 71% rename from src/beast/include/beast/websocket/detail/invokable.hpp rename to src/beast/include/beast/websocket/detail/pausation.hpp index 8070108321..53a99884a3 100644 --- a/src/beast/include/beast/websocket/detail/invokable.hpp +++ b/src/beast/include/beast/websocket/detail/pausation.hpp @@ -5,8 +5,8 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef BEAST_WEBSOCKET_DETAIL_INVOKABLE_HPP -#define BEAST_WEBSOCKET_DETAIL_INVOKABLE_HPP +#ifndef BEAST_WEBSOCKET_DETAIL_PAUSATION_HPP +#define BEAST_WEBSOCKET_DETAIL_PAUSATION_HPP #include #include @@ -19,16 +19,18 @@ namespace beast { namespace websocket { namespace detail { -// "Parks" a composed operation, to invoke later +// A container that holds a suspended, asynchronous composed +// operation. The contained object may be invoked later to +// resume the operation, or the container may be destroyed. // -class invokable +class pausation { struct base { base() = default; base(base &&) = default; virtual ~base() = default; - virtual void move(void* p) = 0; + virtual base* move(void* p) = 0; virtual void operator()() = 0; }; @@ -46,10 +48,10 @@ class invokable { } - void + base* move(void* p) override { - ::new(p) holder(std::move(*this)); + return ::new(p) holder(std::move(*this)); } void @@ -58,7 +60,7 @@ class invokable F f_(std::move(f)); this->~holder(); // invocation of f_() can - // assign a new invokable. + // assign a new object to *this. f_(); } }; @@ -86,38 +88,34 @@ class invokable alignas(holder) buf_type buf_; public: - ~invokable() + ~pausation() { if(base_) base_->~base(); } - invokable() = default; + pausation() = default; - invokable(invokable&& other) + pausation(pausation&& other) { if(other.base_) { - // type-pun - base_ = reinterpret_cast(&buf_[0]); - other.base_->move(buf_); + base_ = other.base_->move(buf_); other.base_ = nullptr; } } - invokable& - operator=(invokable&& other) + pausation& + operator=(pausation&& other) { - // Engaged invokables must be invoked before + // Engaged pausations must be invoked before // assignment otherwise the io_service // completion invariants are broken. BOOST_ASSERT(! base_); if(other.base_) { - // type-pun - base_ = reinterpret_cast(&buf_[0]); - other.base_->move(buf_); + base_ = other.base_->move(buf_); other.base_ = nullptr; } return *this; @@ -143,14 +141,13 @@ public: template void -invokable::emplace(F&& f) +pausation::emplace(F&& f) { - static_assert(sizeof(buf_type) >= sizeof(holder), + using type = holder::type>; + static_assert(sizeof(buf_type) >= sizeof(type), "buffer too small"); BOOST_ASSERT(! base_); - ::new(buf_) holder(std::forward(f)); - // type-pun - base_ = reinterpret_cast(&buf_[0]); + base_ = ::new(buf_) type{std::forward(f)}; } } // detail diff --git a/src/beast/include/beast/websocket/detail/pmd_extension.hpp b/src/beast/include/beast/websocket/detail/pmd_extension.hpp index e7c087a3d9..ac1a5a91cf 100644 --- a/src/beast/include/beast/websocket/detail/pmd_extension.hpp +++ b/src/beast/include/beast/websocket/detail/pmd_extension.hpp @@ -10,7 +10,7 @@ #include #include -#include +#include #include #include #include @@ -46,7 +46,7 @@ struct pmd_offer template int -parse_bits(boost::string_ref const& s) +parse_bits(string_view s) { if(s.size() == 0) return -1; @@ -66,9 +66,10 @@ parse_bits(boost::string_ref const& s) // Parse permessage-deflate request fields // -template +template void -pmd_read(pmd_offer& offer, Fields const& fields) +pmd_read(pmd_offer& offer, + http::basic_fields const& fields) { offer.accept = false; offer.server_max_window_bits= 0; @@ -76,16 +77,15 @@ pmd_read(pmd_offer& offer, Fields const& fields) offer.server_no_context_takeover = false; offer.client_no_context_takeover = false; - using beast::detail::ci_equal; http::ext_list list{ fields["Sec-WebSocket-Extensions"]}; for(auto const& ext : list) { - if(ci_equal(ext.first, "permessage-deflate")) + if(iequals(ext.first, "permessage-deflate")) { for(auto const& param : ext.second) { - if(ci_equal(param.first, + if(iequals(param.first, "server_max_window_bits")) { if(offer.server_max_window_bits != 0) @@ -113,7 +113,7 @@ pmd_read(pmd_offer& offer, Fields const& fields) return; // MUST decline } } - else if(ci_equal(param.first, + else if(iequals(param.first, "client_max_window_bits")) { if(offer.client_max_window_bits != 0) @@ -141,7 +141,7 @@ pmd_read(pmd_offer& offer, Fields const& fields) offer.client_max_window_bits = -1; } } - else if(ci_equal(param.first, + else if(iequals(param.first, "server_no_context_takeover")) { if(offer.server_no_context_takeover) @@ -160,7 +160,7 @@ pmd_read(pmd_offer& offer, Fields const& fields) } offer.server_no_context_takeover = true; } - else if(ci_equal(param.first, + else if(iequals(param.first, "client_no_context_takeover")) { if(offer.client_no_context_takeover) @@ -195,18 +195,19 @@ pmd_read(pmd_offer& offer, Fields const& fields) // Set permessage-deflate fields for a client offer // -template +template void -pmd_write(Fields& fields, pmd_offer const& offer) +pmd_write(http::basic_fields& fields, + pmd_offer const& offer) { - std::string s; + static_string<512> s; s = "permessage-deflate"; if(offer.server_max_window_bits != 0) { if(offer.server_max_window_bits != -1) { s += "; server_max_window_bits="; - s += std::to_string( + s += to_static_string( offer.server_max_window_bits); } else @@ -219,7 +220,7 @@ pmd_write(Fields& fields, pmd_offer const& offer) if(offer.client_max_window_bits != -1) { s += "; client_max_window_bits="; - s += std::to_string( + s += to_static_string( offer.client_max_window_bits); } else @@ -235,15 +236,15 @@ pmd_write(Fields& fields, pmd_offer const& offer) { s += "; client_no_context_takeover"; } - fields.replace("Sec-WebSocket-Extensions", s); + fields.set(http::field::sec_websocket_extensions, s); } // Negotiate a permessage-deflate client offer // -template +template void pmd_negotiate( - Fields& fields, + http::basic_fields& fields, pmd_offer& config, pmd_offer const& offer, permessage_deflate const& o) @@ -255,7 +256,7 @@ pmd_negotiate( } config.accept = true; - std::string s = "permessage-deflate"; + static_string<512> s = "permessage-deflate"; config.server_no_context_takeover = offer.server_no_context_takeover || @@ -285,7 +286,7 @@ pmd_negotiate( config.server_max_window_bits = 9; s += "; server_max_window_bits="; - s += std::to_string( + s += to_static_string( config.server_max_window_bits); } @@ -298,7 +299,7 @@ pmd_negotiate( if(config.client_max_window_bits < 15) { s += "; client_max_window_bits="; - s += std::to_string( + s += to_static_string( config.client_max_window_bits); } break; @@ -323,12 +324,12 @@ pmd_negotiate( o.client_max_window_bits, offer.client_max_window_bits); s += "; client_max_window_bits="; - s += std::to_string( + s += to_static_string( config.client_max_window_bits); break; } if(config.accept) - fields.replace("Sec-WebSocket-Extensions", s); + fields.set(http::field::sec_websocket_extensions, s); } // Normalize the server's response @@ -356,7 +357,7 @@ template void inflate( InflateStream& zi, - DynamicBuffer& dynabuf, + DynamicBuffer& buffer, boost::asio::const_buffer const& in, error_code& ec) { @@ -368,18 +369,18 @@ inflate( for(;;) { // VFALCO we could be smarter about the size - auto const bs = dynabuf.prepare( - read_size_helper(dynabuf, 65536)); + auto const bs = buffer.prepare( + read_size_or_throw(buffer, 65536)); auto const out = *bs.begin(); zs.avail_out = buffer_size(out); zs.next_out = buffer_cast(out); zi.write(zs, zlib::Flush::sync, ec); - dynabuf.commit(zs.total_out); + buffer.commit(zs.total_out); zs.total_out = 0; if( ec == zlib::error::need_buffers || ec == zlib::error::end_of_stream) { - ec = {}; + ec.assign(0, ec.category()); break; } if(ec) @@ -408,7 +409,7 @@ deflate( zs.next_in = nullptr; zs.avail_out = buffer_size(out); zs.next_out = buffer_cast(out); - for(auto const& in : cb) + for(boost::asio::const_buffer in : cb) { zs.avail_in = buffer_size(in); if(zs.avail_in == 0) @@ -421,7 +422,7 @@ deflate( return false; BOOST_ASSERT(zs.avail_out == 0); BOOST_ASSERT(zs.total_out == buffer_size(out)); - ec = {}; + ec.assign(0, ec.category()); break; } if(zs.avail_out == 0) @@ -445,7 +446,7 @@ deflate( zo.write(zs, zlib::Flush::block, ec); BOOST_ASSERT(! ec || ec == zlib::error::need_buffers); if(ec == zlib::error::need_buffers) - ec = {}; + ec.assign(0, ec.category()); if(ec) return false; if(zs.avail_out >= 6) @@ -460,6 +461,7 @@ deflate( } } } + ec.assign(0, ec.category()); out = buffer( buffer_cast(out), zs.total_out); return true; diff --git a/src/beast/include/beast/websocket/detail/stream_base.hpp b/src/beast/include/beast/websocket/detail/stream_base.hpp deleted file mode 100644 index 6cd6731fba..0000000000 --- a/src/beast/include/beast/websocket/detail/stream_base.hpp +++ /dev/null @@ -1,568 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_WEBSOCKET_DETAIL_STREAM_BASE_HPP -#define BEAST_WEBSOCKET_DETAIL_STREAM_BASE_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace websocket { -namespace detail { - -/// Identifies the role of a WebSockets stream. -enum class role_type -{ - /// Stream is operating as a client. - client, - - /// Stream is operating as a server. - server -}; - -//------------------------------------------------------------------------------ - -struct stream_base -{ -protected: - friend class frame_test; - - struct op {}; - - detail::maskgen maskgen_; // source of mask keys - decorator_type d_; // adorns http messages - bool keep_alive_ = false; // close on failed upgrade - std::size_t rd_msg_max_ = - 16 * 1024 * 1024; // max message size - bool wr_autofrag_ = true; // auto fragment - std::size_t wr_buf_size_ = 4096; // write buffer size - std::size_t rd_buf_size_ = 4096; // read buffer size - opcode wr_opcode_ = opcode::text; // outgoing message type - ping_cb ping_cb_; // ping callback - role_type role_; // server or client - bool failed_; // the connection failed - - bool wr_close_; // sent close frame - op* wr_block_; // op currenly writing - - ping_data* ping_data_; // where to put the payload - invokable rd_op_; // read parking - invokable wr_op_; // write parking - invokable ping_op_; // ping parking - close_reason cr_; // set from received close frame - - // State information for the message being received - // - struct rd_t - { - // opcode of current message being read - opcode op; - - // `true` if the next frame is a continuation. - bool cont; - - // Checks that test messages are valid utf8 - detail::utf8_checker utf8; - - // Size of the current message so far. - std::uint64_t size; - - // Size of the read buffer. - // This gets set to the read buffer size option at the - // beginning of sending a message, so that the option can be - // changed mid-send without affecting the current message. - std::size_t buf_size; - - // The read buffer. Used for compression and masking. - std::unique_ptr buf; - }; - - rd_t rd_; - - // State information for the message being sent - // - struct wr_t - { - // `true` if next frame is a continuation, - // `false` if next frame starts a new message - bool cont; - - // `true` if this message should be auto-fragmented - // This gets set to the auto-fragment option at the beginning - // of sending a message, so that the option can be changed - // mid-send without affecting the current message. - bool autofrag; - - // `true` if this message should be compressed. - // This gets set to the compress option at the beginning of - // of sending a message, so that the option can be changed - // mid-send without affecting the current message. - bool compress; - - // Size of the write buffer. - // This gets set to the write buffer size option at the - // beginning of sending a message, so that the option can be - // changed mid-send without affecting the current message. - std::size_t buf_size; - - // The write buffer. Used for compression and masking. - // The buffer is allocated or reallocated at the beginning of - // sending a message. - std::unique_ptr buf; - }; - - wr_t wr_; - - // State information for the permessage-deflate extension - struct pmd_t - { - // `true` if current read message is compressed - bool rd_set; - - zlib::deflate_stream zo; - zlib::inflate_stream zi; - }; - - // If not engaged, then permessage-deflate is not - // enabled for the currently active session. - std::unique_ptr pmd_; - - // Local options for permessage-deflate - permessage_deflate pmd_opts_; - - // Offer for clients, negotiated result for servers - pmd_offer pmd_config_; - - stream_base(stream_base&&) = default; - stream_base(stream_base const&) = delete; - stream_base& operator=(stream_base&&) = default; - stream_base& operator=(stream_base const&) = delete; - - stream_base() - : d_(detail::default_decorator{}) - { - } - - template - void - open(role_type role); - - template - void - close(); - - template - std::size_t - read_fh1(detail::frame_header& fh, - DynamicBuffer& db, close_code::value& code); - - template - void - read_fh2(detail::frame_header& fh, - DynamicBuffer& db, close_code::value& code); - - // Called before receiving the first frame of each message - template - void - rd_begin(); - - // Called before sending the first frame of each message - // - template - void - wr_begin(); - - template - void - write_close(DynamicBuffer& db, close_reason const& rc); - - template - void - write_ping(DynamicBuffer& db, opcode op, ping_data const& data); -}; - -template -void -stream_base:: -open(role_type role) -{ - // VFALCO TODO analyze and remove dupe code in reset() - role_ = role; - failed_ = false; - rd_.cont = false; - wr_close_ = false; - wr_block_ = nullptr; // should be nullptr on close anyway - ping_data_ = nullptr; // should be nullptr on close anyway - - wr_.cont = false; - wr_.buf_size = 0; - - if(((role_ == role_type::client && pmd_opts_.client_enable) || - (role_ == role_type::server && pmd_opts_.server_enable)) && - pmd_config_.accept) - { - pmd_normalize(pmd_config_); - pmd_.reset(new pmd_t); - if(role_ == role_type::client) - { - pmd_->zi.reset( - pmd_config_.server_max_window_bits); - pmd_->zo.reset( - pmd_opts_.compLevel, - pmd_config_.client_max_window_bits, - pmd_opts_.memLevel, - zlib::Strategy::normal); - } - else - { - pmd_->zi.reset( - pmd_config_.client_max_window_bits); - pmd_->zo.reset( - pmd_opts_.compLevel, - pmd_config_.server_max_window_bits, - pmd_opts_.memLevel, - zlib::Strategy::normal); - } - } -} - -template -void -stream_base:: -close() -{ - rd_.buf.reset(); - wr_.buf.reset(); - pmd_.reset(); -} - -// Read fixed frame header from buffer -// Requires at least 2 bytes -// -template -std::size_t -stream_base:: -read_fh1(detail::frame_header& fh, - DynamicBuffer& db, close_code::value& code) -{ - using boost::asio::buffer; - using boost::asio::buffer_copy; - using boost::asio::buffer_size; - auto const err = - [&](close_code::value cv) - { - code = cv; - return 0; - }; - std::uint8_t b[2]; - BOOST_ASSERT(buffer_size(db.data()) >= sizeof(b)); - db.consume(buffer_copy(buffer(b), db.data())); - std::size_t need; - fh.len = b[1] & 0x7f; - switch(fh.len) - { - case 126: need = 2; break; - case 127: need = 8; break; - default: - need = 0; - } - fh.mask = (b[1] & 0x80) != 0; - if(fh.mask) - need += 4; - fh.op = static_cast(b[0] & 0x0f); - fh.fin = (b[0] & 0x80) != 0; - fh.rsv1 = (b[0] & 0x40) != 0; - fh.rsv2 = (b[0] & 0x20) != 0; - fh.rsv3 = (b[0] & 0x10) != 0; - switch(fh.op) - { - case opcode::binary: - case opcode::text: - if(rd_.cont) - { - // new data frame when continuation expected - return err(close_code::protocol_error); - } - if((fh.rsv1 && ! pmd_) || - fh.rsv2 || fh.rsv3) - { - // reserved bits not cleared - return err(close_code::protocol_error); - } - if(pmd_) - pmd_->rd_set = fh.rsv1; - break; - - case opcode::cont: - if(! rd_.cont) - { - // continuation without an active message - return err(close_code::protocol_error); - } - if(fh.rsv1 || fh.rsv2 || fh.rsv3) - { - // reserved bits not cleared - return err(close_code::protocol_error); - } - break; - - default: - if(is_reserved(fh.op)) - { - // reserved opcode - return err(close_code::protocol_error); - } - if(! fh.fin) - { - // fragmented control message - return err(close_code::protocol_error); - } - if(fh.len > 125) - { - // invalid length for control message - return err(close_code::protocol_error); - } - if(fh.rsv1 || fh.rsv2 || fh.rsv3) - { - // reserved bits not cleared - return err(close_code::protocol_error); - } - break; - } - // unmasked frame from client - if(role_ == role_type::server && ! fh.mask) - { - code = close_code::protocol_error; - return 0; - } - // masked frame from server - if(role_ == role_type::client && fh.mask) - { - code = close_code::protocol_error; - return 0; - } - code = close_code::none; - return need; -} - -// Decode variable frame header from buffer -// -template -void -stream_base:: -read_fh2(detail::frame_header& fh, - DynamicBuffer& db, close_code::value& code) -{ - using boost::asio::buffer; - using boost::asio::buffer_copy; - using boost::asio::buffer_size; - using namespace boost::endian; - switch(fh.len) - { - case 126: - { - std::uint8_t b[2]; - BOOST_ASSERT(buffer_size(db.data()) >= sizeof(b)); - db.consume(buffer_copy(buffer(b), db.data())); - fh.len = big_uint16_to_native(&b[0]); - // length not canonical - if(fh.len < 126) - { - code = close_code::protocol_error; - return; - } - break; - } - case 127: - { - std::uint8_t b[8]; - BOOST_ASSERT(buffer_size(db.data()) >= sizeof(b)); - db.consume(buffer_copy(buffer(b), db.data())); - fh.len = big_uint64_to_native(&b[0]); - // length not canonical - if(fh.len < 65536) - { - code = close_code::protocol_error; - return; - } - break; - } - } - if(fh.mask) - { - std::uint8_t b[4]; - BOOST_ASSERT(buffer_size(db.data()) >= sizeof(b)); - db.consume(buffer_copy(buffer(b), db.data())); - fh.key = little_uint32_to_native(&b[0]); - } - else - { - // initialize this otherwise operator== breaks - fh.key = 0; - } - if(! is_control(fh.op)) - { - if(fh.op != opcode::cont) - { - rd_.size = 0; - rd_.op = fh.op; - } - else - { - if(rd_.size > (std::numeric_limits< - std::uint64_t>::max)() - fh.len) - { - code = close_code::too_big; - return; - } - } - rd_.cont = ! fh.fin; - } - code = close_code::none; -} - -template -void -stream_base:: -rd_begin() -{ - // Maintain the read buffer - if(pmd_) - { - if(! rd_.buf || rd_.buf_size != rd_buf_size_) - { - rd_.buf_size = rd_buf_size_; - rd_.buf.reset(new std::uint8_t[rd_.buf_size]); - } - } -} - -template -void -stream_base:: -wr_begin() -{ - wr_.autofrag = wr_autofrag_; - wr_.compress = static_cast(pmd_); - - // Maintain the write buffer - if( wr_.compress || - role_ == detail::role_type::client) - { - if(! wr_.buf || wr_.buf_size != wr_buf_size_) - { - wr_.buf_size = wr_buf_size_; - wr_.buf.reset(new std::uint8_t[wr_.buf_size]); - } - } - else - { - wr_.buf_size = wr_buf_size_; - wr_.buf.reset(); - } -} - -template -void -stream_base:: -write_close(DynamicBuffer& db, close_reason const& cr) -{ - using namespace boost::endian; - frame_header fh; - fh.op = opcode::close; - fh.fin = true; - fh.rsv1 = false; - fh.rsv2 = false; - fh.rsv3 = false; - fh.len = cr.code == close_code::none ? - 0 : 2 + cr.reason.size(); - fh.mask = role_ == detail::role_type::client; - if(fh.mask) - fh.key = maskgen_(); - detail::write(db, fh); - if(cr.code != close_code::none) - { - detail::prepared_key key; - if(fh.mask) - detail::prepare_key(key, fh.key); - { - std::uint8_t b[2]; - ::new(&b[0]) big_uint16_buf_t{ - (std::uint16_t)cr.code}; - auto d = db.prepare(2); - boost::asio::buffer_copy(d, - boost::asio::buffer(b)); - if(fh.mask) - detail::mask_inplace(d, key); - db.commit(2); - } - if(! cr.reason.empty()) - { - auto d = db.prepare(cr.reason.size()); - boost::asio::buffer_copy(d, - boost::asio::const_buffer( - cr.reason.data(), cr.reason.size())); - if(fh.mask) - detail::mask_inplace(d, key); - db.commit(cr.reason.size()); - } - } -} - -template -void -stream_base:: -write_ping( - DynamicBuffer& db, opcode op, ping_data const& data) -{ - frame_header fh; - fh.op = op; - fh.fin = true; - fh.rsv1 = false; - fh.rsv2 = false; - fh.rsv3 = false; - fh.len = data.size(); - fh.mask = role_ == role_type::client; - if(fh.mask) - fh.key = maskgen_(); - detail::write(db, fh); - if(data.empty()) - return; - detail::prepared_key key; - if(fh.mask) - detail::prepare_key(key, fh.key); - auto d = db.prepare(data.size()); - boost::asio::buffer_copy(d, - boost::asio::const_buffers_1( - data.data(), data.size())); - if(fh.mask) - detail::mask_inplace(d, key); - db.commit(data.size()); -} - -} // detail -} // websocket -} // beast - -#endif diff --git a/src/beast/include/beast/websocket/detail/type_traits.hpp b/src/beast/include/beast/websocket/detail/type_traits.hpp new file mode 100644 index 0000000000..7f3481f19d --- /dev/null +++ b/src/beast/include/beast/websocket/detail/type_traits.hpp @@ -0,0 +1,32 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_WEBSOCKET_DETAIL_TYPE_TRAITS_HPP +#define BEAST_WEBSOCKET_DETAIL_TYPE_TRAITS_HPP + +#include +#include + +namespace beast { +namespace websocket { +namespace detail { + +template +using is_RequestDecorator = + typename beast::detail::is_invocable::type; + +template +using is_ResponseDecorator = + typename beast::detail::is_invocable::type; + +} // detail +} // websocket +} // beast + +#endif diff --git a/src/beast/include/beast/websocket/detail/utf8_checker.hpp b/src/beast/include/beast/websocket/detail/utf8_checker.hpp index d8b1460403..cbd9b70c69 100644 --- a/src/beast/include/beast/websocket/detail/utf8_checker.hpp +++ b/src/beast/include/beast/websocket/detail/utf8_checker.hpp @@ -8,9 +8,9 @@ #ifndef BEAST_WEBSOCKET_DETAIL_UTF8_CHECKER_HPP #define BEAST_WEBSOCKET_DETAIL_UTF8_CHECKER_HPP +#include #include #include -#include #include #include @@ -105,7 +105,8 @@ public: template void -utf8_checker_t<_>::reset() +utf8_checker_t<_>:: +reset() { need_ = 0; p_ = have_; @@ -113,7 +114,8 @@ utf8_checker_t<_>::reset() template bool -utf8_checker_t<_>::finish() +utf8_checker_t<_>:: +finish() { auto const success = need_ == 0; reset(); @@ -123,13 +125,14 @@ utf8_checker_t<_>::finish() template template bool -utf8_checker_t<_>::write(ConstBufferSequence const& bs) +utf8_checker_t<_>:: +write(ConstBufferSequence const& bs) { - static_assert(is_ConstBufferSequence::value, + static_assert(is_const_buffer_sequence::value, "ConstBufferSequence requirements not met"); using boost::asio::buffer_cast; using boost::asio::buffer_size; - for(auto const& b : bs) + for(boost::asio::const_buffer b : bs) if(! write(buffer_cast(b), buffer_size(b))) return false; @@ -138,43 +141,44 @@ utf8_checker_t<_>::write(ConstBufferSequence const& bs) template bool -utf8_checker_t<_>::write(std::uint8_t const* in, std::size_t size) +utf8_checker_t<_>:: +write(std::uint8_t const* in, std::size_t size) { auto const valid = - [](std::uint8_t const*& in) + [](std::uint8_t const*& p) { - if (in[0] < 128) + if (p[0] < 128) { - ++in; + ++p; return true; } - if ((in[0] & 0x60) == 0x40) + if ((p[0] & 0x60) == 0x40) { - if ((in[1] & 0xc0) != 0x80) + if ((p[1] & 0xc0) != 0x80) return false; - in += 2; + p += 2; return true; } - if ((in[0] & 0xf0) == 0xe0) + if ((p[0] & 0xf0) == 0xe0) { - if ((in[1] & 0xc0) != 0x80 || - (in[2] & 0xc0) != 0x80 || - (in[0] == 224 && in[1] < 160) || - (in[0] == 237 && in[1] > 159)) + if ((p[1] & 0xc0) != 0x80 || + (p[2] & 0xc0) != 0x80 || + (p[0] == 224 && p[1] < 160) || + (p[0] == 237 && p[1] > 159)) return false; - in += 3; + p += 3; return true; } - if ((in[0] & 0xf8) == 0xf0) + if ((p[0] & 0xf8) == 0xf0) { - if (in[0] > 244 || - (in[1] & 0xc0) != 0x80 || - (in[2] & 0xc0) != 0x80 || - (in[3] & 0xc0) != 0x80 || - (in[0] == 240 && in[1] < 144) || - (in[0] == 244 && in[1] > 143)) + if (p[0] > 244 || + (p[1] & 0xc0) != 0x80 || + (p[2] & 0xc0) != 0x80 || + (p[3] & 0xc0) != 0x80 || + (p[0] == 240 && p[1] < 144) || + (p[0] == 244 && p[1] > 143)) return false; - in += 4; + p += 4; return true; } return false; @@ -195,10 +199,10 @@ utf8_checker_t<_>::write(std::uint8_t const* in, std::size_t size) } if ((have_[0] & 0xf8) == 0xf0) { - auto const size = p_ - have_; - if (size > 2 && (have_[2] & 0xc0) != 0x80) + auto const n = p_ - have_; + if (n > 2 && (have_[2] & 0xc0) != 0x80) return false; - if (size > 1 && + if (n > 1 && ((have_[1] & 0xc0) != 0x80 || (have_[0] == 240 && have_[1] < 144) || (have_[0] == 244 && have_[1] > 143))) @@ -207,17 +211,17 @@ utf8_checker_t<_>::write(std::uint8_t const* in, std::size_t size) return true; }; auto const needed = - [](std::uint8_t const in) + [](std::uint8_t const v) { - if (in < 128) + if (v < 128) return 1; - if (in < 194) + if (v < 194) return 0; - if (in < 224) + if (v < 224) return 2; - if (in < 240) + if (v < 240) return 3; - if (in < 245) + if (v < 245) return 4; return 0; }; @@ -241,39 +245,66 @@ utf8_checker_t<_>::write(std::uint8_t const* in, std::size_t size) p_ = have_; } - auto last = in + size - 7; - while(in < last) - { -#if BEAST_WEBSOCKET_NO_UNALIGNED_READ - auto constexpr align = sizeof(std::size_t) - 1; - auto constexpr mask = static_cast< - std::size_t>(0x8080808080808080 & - ~std::size_t{0}); - if( - ((reinterpret_cast< - std::uintptr_t>(in) & align) == 0) && - (*reinterpret_cast< - std::size_t const*>(in) & mask) == 0) - in += sizeof(std::size_t); - else if(! valid(in)) - return false; -#else - auto constexpr mask = static_cast< - std::size_t>(0x8080808080808080 & - ~std::size_t{0}); - if( - (*reinterpret_cast< - std::size_t const*>(in) & mask) == 0) - in += sizeof(std::size_t); - else if(! valid(in)) - return false; -#endif - } - last += 4; - while(in < last) - if(! valid(in)) - return false; + if(size <= sizeof(std::size_t)) + goto slow; + // align in to sizeof(std::size_t) boundary + { + auto const in0 = in; + auto last = reinterpret_cast( + ((reinterpret_cast(in) + sizeof(std::size_t) - 1) / + sizeof(std::size_t)) * sizeof(std::size_t)); + while(in < last) + { + if(*in & 0x80) + { + size = size - (in - in0); + goto slow; + } + ++in; + } + size = size - (in - in0); + } + + // fast loop + { + auto const in0 = in; + auto last = in + size - 7; + auto constexpr mask = static_cast< + std::size_t>(0x8080808080808080 & ~std::size_t{0}); + while(in < last) + { +#if 0 + std::size_t temp; + std::memcpy(&temp, in, sizeof(temp)); + if((temp & mask) != 0) +#else + // Technically UB but works on all known platforms + if((*reinterpret_cast(in) & mask) != 0) +#endif + { + size = size - (in - in0); + goto slow; + } + in += sizeof(std::size_t); + } + last += 4; + while(in < last) + if(! valid(in)) + return false; + goto tail; + } + + // slow loop: one code point at a time +slow: + { + auto last = in + size - 3; + while(in < last) + if(! valid(in)) + return false; + } + +tail: for(;;) { auto n = end - in; diff --git a/src/beast/include/beast/websocket/error.hpp b/src/beast/include/beast/websocket/error.hpp index 8189a1c2ab..e36a37fa06 100644 --- a/src/beast/include/beast/websocket/error.hpp +++ b/src/beast/include/beast/websocket/error.hpp @@ -23,32 +23,8 @@ enum class error /// WebSocket connection failed, protocol violation failed, - /// Upgrade request failed, connection is closed - handshake_failed, - - /// Upgrade request failed, but connection is still open - keep_alive, - - /// HTTP response is malformed - response_malformed, - - /// HTTP response failed the upgrade - response_failed, - - /// Upgrade request denied for invalid fields. - response_denied, - - /// Upgrade request is malformed - request_malformed, - - /// Upgrade request fields incorrect - request_invalid, - - /// Upgrade request denied - request_denied, - - /// General WebSocket error - general + /// Upgrade handshake failed + handshake_failed }; } // websocket diff --git a/src/beast/include/beast/websocket/impl/accept.ipp b/src/beast/include/beast/websocket/impl/accept.ipp index 877be120c1..892dcffaee 100644 --- a/src/beast/include/beast/websocket/impl/accept.ipp +++ b/src/beast/include/beast/websocket/impl/accept.ipp @@ -8,16 +8,20 @@ #ifndef BEAST_WEBSOCKET_IMPL_ACCEPT_IPP #define BEAST_WEBSOCKET_IMPL_ACCEPT_IPP -#include -#include +#include +#include +#include #include #include #include -#include +#include #include -#include #include +#include +#include +#include #include +#include #include #include @@ -35,23 +39,37 @@ class stream::response_op { bool cont; stream& ws; - http::response res; - error_code final_ec; + response_type res; int state = 0; - template - data(Handler&, stream& ws_, - http::request const& req, - bool cont_) + template + data(Handler&, stream& ws_, http::header< + true, http::basic_fields> const& req, + Decorator const& decorator, + bool cont_) : cont(cont_) , ws(ws_) - , res(ws_.build_response(req)) + , res(ws_.build_response(req, decorator)) { - // can't call stream::reset() here - // otherwise accept_op will malfunction - // - if(res.status != 101) - final_ec = error::handshake_failed; + } + + template + data(Handler&, stream& ws_, http::header< + true, http::basic_fields> const& req, + Buffers const& buffers, + Decorator const& decorator, + bool cont_) + : cont(cont_) + , ws(ws_) + , res(ws_.build_response(req, decorator)) + { + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + // VFALCO What about catch(std::length_error const&)? + ws.stream_.buffer().commit(buffer_copy( + ws.stream_.buffer().prepare( + buffer_size(buffers)), buffers)); } }; @@ -77,16 +95,18 @@ public: void* asio_handler_allocate( std::size_t size, response_op* op) { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); + using boost::asio::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(op->d_.handler())); } friend void asio_handler_deallocate( void* p, std::size_t size, response_op* op) { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->d_.handler())); } friend @@ -99,8 +119,9 @@ public: friend void asio_handler_invoke(Function&& f, response_op* op) { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->d_.handler())); } }; @@ -126,12 +147,13 @@ operator()(error_code ec, bool again) // sent response case 1: d.state = 99; - ec = d.final_ec; + if(d.res.result() != + http::status::switching_protocols) + ec = error::handshake_failed; if(! ec) { - pmd_read( - d.ws.pmd_config_, d.res.fields); - d.ws.open(detail::role_type::server); + pmd_read(d.ws.pmd_config_, d.res); + d.ws.open(role_type::server); } break; } @@ -144,26 +166,32 @@ operator()(error_code ec, bool again) // read and respond to an upgrade request // template -template +template class stream::accept_op { struct data { - bool cont; stream& ws; - http::request req; - int state = 0; + Decorator decorator; + http::request_parser p; + + data(Handler&, stream& ws_, + Decorator const& decorator_) + : ws(ws_) + , decorator(decorator_) + { + } template - data(Handler& handler, stream& ws_, - Buffers const& buffers) - : cont(beast_asio_helpers:: - is_continuation(handler)) - , ws(ws_) + data(Handler&, stream& ws_, + Buffers const& buffers, + Decorator const& decorator_) + : ws(ws_) + , decorator(decorator_) { using boost::asio::buffer_copy; using boost::asio::buffer_size; - ws.reset(); + // VFALCO What about catch(std::length_error const&)? ws.stream_.buffer().commit(buffer_copy( ws.stream_.buffer().prepare( buffer_size(buffers)), buffers)); @@ -182,150 +210,122 @@ public: : d_(std::forward(h), ws, std::forward(args)...) { - (*this)(error_code{}, 0, false); } - void operator()(error_code const& ec) - { - (*this)(ec, 0); - } + void operator()(); - void operator()(error_code const& ec, - std::size_t bytes_transferred, bool again = true); + void operator()(error_code ec); friend void* asio_handler_allocate( std::size_t size, accept_op* op) { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); + using boost::asio::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(op->d_.handler())); } friend void asio_handler_deallocate( void* p, std::size_t size, accept_op* op) { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->d_.handler())); } friend bool asio_handler_is_continuation(accept_op* op) { - return op->d_->cont; + using boost::asio::asio_handler_is_continuation; + return asio_handler_is_continuation( + std::addressof(op->d_.handler())); } template friend void asio_handler_invoke(Function&& f, accept_op* op) { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->d_.handler())); } }; template -template +template void -stream::accept_op:: -operator()(error_code const& ec, - std::size_t bytes_transferred, bool again) +stream::accept_op:: +operator()() { - beast::detail::ignore_unused(bytes_transferred); auto& d = *d_; - d.cont = d.cont || again; - while(! ec && d.state != 99) - { - switch(d.state) - { - case 0: - // read message - d.state = 1; - http::async_read(d.ws.next_layer(), - d.ws.stream_.buffer(), d.req, - std::move(*this)); - return; + http::async_read_header(d.ws.next_layer(), + d.ws.stream_.buffer(), d.p, + std::move(*this)); +} - // got message - case 1: - { - // respond to request - auto& ws = d.ws; - auto req = std::move(d.req); - response_op{ - d_.release_handler(), ws, req, true}; - return; - } - } +template +template +void +stream::accept_op:: +operator()(error_code ec) +{ + auto& d = *d_; + if(! ec) + { + BOOST_ASSERT(d.p.is_header_done()); + // Arguments from our state must be + // moved to the stack before releasing + // the handler. + auto& ws = d.ws; + auto const req = d.p.release(); + auto const decorator = d.decorator; + #if 1 + response_op{ + d_.release_handler(), + ws, req, decorator, true}; + #else + // VFALCO This *should* work but breaks + // coroutine invariants in the unit test. + // Also it calls reset() when it shouldn't. + ws.async_accept_ex( + req, decorator, d_.release_handler()); + #endif + return; } d_.invoke(ec); } -template -template -typename async_completion< - AcceptHandler, void(error_code)>::result_type -stream:: -async_accept(AcceptHandler&& handler) -{ - static_assert(is_AsyncStream::value, - "AsyncStream requirements requirements not met"); - return async_accept(boost::asio::null_buffers{}, - std::forward(handler)); -} - -template -template -typename async_completion< - AcceptHandler, void(error_code)>::result_type -stream:: -async_accept(ConstBufferSequence const& bs, AcceptHandler&& handler) -{ - static_assert(is_AsyncStream::value, - "AsyncStream requirements requirements not met"); - static_assert(beast::is_ConstBufferSequence< - ConstBufferSequence>::value, - "ConstBufferSequence requirements not met"); - beast::async_completion< - AcceptHandler, void(error_code) - > completion{handler}; - accept_op{ - completion.handler, *this, bs}; - return completion.result.get(); -} - -template -template -typename async_completion< - AcceptHandler, void(error_code)>::result_type -stream:: -async_accept(http::request const& req, - AcceptHandler&& handler) -{ - static_assert(is_AsyncStream::value, - "AsyncStream requirements requirements not met"); - beast::async_completion< - AcceptHandler, void(error_code) - > completion{handler}; - reset(); - response_op{ - completion.handler, *this, req, - beast_asio_helpers:: - is_continuation(completion.handler)}; - return completion.result.get(); -} +//------------------------------------------------------------------------------ template void stream:: accept() { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, "SyncStream requirements not met"); error_code ec; - accept(boost::asio::null_buffers{}, ec); + accept(ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); +} + +template +template +void +stream:: +accept_ex(ResponseDecorator const& decorator) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + error_code ec; + accept_ex(decorator, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); } template @@ -333,93 +333,477 @@ void stream:: accept(error_code& ec) { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, "SyncStream requirements not met"); - accept(boost::asio::null_buffers{}, ec); + reset(); + do_accept(&default_decorate_res, ec); +} + +template +template +void +stream:: +accept_ex(ResponseDecorator const& decorator, error_code& ec) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + reset(); + do_accept(decorator, ec); } template template -void +typename std::enable_if::value>::type stream:: accept(ConstBufferSequence const& buffers) { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, "SyncStream requirements not met"); - static_assert(is_ConstBufferSequence< + static_assert(is_const_buffer_sequence< ConstBufferSequence>::value, "ConstBufferSequence requirements not met"); error_code ec; accept(buffers, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); +} + +template +template< + class ConstBufferSequence, class ResponseDecorator> +typename std::enable_if::value>::type +stream:: +accept_ex(ConstBufferSequence const& buffers, + ResponseDecorator const &decorator) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + static_assert(is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + error_code ec; + accept_ex(buffers, decorator, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); } template template -void +typename std::enable_if::value>::type stream:: accept(ConstBufferSequence const& buffers, error_code& ec) { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, "SyncStream requirements not met"); - static_assert(beast::is_ConstBufferSequence< + static_assert(is_const_buffer_sequence< ConstBufferSequence>::value, "ConstBufferSequence requirements not met"); + reset(); using boost::asio::buffer_copy; using boost::asio::buffer_size; - reset(); stream_.buffer().commit(buffer_copy( stream_.buffer().prepare( buffer_size(buffers)), buffers)); - http::request m; - http::read(next_layer(), stream_.buffer(), m, ec); - if(ec) - return; - accept(m, ec); + do_accept(&default_decorate_res, ec); } template -template +template< + class ConstBufferSequence, class ResponseDecorator> +typename std::enable_if::value>::type +stream:: +accept_ex(ConstBufferSequence const& buffers, + ResponseDecorator const& decorator, error_code& ec) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + static_assert(is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + static_assert(is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + reset(); + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + stream_.buffer().commit(buffer_copy( + stream_.buffer().prepare( + buffer_size(buffers)), buffers)); + do_accept(decorator, ec); +} + +template +template void stream:: -accept(http::request const& request) +accept(http::header> const& req) { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, "SyncStream requirements not met"); error_code ec; - accept(request, ec); + accept(req, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); } template -template +template void stream:: -accept(http::request const& req, - error_code& ec) +accept_ex(http::header> const& req, + ResponseDecorator const& decorator) { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + error_code ec; + accept_ex(req, decorator, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); +} + +template +template +void +stream:: +accept(http::header> const& req, + error_code& ec) +{ + static_assert(is_sync_stream::value, "SyncStream requirements not met"); reset(); - auto const res = build_response(req); - http::write(stream_, res, ec); + do_accept(req, &default_decorate_res, ec); +} + +template +template +void +stream:: +accept_ex(http::header> const& req, + ResponseDecorator const& decorator, error_code& ec) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + reset(); + do_accept(req, decorator, ec); +} + +template +template +void +stream:: +accept(http::header> const& req, + ConstBufferSequence const& buffers) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + static_assert(is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + error_code ec; + accept(req, buffers, ec); if(ec) - return; - if(res.status != 101) - { - ec = error::handshake_failed; - // VFALCO TODO Respect keep alive setting, perform - // teardown if Connection: close. - return; - } - pmd_read(pmd_config_, req.fields); - open(detail::role_type::server); + BOOST_THROW_EXCEPTION(system_error{ec}); +} + +template +template +void +stream:: +accept_ex(http::header> const& req, + ConstBufferSequence const& buffers, + ResponseDecorator const& decorator) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + static_assert(is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + error_code ec; + accept_ex(req, buffers, decorator, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); +} + +template +template +void +stream:: +accept(http::header const& req, + ConstBufferSequence const& buffers, error_code& ec) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + static_assert(is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + reset(); + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + stream_.buffer().commit(buffer_copy( + stream_.buffer().prepare( + buffer_size(buffers)), buffers)); + do_accept(req, &default_decorate_res, ec); +} + +template +template +void +stream:: +accept_ex(http::header> const& req, + ConstBufferSequence const& buffers, + ResponseDecorator const& decorator, + error_code& ec) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + static_assert(is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + reset(); + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + stream_.buffer().commit(buffer_copy( + stream_.buffer().prepare( + buffer_size(buffers)), buffers)); + do_accept(req, decorator, ec); } //------------------------------------------------------------------------------ +template +template +async_return_type< + AcceptHandler, void(error_code)> +stream:: +async_accept(AcceptHandler&& handler) +{ + static_assert(is_async_stream::value, + "AsyncStream requirements requirements not met"); + async_completion init{handler}; + reset(); + accept_op>{ + init.completion_handler, *this, &default_decorate_res}(); + return init.result.get(); +} + +template +template +async_return_type< + AcceptHandler, void(error_code)> +stream:: +async_accept_ex(ResponseDecorator const& decorator, + AcceptHandler&& handler) +{ + static_assert(is_async_stream::value, + "AsyncStream requirements requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + async_completion init{handler}; + reset(); + accept_op>{ + init.completion_handler, *this, decorator}(); + return init.result.get(); +} + +template +template +typename std::enable_if< + ! http::detail::is_header::value, + async_return_type>::type +stream:: +async_accept(ConstBufferSequence const& buffers, + AcceptHandler&& handler) +{ + static_assert(is_async_stream::value, + "AsyncStream requirements requirements not met"); + static_assert(is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + async_completion init{handler}; + reset(); + accept_op>{ + init.completion_handler, *this, buffers, + &default_decorate_res}(); + return init.result.get(); +} + +template +template +typename std::enable_if< + ! http::detail::is_header::value, + async_return_type>::type +stream:: +async_accept_ex(ConstBufferSequence const& buffers, + ResponseDecorator const& decorator, + AcceptHandler&& handler) +{ + static_assert(is_async_stream::value, + "AsyncStream requirements requirements not met"); + static_assert(is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + async_completion init{handler}; + reset(); + accept_op>{ + init.completion_handler, *this, buffers, + decorator}(); + return init.result.get(); +} + +template +template +async_return_type< + AcceptHandler, void(error_code)> +stream:: +async_accept(http::header> const& req, + AcceptHandler&& handler) +{ + static_assert(is_async_stream::value, + "AsyncStream requirements requirements not met"); + async_completion init{handler}; + reset(); + using boost::asio::asio_handler_is_continuation; + response_op>{init.completion_handler, + *this, req, &default_decorate_res, + asio_handler_is_continuation( + std::addressof(init.completion_handler))}; + return init.result.get(); +} + +template +template +async_return_type< + AcceptHandler, void(error_code)> +stream:: +async_accept_ex(http::header> const& req, + ResponseDecorator const& decorator, AcceptHandler&& handler) +{ + static_assert(is_async_stream::value, + "AsyncStream requirements requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + async_completion init{handler}; + reset(); + using boost::asio::asio_handler_is_continuation; + response_op>{ + init.completion_handler, *this, req, decorator, + asio_handler_is_continuation( + std::addressof(init.completion_handler))}; + return init.result.get(); +} + +template +template +async_return_type< + AcceptHandler, void(error_code)> +stream:: +async_accept(http::header> const& req, + ConstBufferSequence const& buffers, + AcceptHandler&& handler) +{ + static_assert(is_async_stream::value, + "AsyncStream requirements requirements not met"); + static_assert(is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + async_completion init{handler}; + reset(); + using boost::asio::asio_handler_is_continuation; + response_op>{ + init.completion_handler, *this, req, buffers, + &default_decorate_res, asio_handler_is_continuation( + std::addressof(init.completion_handler))}; + return init.result.get(); +} + +template +template +async_return_type< + AcceptHandler, void(error_code)> +stream:: +async_accept_ex(http::header> const& req, + ConstBufferSequence const& buffers, + ResponseDecorator const& decorator, + AcceptHandler&& handler) +{ + static_assert(is_async_stream::value, + "AsyncStream requirements requirements not met"); + static_assert(is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + async_completion init{handler}; + reset(); + using boost::asio::asio_handler_is_continuation; + response_op>{init.completion_handler, + *this, req, buffers, decorator, asio_handler_is_continuation( + std::addressof(init.completion_handler))}; + return init.result.get(); +} + } // websocket } // beast diff --git a/src/beast/include/beast/websocket/impl/close.ipp b/src/beast/include/beast/websocket/impl/close.ipp index 983126f924..add4bd6b65 100644 --- a/src/beast/include/beast/websocket/impl/close.ipp +++ b/src/beast/include/beast/websocket/impl/close.ipp @@ -8,10 +8,14 @@ #ifndef BEAST_WEBSOCKET_IMPL_CLOSE_IPP #define BEAST_WEBSOCKET_IMPL_CLOSE_IPP -#include #include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include namespace beast { @@ -25,25 +29,20 @@ template template class stream::close_op { - using fb_type = detail::frame_streambuf; - struct data : op { - bool cont; stream& ws; close_reason cr; - fb_type fb; + detail::frame_streambuf fb; int state = 0; - data(Handler& handler, stream& ws_, + data(Handler&, stream& ws_, close_reason const& cr_) - : cont(beast_asio_helpers:: - is_continuation(handler)) - , ws(ws_) + : ws(ws_) , cr(cr_) { ws.template write_close< - static_streambuf>(fb, cr); + static_buffer>(fb, cr); } }; @@ -59,48 +58,50 @@ public: : d_(std::forward(h), ws, std::forward(args)...) { - (*this)(error_code{}, false); } void operator()() { - (*this)(error_code{}); + (*this)({}); } void - operator()(error_code ec, std::size_t); - - void - operator()(error_code ec, bool again = true); + operator()(error_code ec, + std::size_t bytes_transferred = 0); friend void* asio_handler_allocate( std::size_t size, close_op* op) { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); + using boost::asio::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(op->d_.handler())); } friend void asio_handler_deallocate( void* p, std::size_t size, close_op* op) { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->d_.handler())); } friend bool asio_handler_is_continuation(close_op* op) { - return op->d_->cont; + using boost::asio::asio_handler_is_continuation; + return asio_handler_is_continuation( + std::addressof(op->d_.handler())); } template friend void asio_handler_invoke(Function&& f, close_op* op) { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->d_.handler())); } }; @@ -112,104 +113,90 @@ operator()(error_code ec, std::size_t) { auto& d = *d_; if(ec) - d.ws.failed_ = true; - (*this)(ec); -} - -template -template -void -stream::close_op:: -operator()(error_code ec, bool again) -{ - auto& d = *d_; - d.cont = d.cont || again; - if(ec) - goto upcall; - for(;;) { - switch(d.state) + BOOST_ASSERT(d.ws.wr_block_ == &d); + d.ws.failed_ = true; + goto upcall; + } + switch(d.state) + { + case 0: + if(d.ws.wr_block_) { - case 0: - if(d.ws.wr_block_) - { - // suspend - d.state = 2; - d.ws.wr_op_.template emplace< - close_op>(std::move(*this)); - return; - } - if(d.ws.failed_ || d.ws.wr_close_) - { - // call handler - d.ws.get_io_service().post( - bind_handler(std::move(*this), - boost::asio::error::operation_aborted)); - return; - } - d.ws.wr_block_ = &d; - // [[fallthrough]] - - case 1: - // send close frame - BOOST_ASSERT(d.ws.wr_block_ == &d); - d.state = 99; - d.ws.wr_close_ = true; - boost::asio::async_write(d.ws.stream_, - d.fb.data(), std::move(*this)); - return; - - case 2: - BOOST_ASSERT(! d.ws.wr_block_); - d.ws.wr_block_ = &d; - d.state = 3; - // The current context is safe but might not be - // the same as the one for this operation (since - // we are being called from a write operation). - // Call post to make sure we are invoked the same - // way as the final handler for this operation. - d.ws.get_io_service().post( - bind_handler(std::move(*this), ec)); - return; - - case 3: - BOOST_ASSERT(d.ws.wr_block_ == &d); - if(d.ws.failed_ || d.ws.wr_close_) - { - // call handler - ec = boost::asio::error::operation_aborted; - goto upcall; - } + // suspend d.state = 1; - break; + d.ws.close_op_.emplace(std::move(*this)); + return; + } + d.ws.wr_block_ = &d; + if(d.ws.failed_ || d.ws.wr_close_) + { + // call handler + d.ws.get_io_service().post( + bind_handler(std::move(*this), + boost::asio::error::operation_aborted)); + return; + } - case 99: + do_write: + // send close frame + BOOST_ASSERT(d.ws.wr_block_ == &d); + d.state = 3; + d.ws.wr_close_ = true; + boost::asio::async_write(d.ws.stream_, + d.fb.data(), std::move(*this)); + return; + + case 1: + BOOST_ASSERT(! d.ws.wr_block_); + d.ws.wr_block_ = &d; + d.state = 2; + // The current context is safe but might not be + // the same as the one for this operation (since + // we are being called from a write operation). + // Call post to make sure we are invoked the same + // way as the final handler for this operation. + d.ws.get_io_service().post( + bind_handler(std::move(*this), ec)); + return; + + case 2: + BOOST_ASSERT(d.ws.wr_block_ == &d); + if(d.ws.failed_ || d.ws.wr_close_) + { + // call handler + ec = boost::asio::error::operation_aborted; goto upcall; } + goto do_write; + + case 3: + break; } upcall: - if(d.ws.wr_block_ == &d) - d.ws.wr_block_ = nullptr; + BOOST_ASSERT(d.ws.wr_block_ == &d); + d.ws.wr_block_ = nullptr; d.ws.rd_op_.maybe_invoke() || - d.ws.ping_op_.maybe_invoke(); + d.ws.ping_op_.maybe_invoke() || + d.ws.wr_op_.maybe_invoke(); d_.invoke(ec); } template template -typename async_completion< - CloseHandler, void(error_code)>::result_type +async_return_type< + CloseHandler, void(error_code)> stream:: async_close(close_reason const& cr, CloseHandler&& handler) { - static_assert(is_AsyncStream::value, + static_assert(is_async_stream::value, "AsyncStream requirements not met"); - beast::async_completion< - CloseHandler, void(error_code) - > completion{handler}; - close_op{ - completion.handler, *this, cr}; - return completion.result.get(); + async_completion init{handler}; + close_op>{ + init.completion_handler, *this, cr}({}); + return init.result.get(); } template @@ -217,12 +204,12 @@ void stream:: close(close_reason const& cr) { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, "SyncStream requirements not met"); error_code ec; close(cr, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); } template @@ -230,14 +217,19 @@ void stream:: close(close_reason const& cr, error_code& ec) { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, "SyncStream requirements not met"); BOOST_ASSERT(! wr_close_); + if(wr_close_) + { + ec = boost::asio::error::operation_aborted; + return; + } wr_close_ = true; detail::frame_streambuf fb; - write_close(fb, cr); + write_close(fb, cr); boost::asio::write(stream_, fb.data(), ec); - failed_ = ec != 0; + failed_ = !!ec; } //------------------------------------------------------------------------------ diff --git a/src/beast/include/beast/websocket/impl/error.ipp b/src/beast/include/beast/websocket/impl/error.ipp index a31d202054..96b0358560 100644 --- a/src/beast/include/beast/websocket/impl/error.ipp +++ b/src/beast/include/beast/websocket/impl/error.ipp @@ -28,7 +28,7 @@ public: const char* name() const noexcept override { - return "websocket"; + return "beast.websocket"; } std::string @@ -39,16 +39,9 @@ public: case error::closed: return "WebSocket connection closed normally"; case error::failed: return "WebSocket connection failed due to a protocol violation"; case error::handshake_failed: return "WebSocket Upgrade handshake failed"; - case error::keep_alive: return "WebSocket Upgrade handshake failed but connection is still open"; - case error::response_malformed: return "malformed HTTP response"; - case error::response_failed: return "upgrade request failed"; - case error::response_denied: return "upgrade request denied"; - case error::request_malformed: return "malformed HTTP request"; - case error::request_invalid: return "upgrade request invalid"; - case error::request_denied: return "upgrade request denied"; default: - return "websocket error"; + return "beast.websocket error"; } } diff --git a/src/beast/include/beast/websocket/impl/handshake.ipp b/src/beast/include/beast/websocket/impl/handshake.ipp index 11ea56181a..1231b4a852 100644 --- a/src/beast/include/beast/websocket/impl/handshake.ipp +++ b/src/beast/include/beast/websocket/impl/handshake.ipp @@ -8,14 +8,18 @@ #ifndef BEAST_WEBSOCKET_IMPL_HANDSHAKE_IPP #define BEAST_WEBSOCKET_IMPL_HANDSHAKE_IPP +#include #include #include #include #include -#include #include -#include +#include +#include +#include +#include #include +#include #include namespace beast { @@ -33,19 +37,25 @@ class stream::handshake_op { bool cont; stream& ws; - std::string key; + response_type* res_p; + detail::sec_ws_key_type key; http::request req; - http::response resp; + response_type res; int state = 0; + template data(Handler& handler, stream& ws_, - boost::string_ref const& host, - boost::string_ref const& resource) - : cont(beast_asio_helpers:: - is_continuation(handler)) - , ws(ws_) - , req(ws.build_request(host, resource, key)) + response_type* res_p_, + string_view host, + string_view target, + Decorator const& decorator) + : ws(ws_) + , res_p(res_p_) + , req(ws.build_request(key, + host, target, decorator)) { + using boost::asio::asio_handler_is_continuation; + cont = asio_handler_is_continuation(std::addressof(handler)); ws.reset(); } }; @@ -72,16 +82,18 @@ public: void* asio_handler_allocate( std::size_t size, handshake_op* op) { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); + using boost::asio::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(op->d_.handler())); } friend void asio_handler_deallocate( void* p, std::size_t size, handshake_op* op) { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->d_.handler())); } friend @@ -94,8 +106,9 @@ public: friend void asio_handler_invoke(Function&& f, handshake_op* op) { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->d_.handler())); } }; @@ -117,10 +130,13 @@ operator()(error_code ec, bool again) d.state = 1; // VFALCO Do we need the ability to move // a message on the async_write? - pmd_read( - d.ws.pmd_config_, d.req.fields); + // + pmd_read(d.ws.pmd_config_, d.req); http::async_write(d.ws.stream_, d.req, std::move(*this)); + // TODO We don't need d.req now. Figure + // out a way to make it a parameter instead + // of a state variable to reduce footprint. return; } @@ -129,78 +145,245 @@ operator()(error_code ec, bool again) // read http response d.state = 2; http::async_read(d.ws.next_layer(), - d.ws.stream_.buffer(), d.resp, + d.ws.stream_.buffer(), d.res, std::move(*this)); return; // got response case 2: { - d.ws.do_response(d.resp, d.key, ec); + d.ws.do_response(d.res, d.key, ec); // call handler d.state = 99; break; } } } + if(d.res_p) + swap(d.res, *d.res_p); d_.invoke(ec); } template template -typename async_completion< - HandshakeHandler, void(error_code)>::result_type +async_return_type< + HandshakeHandler, void(error_code)> stream:: -async_handshake(boost::string_ref const& host, - boost::string_ref const& resource, HandshakeHandler&& handler) +async_handshake(string_view host, + string_view target, + HandshakeHandler&& handler) { - static_assert(is_AsyncStream::value, + static_assert(is_async_stream::value, "AsyncStream requirements not met"); - beast::async_completion< - HandshakeHandler, void(error_code) - > completion{handler}; - handshake_op{ - completion.handler, *this, host, resource}; - return completion.result.get(); + async_completion init{handler}; + handshake_op>{ + init.completion_handler, *this, nullptr, host, + target, &default_decorate_req}; + return init.result.get(); +} + +template +template +async_return_type< + HandshakeHandler, void(error_code)> +stream:: +async_handshake(response_type& res, + string_view host, + string_view target, + HandshakeHandler&& handler) +{ + static_assert(is_async_stream::value, + "AsyncStream requirements not met"); + async_completion init{handler}; + handshake_op>{ + init.completion_handler, *this, &res, host, + target, &default_decorate_req}; + return init.result.get(); +} + +template +template +async_return_type< + HandshakeHandler, void(error_code)> +stream:: +async_handshake_ex(string_view host, + string_view target, + RequestDecorator const& decorator, + HandshakeHandler&& handler) +{ + static_assert(is_async_stream::value, + "AsyncStream requirements not met"); + static_assert(detail::is_RequestDecorator< + RequestDecorator>::value, + "RequestDecorator requirements not met"); + async_completion init{handler}; + handshake_op>{ + init.completion_handler, *this, nullptr, host, + target, decorator}; + return init.result.get(); +} + +template +template +async_return_type< + HandshakeHandler, void(error_code)> +stream:: +async_handshake_ex(response_type& res, + string_view host, + string_view target, + RequestDecorator const& decorator, + HandshakeHandler&& handler) +{ + static_assert(is_async_stream::value, + "AsyncStream requirements not met"); + static_assert(detail::is_RequestDecorator< + RequestDecorator>::value, + "RequestDecorator requirements not met"); + async_completion init{handler}; + handshake_op>{ + init.completion_handler, *this, &res, host, + target, decorator}; + return init.result.get(); } template void stream:: -handshake(boost::string_ref const& host, - boost::string_ref const& resource) +handshake(string_view host, + string_view target) { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, "SyncStream requirements not met"); error_code ec; - handshake(host, resource, ec); + handshake( + host, target, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); } template void stream:: -handshake(boost::string_ref const& host, - boost::string_ref const& resource, error_code& ec) +handshake(response_type& res, + string_view host, + string_view target) { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, "SyncStream requirements not met"); - reset(); - std::string key; - { - auto const req = - build_request(host, resource, key); - pmd_read(pmd_config_, req.fields); - http::write(stream_, req, ec); - } + error_code ec; + handshake(res, host, target, ec); if(ec) - return; - http::response res; - http::read(next_layer(), stream_.buffer(), res, ec); + BOOST_THROW_EXCEPTION(system_error{ec}); +} + +template +template +void +stream:: +handshake_ex(string_view host, + string_view target, + RequestDecorator const& decorator) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + static_assert(detail::is_RequestDecorator< + RequestDecorator>::value, + "RequestDecorator requirements not met"); + error_code ec; + handshake_ex(host, target, decorator, ec); if(ec) - return; - do_response(res, key, ec); + BOOST_THROW_EXCEPTION(system_error{ec}); +} + +template +template +void +stream:: +handshake_ex(response_type& res, + string_view host, + string_view target, + RequestDecorator const& decorator) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + static_assert(detail::is_RequestDecorator< + RequestDecorator>::value, + "RequestDecorator requirements not met"); + error_code ec; + handshake_ex(res, host, target, decorator, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); +} + +template +void +stream:: +handshake(string_view host, + string_view target, error_code& ec) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + do_handshake(nullptr, + host, target, &default_decorate_req, ec); +} + +template +void +stream:: +handshake(response_type& res, + string_view host, + string_view target, + error_code& ec) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + do_handshake(&res, + host, target, &default_decorate_req, ec); +} + +template +template +void +stream:: +handshake_ex(string_view host, + string_view target, + RequestDecorator const& decorator, + error_code& ec) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + static_assert(detail::is_RequestDecorator< + RequestDecorator>::value, + "RequestDecorator requirements not met"); + do_handshake(nullptr, + host, target, decorator, ec); +} + +template +template +void +stream:: +handshake_ex(response_type& res, + string_view host, + string_view target, + RequestDecorator const& decorator, + error_code& ec) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + static_assert(detail::is_RequestDecorator< + RequestDecorator>::value, + "RequestDecorator requirements not met"); + do_handshake(&res, + host, target, decorator, ec); } //------------------------------------------------------------------------------ diff --git a/src/beast/include/beast/websocket/impl/ping.ipp b/src/beast/include/beast/websocket/impl/ping.ipp index 59071376c9..dc37278e3b 100644 --- a/src/beast/include/beast/websocket/impl/ping.ipp +++ b/src/beast/include/beast/websocket/impl/ping.ipp @@ -9,10 +9,14 @@ #define BEAST_WEBSOCKET_IMPL_PING_IPP #include -#include #include -#include +#include +#include #include +#include +#include +#include +#include #include namespace beast { @@ -28,21 +32,18 @@ class stream::ping_op { struct data : op { - bool cont; stream& ws; detail::frame_streambuf fb; int state = 0; - data(Handler& handler, stream& ws_, - opcode op_, ping_data const& payload) - : cont(beast_asio_helpers:: - is_continuation(handler)) - , ws(ws_) + data(Handler&, stream& ws_, + detail::opcode op_, ping_data const& payload) + : ws(ws_) { using boost::asio::buffer; using boost::asio::buffer_copy; ws.template write_ping< - static_streambuf>(fb, op_, payload); + static_buffer>(fb, op_, payload); } }; @@ -58,175 +59,162 @@ public: : d_(std::forward(h), ws, std::forward(args)...) { - (*this)(error_code{}, false); } void operator()() { - (*this)(error_code{}); + (*this)({}); } - void operator()(error_code ec, std::size_t); - - void operator()(error_code ec, bool again = true); + void operator()(error_code ec, + std::size_t bytes_transferred = 0); friend void* asio_handler_allocate( std::size_t size, ping_op* op) { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); + using boost::asio::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(op->d_.handler())); } friend void asio_handler_deallocate( void* p, std::size_t size, ping_op* op) { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->d_.handler())); } friend bool asio_handler_is_continuation(ping_op* op) { - return op->d_->cont; + using boost::asio::asio_handler_is_continuation; + return asio_handler_is_continuation( + std::addressof(op->d_.handler())); } template friend void asio_handler_invoke(Function&& f, ping_op* op) { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->d_.handler())); } }; template template void -stream::ping_op:: +stream:: +ping_op:: operator()(error_code ec, std::size_t) { auto& d = *d_; if(ec) - d.ws.failed_ = true; - (*this)(ec); -} - -template -template -void -stream:: -ping_op:: -operator()(error_code ec, bool again) -{ - auto& d = *d_; - d.cont = d.cont || again; - if(ec) - goto upcall; - for(;;) { - switch(d.state) + BOOST_ASSERT(d.ws.wr_block_ == &d); + d.ws.failed_ = true; + goto upcall; + } + switch(d.state) + { + case 0: + if(d.ws.wr_block_) { - case 0: - if(d.ws.wr_block_) - { - // suspend - d.state = 2; - d.ws.ping_op_.template emplace< - ping_op>(std::move(*this)); - return; - } - if(d.ws.failed_ || d.ws.wr_close_) - { - // call handler - d.state = 99; - d.ws.get_io_service().post( - bind_handler(std::move(*this), - boost::asio::error::operation_aborted)); - return; - } - d.ws.wr_block_ = &d; - // [[fallthrough]] - - case 1: - // send ping frame - BOOST_ASSERT(d.ws.wr_block_ == &d); - d.state = 99; - boost::asio::async_write(d.ws.stream_, - d.fb.data(), std::move(*this)); - return; - - case 2: - BOOST_ASSERT(! d.ws.wr_block_); - d.ws.wr_block_ = &d; - d.state = 3; - // The current context is safe but might not be - // the same as the one for this operation (since - // we are being called from a write operation). - // Call post to make sure we are invoked the same - // way as the final handler for this operation. - d.ws.get_io_service().post( - bind_handler(std::move(*this), ec)); - return; - - case 3: - BOOST_ASSERT(d.ws.wr_block_ == &d); - if(d.ws.failed_ || d.ws.wr_close_) - { - // call handler - ec = boost::asio::error::operation_aborted; - goto upcall; - } + // suspend d.state = 1; - break; + d.ws.ping_op_.emplace(std::move(*this)); + return; + } + d.ws.wr_block_ = &d; + if(d.ws.failed_ || d.ws.wr_close_) + { + // call handler + return d.ws.get_io_service().post( + bind_handler(std::move(*this), + boost::asio::error::operation_aborted)); + } - case 99: + do_write: + // send ping frame + BOOST_ASSERT(d.ws.wr_block_ == &d); + d.state = 3; + boost::asio::async_write(d.ws.stream_, + d.fb.data(), std::move(*this)); + return; + + case 1: + BOOST_ASSERT(! d.ws.wr_block_); + d.ws.wr_block_ = &d; + d.state = 2; + // The current context is safe but might not be + // the same as the one for this operation (since + // we are being called from a write operation). + // Call post to make sure we are invoked the same + // way as the final handler for this operation. + d.ws.get_io_service().post( + bind_handler(std::move(*this), ec)); + return; + + case 2: + BOOST_ASSERT(d.ws.wr_block_ == &d); + if(d.ws.failed_ || d.ws.wr_close_) + { + // call handler + ec = boost::asio::error::operation_aborted; goto upcall; } + goto do_write; + + case 3: + break; } upcall: - if(d.ws.wr_block_ == &d) - d.ws.wr_block_ = nullptr; - d.ws.rd_op_.maybe_invoke() || + BOOST_ASSERT(d.ws.wr_block_ == &d); + d.ws.wr_block_ = nullptr; + d.ws.close_op_.maybe_invoke() || + d.ws.rd_op_.maybe_invoke() || d.ws.wr_op_.maybe_invoke(); d_.invoke(ec); } template template -typename async_completion< - WriteHandler, void(error_code)>::result_type +async_return_type< + WriteHandler, void(error_code)> stream:: async_ping(ping_data const& payload, WriteHandler&& handler) { - static_assert(is_AsyncStream::value, + static_assert(is_async_stream::value, "AsyncStream requirements requirements not met"); - beast::async_completion< - WriteHandler, void(error_code) - > completion{handler}; - ping_op{ - completion.handler, *this, - opcode::ping, payload}; - return completion.result.get(); + async_completion init{handler}; + ping_op>{ + init.completion_handler, *this, + detail::opcode::ping, payload}({}); + return init.result.get(); } template template -typename async_completion< - WriteHandler, void(error_code)>::result_type +async_return_type< + WriteHandler, void(error_code)> stream:: async_pong(ping_data const& payload, WriteHandler&& handler) { - static_assert(is_AsyncStream::value, + static_assert(is_async_stream::value, "AsyncStream requirements requirements not met"); - beast::async_completion< - WriteHandler, void(error_code) - > completion{handler}; - ping_op{ - completion.handler, *this, - opcode::pong, payload}; - return completion.result.get(); + async_completion init{handler}; + ping_op>{ + init.completion_handler, *this, + detail::opcode::pong, payload}({}); + return init.result.get(); } template @@ -237,7 +225,7 @@ ping(ping_data const& payload) error_code ec; ping(payload, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); } template @@ -246,8 +234,8 @@ stream:: ping(ping_data const& payload, error_code& ec) { detail::frame_streambuf db; - write_ping( - db, opcode::ping, payload); + write_ping( + db, detail::opcode::ping, payload); boost::asio::write(stream_, db.data(), ec); } @@ -259,7 +247,7 @@ pong(ping_data const& payload) error_code ec; pong(payload, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); } template @@ -268,8 +256,8 @@ stream:: pong(ping_data const& payload, error_code& ec) { detail::frame_streambuf db; - write_ping( - db, opcode::pong, payload); + write_ping( + db, detail::opcode::pong, payload); boost::asio::write(stream_, db.data(), ec); } diff --git a/src/beast/include/beast/websocket/impl/read.ipp b/src/beast/include/beast/websocket/impl/read.ipp index 4defa6743e..ec27dba4f9 100644 --- a/src/beast/include/beast/websocket/impl/read.ipp +++ b/src/beast/include/beast/websocket/impl/read.ipp @@ -9,15 +9,19 @@ #define BEAST_WEBSOCKET_IMPL_READ_IPP #include -#include -#include +#include #include -#include -#include -#include +#include +#include #include +#include +#include +#include +#include #include +#include #include +#include #include #include @@ -46,7 +50,6 @@ class stream::read_frame_op { bool cont; stream& ws; - frame_info& fi; DynamicBuffer& db; fb_type fb; std::uint64_t remain; @@ -57,13 +60,12 @@ class stream::read_frame_op int state = 0; data(Handler& handler, stream& ws_, - frame_info& fi_, DynamicBuffer& sb_) - : cont(beast_asio_helpers:: - is_continuation(handler)) - , ws(ws_) - , fi(fi_) + DynamicBuffer& sb_) + : ws(ws_) , db(sb_) { + using boost::asio::asio_handler_is_continuation; + cont = asio_handler_is_continuation(std::addressof(handler)); } }; @@ -79,7 +81,6 @@ public: : d_(std::forward(h), ws, std::forward(args)...) { - (*this)(error_code{}, 0, false); } void operator()() @@ -102,16 +103,18 @@ public: void* asio_handler_allocate( std::size_t size, read_frame_op* op) { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); + using boost::asio::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(op->d_.handler())); } friend void asio_handler_deallocate( void* p, std::size_t size, read_frame_op* op) { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->d_.handler())); } friend @@ -124,8 +127,9 @@ public: friend void asio_handler_invoke(Function&& f, read_frame_op* op) { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->d_.handler())); } }; @@ -160,9 +164,8 @@ operator()(error_code ec, do_control_payload = 8, do_control = 9, do_pong_resume = 10, - do_pong = 12, + do_ponged = 12, do_close_resume = 14, - do_close = 16, do_teardown = 17, do_fail = 19, @@ -170,10 +173,16 @@ operator()(error_code ec, }; auto& d = *d_; + if(d.state == do_teardown + 1 && ec == boost::asio::error::eof) + { + // Rationale: + // http://stackoverflow.com/questions/25587403/boost-asio-ssl-async-shutdown-always-finishes-with-an-error + ec.assign(0, ec.category()); + } if(! ec) { d.cont = d.cont || again; - close_code::value code = close_code::none; + close_code code = close_code::none; do { switch(d.state) @@ -210,7 +219,7 @@ operator()(error_code ec, d.remain = d.fh.len; if(d.fh.mask) detail::prepare_key(d.key, d.fh.key); - // fall through + BEAST_FALLTHROUGH; case do_read_payload + 1: d.state = do_read_payload + 2; @@ -223,11 +232,11 @@ operator()(error_code ec, case do_read_payload + 2: { d.remain -= bytes_transferred; - auto const pb = prepare_buffers( + auto const pb = buffer_prefix( bytes_transferred, *d.dmb); if(d.fh.mask) detail::mask_inplace(pb, d.key); - if(d.ws.rd_.op == opcode::text) + if(d.ws.rd_.op == detail::opcode::text) { if(! d.ws.rd_.utf8.write(pb) || (d.remain == 0 && d.fh.fin && @@ -285,7 +294,7 @@ operator()(error_code ec, detail::mask_inplace(in, d.key); auto const prev = d.db.size(); detail::inflate(d.ws.pmd_->zi, d.db, in, ec); - d.ws.failed_ = ec != 0; + d.ws.failed_ = !!ec; if(d.ws.failed_) break; if(d.remain == 0 && d.fh.fin) @@ -295,11 +304,11 @@ operator()(error_code ec, 0x00, 0x00, 0xff, 0xff }; detail::inflate(d.ws.pmd_->zi, d.db, buffer(&empty_block[0], 4), ec); - d.ws.failed_ = ec != 0; + d.ws.failed_ = !!ec; if(d.ws.failed_) break; } - if(d.ws.rd_.op == opcode::text) + if(d.ws.rd_.op == detail::opcode::text) { consuming_bufferszi.reset(); d.state = do_frame_done; @@ -333,9 +342,6 @@ operator()(error_code ec, //------------------------------------------------------------------ case do_frame_done: - // call handler - d.fi.op = d.ws.rd_.op; - d.fi.fin = d.fh.fin; goto upcall; //------------------------------------------------------------------ @@ -395,8 +401,8 @@ operator()(error_code ec, d.state = do_control; break; } - if(d.fh.op == opcode::text || - d.fh.op == opcode::binary) + if(d.fh.op == detail::opcode::text || + d.fh.op == detail::opcode::binary) d.ws.rd_begin(); if(d.fh.len == 0 && ! d.fh.fin) { @@ -425,45 +431,46 @@ operator()(error_code ec, //------------------------------------------------------------------ case do_control: - if(d.fh.op == opcode::ping) + if(d.fh.op == detail::opcode::ping) { ping_data payload; detail::read(payload, d.fb.data()); - d.fb.reset(); - if(d.ws.ping_cb_) - d.ws.ping_cb_(false, payload); + d.fb.consume(d.fb.size()); + if(d.ws.ctrl_cb_) + d.ws.ctrl_cb_( + frame_type::ping, payload); if(d.ws.wr_close_) { // ignore ping when closing d.state = do_read_fh; break; } - d.ws.template write_ping( - d.fb, opcode::pong, payload); + d.ws.template write_ping( + d.fb, detail::opcode::pong, payload); if(d.ws.wr_block_) { // suspend - d.state = do_pong_resume; BOOST_ASSERT(d.ws.wr_block_ != &d); - d.ws.rd_op_.template emplace< - read_frame_op>(std::move(*this)); + d.state = do_pong_resume; + d.ws.rd_op_.emplace(std::move(*this)); return; } - d.state = do_pong; - break; + d.ws.wr_block_ = &d; + goto go_pong; } - else if(d.fh.op == opcode::pong) + if(d.fh.op == detail::opcode::pong) { code = close_code::none; ping_data payload; detail::read(payload, d.fb.data()); - if(d.ws.ping_cb_) - d.ws.ping_cb_(true, payload); - d.fb.reset(); + if(d.ws.ctrl_cb_) + d.ws.ctrl_cb_( + frame_type::pong, payload); + d.fb.consume(d.fb.size()); d.state = do_read_fh; break; } - BOOST_ASSERT(d.fh.op == opcode::close); + BOOST_ASSERT(d.fh.op == detail::opcode::close); { detail::read(d.ws.cr_, d.fb.data(), code); if(code != close_code::none) @@ -472,25 +479,28 @@ operator()(error_code ec, d.state = do_fail; break; } + if(d.ws.ctrl_cb_) + d.ws.ctrl_cb_(frame_type::close, + d.ws.cr_.reason); if(! d.ws.wr_close_) { auto cr = d.ws.cr_; if(cr.code == close_code::none) cr.code = close_code::normal; cr.reason = ""; - d.fb.reset(); + d.fb.consume(d.fb.size()); d.ws.template write_close< - static_streambuf>(d.fb, cr); + static_buffer>(d.fb, cr); if(d.ws.wr_block_) { // suspend + BOOST_ASSERT(d.ws.wr_block_ != &d); d.state = do_close_resume; - d.ws.rd_op_.template emplace< - read_frame_op>(std::move(*this)); + d.ws.rd_op_.emplace(std::move(*this)); return; } - d.state = do_close; - break; + d.ws.wr_block_ = &d; + goto go_close; } d.state = do_teardown; break; @@ -502,48 +512,46 @@ operator()(error_code ec, BOOST_ASSERT(! d.ws.wr_block_); d.ws.wr_block_ = &d; d.state = do_pong_resume + 1; + // The current context is safe but might not be + // the same as the one for this operation (since + // we are being called from a write operation). + // Call post to make sure we are invoked the same + // way as the final handler for this operation. d.ws.get_io_service().post(bind_handler( - std::move(*this), ec, bytes_transferred)); + std::move(*this), ec, 0)); return; case do_pong_resume + 1: + BOOST_ASSERT(d.ws.wr_block_ == &d); if(d.ws.failed_) { // call handler ec = boost::asio::error::operation_aborted; goto upcall; } - // [[fallthrough]] - - //------------------------------------------------------------------ - - case do_pong: if(d.ws.wr_close_) { // ignore ping when closing - if(d.ws.wr_block_) - { - BOOST_ASSERT(d.ws.wr_block_ == &d); - d.ws.wr_block_ = nullptr; - } - d.fb.reset(); + d.ws.wr_block_ = nullptr; + d.fb.consume(d.fb.size()); d.state = do_read_fh; break; } + + //------------------------------------------------------------------ + + go_pong: // send pong - if(! d.ws.wr_block_) - d.ws.wr_block_ = &d; - else - BOOST_ASSERT(d.ws.wr_block_ == &d); - d.state = do_pong + 1; + BOOST_ASSERT(d.ws.wr_block_ == &d); + d.state = do_ponged; boost::asio::async_write(d.ws.stream_, d.fb.data(), std::move(*this)); return; - case do_pong + 1: - d.fb.reset(); - d.state = do_read_fh; + case do_ponged: d.ws.wr_block_ = nullptr; + d.fb.consume(d.fb.size()); + d.state = do_read_fh; break; //------------------------------------------------------------------ @@ -571,20 +579,15 @@ operator()(error_code ec, } if(d.ws.wr_close_) { - // call handler + // already sent a close frame ec = error::closed; goto upcall; } - d.state = do_close; - break; //------------------------------------------------------------------ - case do_close: - if(! d.ws.wr_block_) - d.ws.wr_block_ = &d; - else - BOOST_ASSERT(d.ws.wr_block_ == &d); + go_close: + BOOST_ASSERT(d.ws.wr_block_ == &d); d.state = do_teardown; d.ws.wr_close_ = true; boost::asio::async_write(d.ws.stream_, @@ -612,41 +615,51 @@ operator()(error_code ec, d.state = do_fail + 4; break; } - d.fb.reset(); + d.fb.consume(d.fb.size()); d.ws.template write_close< - static_streambuf>(d.fb, code); + static_buffer>(d.fb, code); if(d.ws.wr_block_) { // suspend + BOOST_ASSERT(d.ws.wr_block_ != &d); d.state = do_fail + 2; - d.ws.rd_op_.template emplace< - read_frame_op>(std::move(*this)); + d.ws.rd_op_.emplace(std::move(*this)); return; } - // fall through + d.ws.wr_block_ = &d; + BEAST_FALLTHROUGH; case do_fail + 1: + BOOST_ASSERT(d.ws.wr_block_ == &d); d.ws.failed_ = true; // send close frame d.state = do_fail + 4; d.ws.wr_close_ = true; - BOOST_ASSERT(! d.ws.wr_block_); - d.ws.wr_block_ = &d; boost::asio::async_write(d.ws.stream_, d.fb.data(), std::move(*this)); return; case do_fail + 2: + // resume + BOOST_ASSERT(! d.ws.wr_block_); + d.ws.wr_block_ = &d; d.state = do_fail + 3; + // The current context is safe but might not be + // the same as the one for this operation (since + // we are being called from a write operation). + // Call post to make sure we are invoked the same + // way as the final handler for this operation. d.ws.get_io_service().post(bind_handler( std::move(*this), ec, bytes_transferred)); return; case do_fail + 3: - if(d.ws.failed_) + BOOST_ASSERT(d.ws.wr_block_ == &d); + if(d.ws.failed_ || d.ws.wr_close_) { - d.state = do_fail + 5; - break; + // call handler + ec = error::failed; + goto upcall; } d.state = do_fail + 1; break; @@ -673,61 +686,65 @@ operator()(error_code ec, upcall: if(d.ws.wr_block_ == &d) d.ws.wr_block_ = nullptr; - d.ws.ping_op_.maybe_invoke() || + d.ws.close_op_.maybe_invoke() || + d.ws.ping_op_.maybe_invoke() || d.ws.wr_op_.maybe_invoke(); - d_.invoke(ec); + bool const fin = (! ec) ? d.fh.fin : false; + d_.invoke(ec, fin); } template template -typename async_completion< - ReadHandler, void(error_code)>::result_type +async_return_type< + ReadHandler, void(error_code, bool)> stream:: -async_read_frame(frame_info& fi, - DynamicBuffer& dynabuf, ReadHandler&& handler) +async_read_frame(DynamicBuffer& buffer, ReadHandler&& handler) { - static_assert(is_AsyncStream::value, + static_assert(is_async_stream::value, "AsyncStream requirements requirements not met"); - static_assert(beast::is_DynamicBuffer::value, + static_assert(beast::is_dynamic_buffer::value, "DynamicBuffer requirements not met"); - beast::async_completion< - ReadHandler, void(error_code)> completion{handler}; - read_frame_op{ - completion.handler, *this, fi, dynabuf}; - return completion.result.get(); + async_completion init{handler}; + read_frame_op>{ + init.completion_handler,*this, buffer}( + {}, 0, false); + return init.result.get(); } template template -void +bool stream:: -read_frame(frame_info& fi, DynamicBuffer& dynabuf) +read_frame(DynamicBuffer& buffer) { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, "SyncStream requirements not met"); - static_assert(beast::is_DynamicBuffer::value, + static_assert(beast::is_dynamic_buffer::value, "DynamicBuffer requirements not met"); error_code ec; - read_frame(fi, dynabuf, ec); + auto const fin = read_frame(buffer, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); + return fin; } template template -void +bool stream:: -read_frame(frame_info& fi, DynamicBuffer& dynabuf, error_code& ec) +read_frame(DynamicBuffer& dynabuf, error_code& ec) { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, "SyncStream requirements not met"); - static_assert(beast::is_DynamicBuffer::value, + static_assert(beast::is_dynamic_buffer::value, "DynamicBuffer requirements not met"); using beast::detail::clamp; using boost::asio::buffer; using boost::asio::buffer_cast; using boost::asio::buffer_size; - close_code::value code{}; + close_code code{}; for(;;) { // Read frame header @@ -736,9 +753,9 @@ read_frame(frame_info& fi, DynamicBuffer& dynabuf, error_code& ec) { fb.commit(boost::asio::read( stream_, fb.prepare(2), ec)); - failed_ = ec != 0; + failed_ = !!ec; if(failed_) - return; + return false; { auto const n = read_fh1(fh, fb, code); if(code != close_code::none) @@ -747,16 +764,16 @@ read_frame(frame_info& fi, DynamicBuffer& dynabuf, error_code& ec) { fb.commit(boost::asio::read( stream_, fb.prepare(n), ec)); - failed_ = ec != 0; + failed_ = !!ec; if(failed_) - return; + return false; } } read_fh2(fh, fb, code); - failed_ = ec != 0; + failed_ = !!ec; if(failed_) - return; + return false; if(code != close_code::none) goto do_close; } @@ -768,9 +785,9 @@ read_frame(frame_info& fi, DynamicBuffer& dynabuf, error_code& ec) auto const mb = fb.prepare( static_cast(fh.len)); fb.commit(boost::asio::read(stream_, mb, ec)); - failed_ = ec != 0; + failed_ = !!ec; if(failed_) - return; + return false; if(fh.mask) { detail::prepared_key key; @@ -780,52 +797,54 @@ read_frame(frame_info& fi, DynamicBuffer& dynabuf, error_code& ec) fb.commit(static_cast(fh.len)); } // Process control frame - if(fh.op == opcode::ping) + if(fh.op == detail::opcode::ping) { ping_data payload; detail::read(payload, fb.data()); - fb.reset(); - if(ping_cb_) - ping_cb_(false, payload); - write_ping( - fb, opcode::pong, payload); + fb.consume(fb.size()); + if(ctrl_cb_) + ctrl_cb_(frame_type::ping, payload); + write_ping(fb, + detail::opcode::pong, payload); boost::asio::write(stream_, fb.data(), ec); - failed_ = ec != 0; + failed_ = !!ec; if(failed_) - return; + return false; continue; } - else if(fh.op == opcode::pong) + else if(fh.op == detail::opcode::pong) { ping_data payload; detail::read(payload, fb.data()); - if(ping_cb_) - ping_cb_(true, payload); + if(ctrl_cb_) + ctrl_cb_(frame_type::pong, payload); continue; } - BOOST_ASSERT(fh.op == opcode::close); + BOOST_ASSERT(fh.op == detail::opcode::close); { detail::read(cr_, fb.data(), code); if(code != close_code::none) goto do_close; + if(ctrl_cb_) + ctrl_cb_(frame_type::close, cr_.reason); if(! wr_close_) { auto cr = cr_; if(cr.code == close_code::none) cr.code = close_code::normal; cr.reason = ""; - fb.reset(); + fb.consume(fb.size()); wr_close_ = true; - write_close(fb, cr); + write_close(fb, cr); boost::asio::write(stream_, fb.data(), ec); - failed_ = ec != 0; + failed_ = !!ec; if(failed_) - return; + return false; } goto do_close; } } - if(fh.op != opcode::cont) + if(fh.op != detail::opcode::cont) rd_begin(); if(fh.len == 0 && ! fh.fin) { @@ -853,16 +872,16 @@ read_frame(frame_info& fi, DynamicBuffer& dynabuf, error_code& ec) dynabuf.prepare(clamp(remain)); auto const bytes_transferred = stream_.read_some(b, ec); - failed_ = ec != 0; + failed_ = !!ec; if(failed_) - return; + return false; BOOST_ASSERT(bytes_transferred > 0); remain -= bytes_transferred; - auto const pb = prepare_buffers( + auto const pb = buffer_prefix( bytes_transferred, b); if(fh.mask) detail::mask_inplace(pb, key); - if(rd_.op == opcode::text) + if(rd_.op == detail::opcode::text) { if(! rd_.utf8.write(pb) || (remain == 0 && fh.fin && @@ -885,9 +904,9 @@ read_frame(frame_info& fi, DynamicBuffer& dynabuf, error_code& ec) auto const bytes_transferred = stream_.read_some(buffer(rd_.buf.get(), clamp(remain, rd_.buf_size)), ec); - failed_ = ec != 0; + failed_ = !!ec; if(failed_) - return; + return false; remain -= bytes_transferred; auto const in = buffer( rd_.buf.get(), bytes_transferred); @@ -895,9 +914,9 @@ read_frame(frame_info& fi, DynamicBuffer& dynabuf, error_code& ec) detail::mask_inplace(in, key); auto const prev = dynabuf.size(); detail::inflate(pmd_->zi, dynabuf, in, ec); - failed_ = ec != 0; + failed_ = !!ec; if(failed_) - return; + return false; if(remain == 0 && fh.fin) { static std::uint8_t constexpr @@ -905,11 +924,11 @@ read_frame(frame_info& fi, DynamicBuffer& dynabuf, error_code& ec) 0x00, 0x00, 0xff, 0xff }; detail::inflate(pmd_->zi, dynabuf, buffer(&empty_block[0], 4), ec); - failed_ = ec != 0; + failed_ = !!ec; if(failed_) - return; + return false; } - if(rd_.op == opcode::text) + if(rd_.op == detail::opcode::text) { consuming_bufferszi.reset(); } - fi.op = rd_.op; - fi.fin = fh.fin; - return; + return fh.fin; } do_close: if(code != close_code::none) @@ -945,25 +962,41 @@ do_close: { wr_close_ = true; detail::frame_streambuf fb; - write_close(fb, code); + write_close(fb, code); boost::asio::write(stream_, fb.data(), ec); - failed_ = ec != 0; + failed_ = !!ec; if(failed_) - return; + return false; } websocket_helpers::call_teardown(next_layer(), ec); - failed_ = ec != 0; + if(ec == boost::asio::error::eof) + { + // Rationale: + // http://stackoverflow.com/questions/25587403/boost-asio-ssl-async-shutdown-always-finishes-with-an-error + ec.assign(0, ec.category()); + } + failed_ = !!ec; if(failed_) - return; + return false; ec = error::failed; failed_ = true; - return; + return false; } if(! ec) + { websocket_helpers::call_teardown(next_layer(), ec); + if(ec == boost::asio::error::eof) + { + // (See above) + ec.assign(0, ec.category()); + } + } if(! ec) ec = error::closed; - failed_ = ec != 0; + failed_ = !!ec; + if(failed_) + return false; + return true; } //------------------------------------------------------------------------------ @@ -974,73 +1007,58 @@ template template class stream::read_op { - struct data - { - bool cont; - stream& ws; - opcode& op; - DynamicBuffer& db; - frame_info fi; - int state = 0; - - data(Handler& handler, - stream& ws_, opcode& op_, - DynamicBuffer& sb_) - : cont(beast_asio_helpers:: - is_continuation(handler)) - , ws(ws_) - , op(op_) - , db(sb_) - { - } - }; - - handler_ptr d_; + int state_ = 0; + stream& ws_; + DynamicBuffer& b_; + Handler h_; public: read_op(read_op&&) = default; read_op(read_op const&) = default; - template + template read_op(DeducedHandler&& h, - stream& ws, Args&&... args) - : d_(std::forward(h), - ws, std::forward(args)...) + stream& ws, DynamicBuffer& b) + : ws_(ws) + , b_(b) + , h_(std::forward(h)) { - (*this)(error_code{}, false); } - void operator()( - error_code const& ec, bool again = true); + void operator()(error_code const& ec, bool fin); friend void* asio_handler_allocate( std::size_t size, read_op* op) { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); + using boost::asio::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(op->h_)); } friend void asio_handler_deallocate( void* p, std::size_t size, read_op* op) { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->h_)); } friend bool asio_handler_is_continuation(read_op* op) { - return op->d_->cont; + using boost::asio::asio_handler_is_continuation; + return op->state_ >= 2 ? true: + asio_handler_is_continuation(std::addressof(op->h_)); } template friend void asio_handler_invoke(Function&& f, read_op* op) { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); + using boost::asio::asio_handler_invoke; + asio_handler_invoke(f, std::addressof(op->h_)); } }; @@ -1048,88 +1066,83 @@ template template void stream::read_op:: -operator()(error_code const& ec, bool again) +operator()(error_code const& ec, bool fin) { - auto& d = *d_; - d.cont = d.cont || again; - while(! ec) + switch(state_) { - switch(d.state) - { - case 0: - // read payload - d.state = 1; - d.ws.async_read_frame( - d.fi, d.db, std::move(*this)); - return; + case 0: + state_ = 1; + goto do_read; - // got payload - case 1: - d.op = d.fi.op; - if(d.fi.fin) - goto upcall; - d.state = 0; - break; - } + case 1: + state_ = 2; + BEAST_FALLTHROUGH; + + case 2: + if(ec) + goto upcall; + if(fin) + goto upcall; + do_read: + return ws_.async_read_frame( + b_, std::move(*this)); } upcall: - d_.invoke(ec); + h_(ec); } template template -typename async_completion< - ReadHandler, void(error_code)>::result_type +async_return_type< + ReadHandler, void(error_code)> stream:: -async_read(opcode& op, - DynamicBuffer& dynabuf, ReadHandler&& handler) +async_read(DynamicBuffer& buffer, ReadHandler&& handler) { - static_assert(is_AsyncStream::value, + static_assert(is_async_stream::value, "AsyncStream requirements requirements not met"); - static_assert(beast::is_DynamicBuffer::value, + static_assert(beast::is_dynamic_buffer::value, "DynamicBuffer requirements not met"); - beast::async_completion< - ReadHandler, void(error_code) - > completion{handler}; - read_op{ - completion.handler, *this, op, dynabuf}; - return completion.result.get(); + async_completion init{handler}; + read_op>{ + init.completion_handler, *this, buffer}( + {}, false); + return init.result.get(); } template template void stream:: -read(opcode& op, DynamicBuffer& dynabuf) +read(DynamicBuffer& buffer) { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, "SyncStream requirements not met"); - static_assert(beast::is_DynamicBuffer::value, + static_assert(beast::is_dynamic_buffer::value, "DynamicBuffer requirements not met"); error_code ec; - read(op, dynabuf, ec); + read(buffer, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); } template template void stream:: -read(opcode& op, DynamicBuffer& dynabuf, error_code& ec) +read(DynamicBuffer& buffer, error_code& ec) { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, "SyncStream requirements not met"); - static_assert(beast::is_DynamicBuffer::value, + static_assert(beast::is_dynamic_buffer::value, "DynamicBuffer requirements not met"); - frame_info fi; for(;;) { - read_frame(fi, dynabuf, ec); + auto const fin = read_frame(buffer, ec); if(ec) break; - op = fi.op; - if(fi.fin) + if(fin) break; } } diff --git a/src/beast/include/beast/websocket/impl/rfc6455.ipp b/src/beast/include/beast/websocket/impl/rfc6455.ipp new file mode 100644 index 0000000000..e06b152c4b --- /dev/null +++ b/src/beast/include/beast/websocket/impl/rfc6455.ipp @@ -0,0 +1,38 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_WEBSOCKET_IMPL_RFC6455_IPP +#define BEAST_WEBSOCKET_IMPL_RFC6455_IPP + +#include +#include + +namespace beast { +namespace websocket { + +template +bool +is_upgrade(http::header> const& req) +{ + if(req.version < 11) + return false; + if(req.method() != http::verb::get) + return false; + if(! http::token_list{req["Connection"]}.exists("upgrade")) + return false; + if(! http::token_list{req["Upgrade"]}.exists("websocket")) + return false; + if(! req.count(http::field::sec_websocket_version)) + return false; + return true; +} + +} // websocket +} // beast + +#endif diff --git a/src/beast/include/beast/websocket/impl/ssl.ipp b/src/beast/include/beast/websocket/impl/ssl.ipp index fc73530433..88e8a5e433 100644 --- a/src/beast/include/beast/websocket/impl/ssl.ipp +++ b/src/beast/include/beast/websocket/impl/ssl.ipp @@ -8,16 +8,11 @@ #ifndef BEAST_WEBSOCKET_IMPL_SSL_IPP_INCLUDED #define BEAST_WEBSOCKET_IMPL_SSL_IPP_INCLUDED -#include -#include -#include -#include +#include namespace beast { namespace websocket { -namespace detail { - /* See @@ -32,97 +27,6 @@ Behavior of ssl::stream regarding close_ to async_shutdown will complete with eof. */ -template -class teardown_ssl_op -{ - using stream_type = - boost::asio::ssl::stream; - - struct data - { - bool cont; - stream_type& stream; - int state = 0; - - data(Handler& handler, stream_type& stream_) - : cont(beast_asio_helpers:: - is_continuation(handler)) - , stream(stream_) - { - } - }; - - handler_ptr d_; - -public: - template - explicit - teardown_ssl_op( - DeducedHandler&& h, stream_type& stream) - : d_(std::forward(h), stream) - { - (*this)(error_code{}, false); - } - - void - operator()(error_code ec, bool again = true); - - friend - void* asio_handler_allocate(std::size_t size, - teardown_ssl_op* op) - { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); - } - - friend - void asio_handler_deallocate(void* p, - std::size_t size, teardown_ssl_op* op) - { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); - } - - friend - bool asio_handler_is_continuation( - teardown_ssl_op* op) - { - return op->d_->cont; - } - - template - friend - void asio_handler_invoke(Function&& f, - teardown_ssl_op* op) - { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); - } -}; - -template -void -teardown_ssl_op:: -operator()(error_code ec, bool again) -{ - auto& d = *d_; - d.cont = d.cont || again; - while(!ec && d.state != 99) - { - switch(d.state) - { - case 0: - d.state = 99; - d.stream.async_shutdown(*this); - return; - } - } - d_.invoke(ec); -} - -} // detail - -//------------------------------------------------------------------------------ template void @@ -139,12 +43,7 @@ async_teardown(teardown_tag, boost::asio::ssl::stream& stream, TeardownHandler&& handler) { - static_assert(beast::is_CompletionHandler< - TeardownHandler, void(error_code)>::value, - "TeardownHandler requirements not met"); - detail::teardown_ssl_op::type>{std::forward( - handler), stream}; + stream.async_shutdown(std::forward(handler)); } } // websocket diff --git a/src/beast/include/beast/websocket/impl/stream.ipp b/src/beast/include/beast/websocket/impl/stream.ipp index fa932ffc7f..62bfe59522 100644 --- a/src/beast/include/beast/websocket/impl/stream.ipp +++ b/src/beast/include/beast/websocket/impl/stream.ipp @@ -8,27 +8,31 @@ #ifndef BEAST_WEBSOCKET_IMPL_STREAM_IPP #define BEAST_WEBSOCKET_IMPL_STREAM_IPP +#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 +#include + namespace beast { namespace websocket { @@ -47,20 +51,20 @@ set_option(permessage_deflate const& o) { if( o.server_max_window_bits > 15 || o.server_max_window_bits < 9) - throw std::invalid_argument{ - "invalid server_max_window_bits"}; + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "invalid server_max_window_bits"}); if( o.client_max_window_bits > 15 || o.client_max_window_bits < 9) - throw std::invalid_argument{ - "invalid client_max_window_bits"}; + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "invalid client_max_window_bits"}); if( o.compLevel < 0 || o.compLevel > 9) - throw std::invalid_argument{ - "invalid compLevel"}; + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "invalid compLevel"}); if( o.memLevel < 1 || o.memLevel > 9) - throw std::invalid_argument{ - "invalid memLevel"}; + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "invalid memLevel"}); pmd_opts_ = o; } @@ -83,20 +87,91 @@ reset() } template -http::request +template +void stream:: -build_request(boost::string_ref const& host, - boost::string_ref const& resource, std::string& key) +do_accept( + Decorator const& decorator, error_code& ec) { - http::request req; - req.url = { resource.data(), resource.size() }; + http::request_parser p; + http::read_header(next_layer(), + stream_.buffer(), p, ec); + if(ec) + return; + do_accept(p.get(), decorator, ec); +} + +template +template +void +stream:: +do_accept(http::header> const& req, + Decorator const& decorator, error_code& ec) +{ + auto const res = build_response(req, decorator); + http::write(stream_, res, ec); + if(ec) + return; + if(res.result() != http::status::switching_protocols) + { + ec = error::handshake_failed; + // VFALCO TODO Respect keep alive setting, perform + // teardown if Connection: close. + return; + } + pmd_read(pmd_config_, req); + open(role_type::server); +} + +template +template +void +stream:: +do_handshake(response_type* res_p, + string_view host, + string_view target, + RequestDecorator const& decorator, + error_code& ec) +{ + response_type res; + reset(); + detail::sec_ws_key_type key; + { + auto const req = build_request( + key, host, target, decorator); + pmd_read(pmd_config_, req); + http::write(stream_, req, ec); + } + if(ec) + return; + http::read(next_layer(), stream_.buffer(), res, ec); + if(ec) + return; + do_response(res, key, ec); + if(res_p) + *res_p = std::move(res); +} + +template +template +request_type +stream:: +build_request(detail::sec_ws_key_type& key, + string_view host, + string_view target, + Decorator const& decorator) +{ + request_type req; + req.target(target); req.version = 11; - req.method = "GET"; - req.fields.insert("Host", host); - req.fields.insert("Upgrade", "websocket"); - key = detail::make_sec_ws_key(maskgen_); - req.fields.insert("Sec-WebSocket-Key", key); - req.fields.insert("Sec-WebSocket-Version", "13"); + req.method(http::verb::get); + req.set(http::field::host, host); + req.set(http::field::upgrade, "websocket"); + req.set(http::field::connection, "upgrade"); + detail::make_sec_ws_key(key, maskgen_); + req.set(http::field::sec_websocket_key, key); + req.set(http::field::sec_websocket_version, "13"); if(pmd_opts_.client_enable) { detail::pmd_offer config; @@ -109,119 +184,502 @@ build_request(boost::string_ref const& host, pmd_opts_.server_no_context_takeover; config.client_no_context_takeover = pmd_opts_.client_no_context_takeover; - detail::pmd_write( - req.fields, config); + detail::pmd_write(req, config); } - d_(req); - http::prepare(req, http::connection::upgrade); + decorator(req); + if(! req.count(http::field::user_agent)) + req.set(http::field::user_agent, + BEAST_VERSION_STRING); return req; } template -template -http::response +template +response_type stream:: -build_response(http::request const& req) +build_response(http::header> const& req, + Decorator const& decorator) { + auto const decorate = + [&decorator](response_type& res) + { + decorator(res); + if(! res.count(http::field::server)) + { + BOOST_STATIC_ASSERT(sizeof(BEAST_VERSION_STRING) < 20); + static_string<20> s(BEAST_VERSION_STRING); + res.set(http::field::server, s); + } + }; auto err = [&](std::string const& text) { - http::response res; - res.status = 400; - res.reason = http::reason_string(res.status); + response_type res; res.version = req.version; + res.result(http::status::bad_request); res.body = text; - d_(res); - prepare(res, - (is_keep_alive(req) && keep_alive_) ? - http::connection::keep_alive : - http::connection::close); + res.prepare_payload(); + decorate(res); return res; }; if(req.version < 11) return err("HTTP version 1.1 required"); - if(req.method != "GET") + if(req.method() != http::verb::get) return err("Wrong method"); if(! is_upgrade(req)) return err("Expected Upgrade request"); - if(! req.fields.exists("Host")) + if(! req.count(http::field::host)) return err("Missing Host"); - if(! req.fields.exists("Sec-WebSocket-Key")) + if(! req.count(http::field::sec_websocket_key)) return err("Missing Sec-WebSocket-Key"); - if(! http::token_list{req.fields["Upgrade"]}.exists("websocket")) + if(! http::token_list{req[http::field::upgrade]}.exists("websocket")) return err("Missing websocket Upgrade token"); + auto const key = req[http::field::sec_websocket_key]; + if(key.size() > detail::sec_ws_key_type::max_size_n) + return err("Invalid Sec-WebSocket-Key"); { auto const version = - req.fields["Sec-WebSocket-Version"]; + req[http::field::sec_websocket_version]; if(version.empty()) return err("Missing Sec-WebSocket-Version"); if(version != "13") { - http::response res; - res.status = 426; - res.reason = http::reason_string(res.status); + response_type res; + res.result(http::status::upgrade_required); res.version = req.version; - res.fields.insert("Sec-WebSocket-Version", "13"); - d_(res); - prepare(res, - (is_keep_alive(req) && keep_alive_) ? - http::connection::keep_alive : - http::connection::close); + res.set(http::field::sec_websocket_version, "13"); + res.prepare_payload(); + decorate(res); return res; } } - http::response res; + + response_type res; { detail::pmd_offer offer; detail::pmd_offer unused; - pmd_read(offer, req.fields); - pmd_negotiate( - res.fields, unused, offer, pmd_opts_); + pmd_read(offer, req); + pmd_negotiate(res, unused, offer, pmd_opts_); } - res.status = 101; - res.reason = http::reason_string(res.status); + res.result(http::status::switching_protocols); res.version = req.version; - res.fields.insert("Upgrade", "websocket"); + res.set(http::field::upgrade, "websocket"); + res.set(http::field::connection, "upgrade"); { - auto const key = - req.fields["Sec-WebSocket-Key"]; - res.fields.insert("Sec-WebSocket-Accept", - detail::make_sec_ws_accept(key)); + detail::sec_ws_accept_type acc; + detail::make_sec_ws_accept(acc, key); + res.set(http::field::sec_websocket_accept, acc); } - res.fields.replace("Server", "Beast.WSProto"); - d_(res); - http::prepare(res, http::connection::upgrade); + decorate(res); return res; } template -template void stream:: -do_response(http::response const& res, - boost::string_ref const& key, error_code& ec) +do_response(http::header const& res, + detail::sec_ws_key_type const& key, error_code& ec) { - // VFALCO Review these error codes - auto fail = [&]{ ec = error::response_failed; }; - if(res.version < 11) - return fail(); - if(res.status != 101) - return fail(); - if(! is_upgrade(res)) - return fail(); - if(! http::token_list{res.fields["Upgrade"]}.exists("websocket")) - return fail(); - if(! res.fields.exists("Sec-WebSocket-Accept")) - return fail(); - if(res.fields["Sec-WebSocket-Accept"] != - detail::make_sec_ws_accept(key)) - return fail(); + bool const success = [&]() + { + if(res.version < 11) + return false; + if(res.result() != http::status::switching_protocols) + return false; + if(! http::token_list{res[http::field::connection]}.exists("upgrade")) + return false; + if(! http::token_list{res[http::field::upgrade]}.exists("websocket")) + return false; + if(res.count(http::field::sec_websocket_accept) != 1) + return false; + detail::sec_ws_accept_type acc; + detail::make_sec_ws_accept(acc, key); + if(acc.compare( + res[http::field::sec_websocket_accept]) != 0) + return false; + return true; + }(); + if(! success) + { + ec = error::handshake_failed; + return; + } + ec.assign(0, ec.category()); detail::pmd_offer offer; - pmd_read(offer, res.fields); + pmd_read(offer, res); // VFALCO see if offer satisfies pmd_config_, // return an error if not. pmd_config_ = offer; // overwrite for now - open(detail::role_type::client); + open(role_type::client); +} + +//------------------------------------------------------------------------------ + +template +void +stream:: +open(role_type role) +{ + // VFALCO TODO analyze and remove dupe code in reset() + role_ = role; + failed_ = false; + rd_.cont = false; + wr_close_ = false; + wr_block_ = nullptr; // should be nullptr on close anyway + ping_data_ = nullptr; // should be nullptr on close anyway + + wr_.cont = false; + wr_.buf_size = 0; + + if(((role_ == role_type::client && pmd_opts_.client_enable) || + (role_ == role_type::server && pmd_opts_.server_enable)) && + pmd_config_.accept) + { + pmd_normalize(pmd_config_); + pmd_.reset(new pmd_t); + if(role_ == role_type::client) + { + pmd_->zi.reset( + pmd_config_.server_max_window_bits); + pmd_->zo.reset( + pmd_opts_.compLevel, + pmd_config_.client_max_window_bits, + pmd_opts_.memLevel, + zlib::Strategy::normal); + } + else + { + pmd_->zi.reset( + pmd_config_.client_max_window_bits); + pmd_->zo.reset( + pmd_opts_.compLevel, + pmd_config_.server_max_window_bits, + pmd_opts_.memLevel, + zlib::Strategy::normal); + } + } +} + +template +void +stream:: +close() +{ + rd_.buf.reset(); + wr_.buf.reset(); + pmd_.reset(); +} + +// Read fixed frame header from buffer +// Requires at least 2 bytes +// +template +template +std::size_t +stream:: +read_fh1(detail::frame_header& fh, + DynamicBuffer& db, close_code& code) +{ + using boost::asio::buffer; + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + auto const err = + [&](close_code cv) + { + code = cv; + return 0; + }; + std::uint8_t b[2]; + BOOST_ASSERT(buffer_size(db.data()) >= sizeof(b)); + db.consume(buffer_copy(buffer(b), db.data())); + std::size_t need; + fh.len = b[1] & 0x7f; + switch(fh.len) + { + case 126: need = 2; break; + case 127: need = 8; break; + default: + need = 0; + } + fh.mask = (b[1] & 0x80) != 0; + if(fh.mask) + need += 4; + fh.op = static_cast< + detail::opcode>(b[0] & 0x0f); + fh.fin = (b[0] & 0x80) != 0; + fh.rsv1 = (b[0] & 0x40) != 0; + fh.rsv2 = (b[0] & 0x20) != 0; + fh.rsv3 = (b[0] & 0x10) != 0; + switch(fh.op) + { + case detail::opcode::binary: + case detail::opcode::text: + if(rd_.cont) + { + // new data frame when continuation expected + return err(close_code::protocol_error); + } + if((fh.rsv1 && ! pmd_) || + fh.rsv2 || fh.rsv3) + { + // reserved bits not cleared + return err(close_code::protocol_error); + } + if(pmd_) + pmd_->rd_set = fh.rsv1; + break; + + case detail::opcode::cont: + if(! rd_.cont) + { + // continuation without an active message + return err(close_code::protocol_error); + } + if(fh.rsv1 || fh.rsv2 || fh.rsv3) + { + // reserved bits not cleared + return err(close_code::protocol_error); + } + break; + + default: + if(is_reserved(fh.op)) + { + // reserved opcode + return err(close_code::protocol_error); + } + if(! fh.fin) + { + // fragmented control message + return err(close_code::protocol_error); + } + if(fh.len > 125) + { + // invalid length for control message + return err(close_code::protocol_error); + } + if(fh.rsv1 || fh.rsv2 || fh.rsv3) + { + // reserved bits not cleared + return err(close_code::protocol_error); + } + break; + } + // unmasked frame from client + if(role_ == role_type::server && ! fh.mask) + { + code = close_code::protocol_error; + return 0; + } + // masked frame from server + if(role_ == role_type::client && fh.mask) + { + code = close_code::protocol_error; + return 0; + } + code = close_code::none; + return need; +} + +// Decode variable frame header from buffer +// +template +template +void +stream:: +read_fh2(detail::frame_header& fh, + DynamicBuffer& db, close_code& code) +{ + using boost::asio::buffer; + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + using namespace boost::endian; + switch(fh.len) + { + case 126: + { + std::uint8_t b[2]; + BOOST_ASSERT(buffer_size(db.data()) >= sizeof(b)); + db.consume(buffer_copy(buffer(b), db.data())); + fh.len = detail::big_uint16_to_native(&b[0]); + // length not canonical + if(fh.len < 126) + { + code = close_code::protocol_error; + return; + } + break; + } + case 127: + { + std::uint8_t b[8]; + BOOST_ASSERT(buffer_size(db.data()) >= sizeof(b)); + db.consume(buffer_copy(buffer(b), db.data())); + fh.len = detail::big_uint64_to_native(&b[0]); + // length not canonical + if(fh.len < 65536) + { + code = close_code::protocol_error; + return; + } + break; + } + } + if(fh.mask) + { + std::uint8_t b[4]; + BOOST_ASSERT(buffer_size(db.data()) >= sizeof(b)); + db.consume(buffer_copy(buffer(b), db.data())); + fh.key = detail::little_uint32_to_native(&b[0]); + } + else + { + // initialize this otherwise operator== breaks + fh.key = 0; + } + if(! is_control(fh.op)) + { + if(fh.op != detail::opcode::cont) + { + rd_.size = 0; + rd_.op = fh.op; + } + else + { + if(rd_.size > (std::numeric_limits< + std::uint64_t>::max)() - fh.len) + { + code = close_code::too_big; + return; + } + } + rd_.cont = ! fh.fin; + } + code = close_code::none; +} + +template +void +stream:: +rd_begin() +{ + // Maintain the read buffer + if(pmd_) + { + if(! rd_.buf || rd_.buf_size != rd_buf_size_) + { + rd_.buf_size = rd_buf_size_; + rd_.buf = boost::make_unique_noinit< + std::uint8_t[]>(rd_.buf_size); + } + } +} + +template +void +stream:: +wr_begin() +{ + wr_.autofrag = wr_autofrag_; + wr_.compress = static_cast(pmd_); + + // Maintain the write buffer + if( wr_.compress || + role_ == role_type::client) + { + if(! wr_.buf || wr_.buf_size != wr_buf_size_) + { + wr_.buf_size = wr_buf_size_; + wr_.buf = boost::make_unique_noinit< + std::uint8_t[]>(wr_.buf_size); + } + } + else + { + wr_.buf_size = wr_buf_size_; + wr_.buf.reset(); + } +} + +template +template +void +stream:: +write_close(DynamicBuffer& db, close_reason const& cr) +{ + using namespace boost::endian; + detail::frame_header fh; + fh.op = detail::opcode::close; + fh.fin = true; + fh.rsv1 = false; + fh.rsv2 = false; + fh.rsv3 = false; + fh.len = cr.code == close_code::none ? + 0 : 2 + cr.reason.size(); + fh.mask = role_ == role_type::client; + if(fh.mask) + fh.key = maskgen_(); + detail::write(db, fh); + if(cr.code != close_code::none) + { + detail::prepared_key key; + if(fh.mask) + detail::prepare_key(key, fh.key); + { + std::uint8_t b[2]; + ::new(&b[0]) big_uint16_buf_t{ + (std::uint16_t)cr.code}; + auto d = db.prepare(2); + boost::asio::buffer_copy(d, + boost::asio::buffer(b)); + if(fh.mask) + detail::mask_inplace(d, key); + db.commit(2); + } + if(! cr.reason.empty()) + { + auto d = db.prepare(cr.reason.size()); + boost::asio::buffer_copy(d, + boost::asio::const_buffer( + cr.reason.data(), cr.reason.size())); + if(fh.mask) + detail::mask_inplace(d, key); + db.commit(cr.reason.size()); + } + } +} + +template +template +void +stream:: +write_ping(DynamicBuffer& db, + detail::opcode code, ping_data const& data) +{ + detail::frame_header fh; + fh.op = code; + fh.fin = true; + fh.rsv1 = false; + fh.rsv2 = false; + fh.rsv3 = false; + fh.len = data.size(); + fh.mask = role_ == role_type::client; + if(fh.mask) + fh.key = maskgen_(); + detail::write(db, fh); + if(data.empty()) + return; + detail::prepared_key key; + if(fh.mask) + detail::prepare_key(key, fh.key); + auto d = db.prepare(data.size()); + boost::asio::buffer_copy(d, + boost::asio::const_buffers_1( + data.data(), data.size())); + if(fh.mask) + detail::mask_inplace(d, key); + db.commit(data.size()); } } // websocket diff --git a/src/beast/include/beast/websocket/impl/teardown.ipp b/src/beast/include/beast/websocket/impl/teardown.ipp index cc8a46e368..f29a517b01 100644 --- a/src/beast/include/beast/websocket/impl/teardown.ipp +++ b/src/beast/include/beast/websocket/impl/teardown.ipp @@ -8,10 +8,12 @@ #ifndef BEAST_WEBSOCKET_IMPL_TEARDOWN_IPP #define BEAST_WEBSOCKET_IMPL_TEARDOWN_IPP -#include -#include -#include +#include #include +#include +#include +#include +#include #include namespace beast { @@ -33,10 +35,10 @@ class teardown_tcp_op int state = 0; data(Handler& handler, socket_type& socket_) - : cont(beast_asio_helpers:: - is_continuation(handler)) - , socket(socket_) + : socket(socket_) { + using boost::asio::asio_handler_is_continuation; + cont = asio_handler_is_continuation(std::addressof(handler)); } }; @@ -59,16 +61,18 @@ public: void* asio_handler_allocate(std::size_t size, teardown_tcp_op* op) { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); + using boost::asio::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(op->d_.handler())); } friend void asio_handler_deallocate(void* p, std::size_t size, teardown_tcp_op* op) { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->d_.handler())); } friend @@ -82,8 +86,9 @@ public: void asio_handler_invoke(Function&& f, teardown_tcp_op* op) { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->d_.handler())); } }; @@ -152,7 +157,7 @@ async_teardown(teardown_tag, boost::asio::ip::tcp::socket& socket, TeardownHandler&& handler) { - static_assert(beast::is_CompletionHandler< + static_assert(beast::is_completion_handler< TeardownHandler, void(error_code)>::value, "TeardownHandler requirements not met"); detail::teardown_tcp_op #include -#include +#include #include -#include #include -#include -#include -#include +#include +#include #include +#include #include +#include +#include +#include #include +#include +#include #include #include @@ -32,7 +36,6 @@ class stream::write_frame_op { struct data : op { - Handler& handler; bool cont; stream& ws; consuming_buffers cb; @@ -41,18 +44,17 @@ class stream::write_frame_op detail::fh_streambuf fh_buf; detail::prepared_key key; std::uint64_t remain; - int state = 0; + int step = 0; int entry_state; - data(Handler& handler_, stream& ws_, + data(Handler& handler, stream& ws_, bool fin_, Buffers const& bs) - : handler(handler_) - , cont(beast_asio_helpers:: - is_continuation(handler)) - , ws(ws_) + : ws(ws_) , cb(bs) , fin(fin_) { + using boost::asio::asio_handler_is_continuation; + cont = asio_handler_is_continuation(std::addressof(handler)); } }; @@ -68,39 +70,33 @@ public: : d_(std::forward(h), ws, std::forward(args)...) { - (*this)(error_code{}, 0, false); } void operator()() { - (*this)(error_code{}, 0, true); - } - - void operator()(error_code const& ec) - { - (*this)(ec, 0, true); + (*this)({}, 0, true); } void operator()(error_code ec, - std::size_t bytes_transferred); - - void operator()(error_code ec, - std::size_t bytes_transferred, bool again); + std::size_t bytes_transferred, + bool again = true); friend void* asio_handler_allocate( std::size_t size, write_frame_op* op) { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); + using boost::asio::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(op->d_.handler())); } friend void asio_handler_deallocate( void* p, std::size_t size, write_frame_op* op) { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->d_.handler())); } friend @@ -113,24 +109,12 @@ public: friend void asio_handler_invoke(Function&& f, write_frame_op* op) { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->d_.handler())); } }; -template -template -void -stream:: -write_frame_op:: -operator()(error_code ec, std::size_t bytes_transferred) -{ - auto& d = *d_; - if(ec) - d.ws.failed_ = true; - (*this)(ec, bytes_transferred, true); -} - template template void @@ -157,429 +141,496 @@ operator()(error_code ec, auto& d = *d_; d.cont = d.cont || again; if(ec) - goto upcall; - for(;;) { - switch(d.state) + BOOST_ASSERT(d.ws.wr_block_ == &d); + d.ws.failed_ = true; + goto upcall; + } +loop: + switch(d.step) + { + case do_init: + if(! d.ws.wr_.cont) { - case do_init: - if(! d.ws.wr_.cont) - { - d.ws.wr_begin(); - d.fh.rsv1 = d.ws.wr_.compress; - } - else - { - d.fh.rsv1 = false; - } - d.fh.rsv2 = false; - d.fh.rsv3 = false; - d.fh.op = d.ws.wr_.cont ? - opcode::cont : d.ws.wr_opcode_; - d.fh.mask = - d.ws.role_ == detail::role_type::client; - - // entry_state determines which algorithm - // we will use to send. If we suspend, we - // will transition to entry_state + 1 on - // the resume. - if(d.ws.wr_.compress) - { - d.entry_state = do_deflate; - } - else if(! d.fh.mask) - { - if(! d.ws.wr_.autofrag) - { - d.entry_state = do_nomask_nofrag; - } - else - { - BOOST_ASSERT(d.ws.wr_.buf_size != 0); - d.remain = buffer_size(d.cb); - if(d.remain > d.ws.wr_.buf_size) - d.entry_state = do_nomask_frag; - else - d.entry_state = do_nomask_nofrag; - } - } - else - { - if(! d.ws.wr_.autofrag) - { - d.entry_state = do_mask_nofrag; - } - else - { - BOOST_ASSERT(d.ws.wr_.buf_size != 0); - d.remain = buffer_size(d.cb); - if(d.remain > d.ws.wr_.buf_size) - d.entry_state = do_mask_frag; - else - d.entry_state = do_mask_nofrag; - } - } - d.state = do_maybe_suspend; - break; - - //---------------------------------------------------------------------- - - case do_nomask_nofrag: - BOOST_ASSERT(! d.ws.wr_block_); - d.ws.wr_block_ = &d; - // [[fallthrough]] - - case do_nomask_nofrag + 1: - { - BOOST_ASSERT(d.ws.wr_block_ == &d); - d.fh.fin = d.fin; - d.fh.len = buffer_size(d.cb); - detail::write( - d.fh_buf, d.fh); - d.ws.wr_.cont = ! d.fin; - // Send frame - d.state = do_upcall; - boost::asio::async_write(d.ws.stream_, - buffer_cat(d.fh_buf.data(), d.cb), - std::move(*this)); - return; + d.ws.wr_begin(); + d.fh.rsv1 = d.ws.wr_.compress; } - - //---------------------------------------------------------------------- - - case do_nomask_frag: - BOOST_ASSERT(! d.ws.wr_block_); - d.ws.wr_block_ = &d; - // [[fallthrough]] - - case do_nomask_frag + 1: + else { - BOOST_ASSERT(d.ws.wr_block_ == &d); - auto const n = clamp( - d.remain, d.ws.wr_.buf_size); - d.remain -= n; - d.fh.len = n; - d.fh.fin = d.fin ? d.remain == 0 : false; - detail::write( - d.fh_buf, d.fh); - d.ws.wr_.cont = ! d.fin; - // Send frame - d.state = d.remain == 0 ? - do_upcall : do_nomask_frag + 2; - boost::asio::async_write(d.ws.stream_, - buffer_cat(d.fh_buf.data(), - prepare_buffers(n, d.cb)), - std::move(*this)); - return; - } - - case do_nomask_frag + 2: - d.cb.consume( - bytes_transferred - d.fh_buf.size()); - d.fh_buf.reset(); - d.fh.op = opcode::cont; - if(d.ws.wr_block_ == &d) - d.ws.wr_block_ = nullptr; - // Allow outgoing control frames to - // be sent in between message frames: - if(d.ws.rd_op_.maybe_invoke() || - d.ws.ping_op_.maybe_invoke()) - { - d.state = do_maybe_suspend; - d.ws.get_io_service().post( - std::move(*this)); - return; - } - d.state = d.entry_state; - break; - - //---------------------------------------------------------------------- - - case do_mask_nofrag: - BOOST_ASSERT(! d.ws.wr_block_); - d.ws.wr_block_ = &d; - // [[fallthrough]] - - case do_mask_nofrag + 1: - { - BOOST_ASSERT(d.ws.wr_block_ == &d); - d.remain = buffer_size(d.cb); - d.fh.fin = d.fin; - d.fh.len = d.remain; - d.fh.key = d.ws.maskgen_(); - detail::prepare_key(d.key, d.fh.key); - detail::write( - d.fh_buf, d.fh); - auto const n = - clamp(d.remain, d.ws.wr_.buf_size); - auto const b = - buffer(d.ws.wr_.buf.get(), n); - buffer_copy(b, d.cb); - detail::mask_inplace(b, d.key); - d.remain -= n; - d.ws.wr_.cont = ! d.fin; - // Send frame header and partial payload - d.state = d.remain == 0 ? - do_upcall : do_mask_nofrag + 2; - boost::asio::async_write(d.ws.stream_, - buffer_cat(d.fh_buf.data(), b), - std::move(*this)); - return; - } - - case do_mask_nofrag + 2: - { - d.cb.consume(d.ws.wr_.buf_size); - auto const n = - clamp(d.remain, d.ws.wr_.buf_size); - auto const b = - buffer(d.ws.wr_.buf.get(), n); - buffer_copy(b, d.cb); - detail::mask_inplace(b, d.key); - d.remain -= n; - // Send parial payload - if(d.remain == 0) - d.state = do_upcall; - boost::asio::async_write( - d.ws.stream_, b, std::move(*this)); - return; - } - - //---------------------------------------------------------------------- - - case do_mask_frag: - BOOST_ASSERT(! d.ws.wr_block_); - d.ws.wr_block_ = &d; - // [[fallthrough]] - - case do_mask_frag + 1: - { - BOOST_ASSERT(d.ws.wr_block_ == &d); - auto const n = clamp( - d.remain, d.ws.wr_.buf_size); - d.remain -= n; - d.fh.len = n; - d.fh.key = d.ws.maskgen_(); - d.fh.fin = d.fin ? d.remain == 0 : false; - detail::prepare_key(d.key, d.fh.key); - auto const b = buffer( - d.ws.wr_.buf.get(), n); - buffer_copy(b, d.cb); - detail::mask_inplace(b, d.key); - detail::write( - d.fh_buf, d.fh); - d.ws.wr_.cont = ! d.fin; - // Send frame - d.state = d.remain == 0 ? - do_upcall : do_mask_frag + 2; - boost::asio::async_write(d.ws.stream_, - buffer_cat(d.fh_buf.data(), b), - std::move(*this)); - return; - } - - case do_mask_frag + 2: - d.cb.consume( - bytes_transferred - d.fh_buf.size()); - d.fh_buf.reset(); - d.fh.op = opcode::cont; - BOOST_ASSERT(d.ws.wr_block_ == &d); - d.ws.wr_block_ = nullptr; - // Allow outgoing control frames to - // be sent in between message frames: - if(d.ws.rd_op_.maybe_invoke() || - d.ws.ping_op_.maybe_invoke()) - { - d.state = do_maybe_suspend; - d.ws.get_io_service().post( - std::move(*this)); - return; - } - d.state = d.entry_state; - break; - - //---------------------------------------------------------------------- - - case do_deflate: - BOOST_ASSERT(! d.ws.wr_block_); - d.ws.wr_block_ = &d; - // [[fallthrough]] - - case do_deflate + 1: - { - BOOST_ASSERT(d.ws.wr_block_ == &d); - auto b = buffer(d.ws.wr_.buf.get(), - d.ws.wr_.buf_size); - auto const more = detail::deflate( - d.ws.pmd_->zo, b, d.cb, d.fin, ec); - d.ws.failed_ = ec != 0; - if(d.ws.failed_) - goto upcall; - auto const n = buffer_size(b); - if(n == 0) - { - // The input was consumed, but there - // is no output due to compression - // latency. - BOOST_ASSERT(! d.fin); - BOOST_ASSERT(buffer_size(d.cb) == 0); - - // We can skip the dispatch if the - // asynchronous initiation function is - // not on call stack but its hard to - // figure out so be safe and dispatch. - d.state = do_upcall; - d.ws.get_io_service().post(std::move(*this)); - return; - } - if(d.fh.mask) - { - d.fh.key = d.ws.maskgen_(); - detail::prepared_key key; - detail::prepare_key(key, d.fh.key); - detail::mask_inplace(b, key); - } - d.fh.fin = ! more; - d.fh.len = n; - detail::fh_streambuf fh_buf; - detail::write(fh_buf, d.fh); - d.ws.wr_.cont = ! d.fin; - // Send frame - d.state = more ? - do_deflate + 2 : do_deflate + 3; - boost::asio::async_write(d.ws.stream_, - buffer_cat(fh_buf.data(), b), - std::move(*this)); - return; - } - - case do_deflate + 2: - d.fh.op = opcode::cont; d.fh.rsv1 = false; - BOOST_ASSERT(d.ws.wr_block_ == &d); - d.ws.wr_block_ = nullptr; - // Allow outgoing control frames to - // be sent in between message frames: - if(d.ws.rd_op_.maybe_invoke() || - d.ws.ping_op_.maybe_invoke()) - { - d.state = do_maybe_suspend; - d.ws.get_io_service().post( - std::move(*this)); - return; - } - d.state = d.entry_state; - break; + } + d.fh.rsv2 = false; + d.fh.rsv3 = false; + d.fh.op = d.ws.wr_.cont ? + detail::opcode::cont : d.ws.wr_opcode_; + d.fh.mask = + d.ws.role_ == role_type::client; - case do_deflate + 3: - if(d.fh.fin && ( - (d.ws.role_ == detail::role_type::client && - d.ws.pmd_config_.client_no_context_takeover) || - (d.ws.role_ == detail::role_type::server && - d.ws.pmd_config_.server_no_context_takeover))) - d.ws.pmd_->zo.reset(); - goto upcall; - - //---------------------------------------------------------------------- - - case do_maybe_suspend: + // entry_state determines which algorithm + // we will use to send. If we suspend, we + // will transition to entry_state + 1 on + // the resume. + if(d.ws.wr_.compress) { - if(d.ws.wr_block_) - { - // suspend - d.state = do_maybe_suspend + 1; - d.ws.wr_op_.template emplace< - write_frame_op>(std::move(*this)); - return; - } - if(d.ws.failed_ || d.ws.wr_close_) - { - // call handler - d.state = do_upcall; - d.ws.get_io_service().post( - bind_handler(std::move(*this), - boost::asio::error::operation_aborted)); - return; - } - d.state = d.entry_state; - break; + d.entry_state = do_deflate; } - - case do_maybe_suspend + 1: - BOOST_ASSERT(! d.ws.wr_block_); - d.ws.wr_block_ = &d; - d.state = do_maybe_suspend + 2; - // The current context is safe but might not be - // the same as the one for this operation (since - // we are being called from a write operation). - // Call post to make sure we are invoked the same - // way as the final handler for this operation. - d.ws.get_io_service().post(bind_handler( - std::move(*this), ec)); - return; - - case do_maybe_suspend + 2: - BOOST_ASSERT(d.ws.wr_block_ == &d); - if(d.ws.failed_ || d.ws.wr_close_) + else if(! d.fh.mask) + { + if(! d.ws.wr_.autofrag) { - // call handler - ec = boost::asio::error::operation_aborted; - goto upcall; + d.entry_state = do_nomask_nofrag; } - d.state = d.entry_state + 1; - break; + else + { + BOOST_ASSERT(d.ws.wr_.buf_size != 0); + d.remain = buffer_size(d.cb); + if(d.remain > d.ws.wr_.buf_size) + d.entry_state = do_nomask_frag; + else + d.entry_state = do_nomask_nofrag; + } + } + else + { + if(! d.ws.wr_.autofrag) + { + d.entry_state = do_mask_nofrag; + } + else + { + BOOST_ASSERT(d.ws.wr_.buf_size != 0); + d.remain = buffer_size(d.cb); + if(d.remain > d.ws.wr_.buf_size) + d.entry_state = do_mask_frag; + else + d.entry_state = do_mask_nofrag; + } + } + d.step = do_maybe_suspend; + goto loop; - //---------------------------------------------------------------------- + //---------------------------------------------------------------------- - case do_upcall: + case do_nomask_nofrag: + BOOST_ASSERT(d.ws.wr_block_ == &d); + d.fh.fin = d.fin; + d.fh.len = buffer_size(d.cb); + detail::write( + d.fh_buf, d.fh); + d.ws.wr_.cont = ! d.fin; + // Send frame + d.step = do_upcall; + return boost::asio::async_write(d.ws.stream_, + buffer_cat(d.fh_buf.data(), d.cb), + std::move(*this)); + + //---------------------------------------------------------------------- + + go_nomask_frag: + case do_nomask_frag: + { + BOOST_ASSERT(d.ws.wr_block_ == &d); + auto const n = clamp( + d.remain, d.ws.wr_.buf_size); + d.remain -= n; + d.fh.len = n; + d.fh.fin = d.fin ? d.remain == 0 : false; + detail::write( + d.fh_buf, d.fh); + d.ws.wr_.cont = ! d.fin; + // Send frame + d.step = d.remain == 0 ? + do_upcall : do_nomask_frag + 1; + return boost::asio::async_write( + d.ws.stream_, buffer_cat( + d.fh_buf.data(), buffer_prefix( + n, d.cb)), std::move(*this)); + } + + case do_nomask_frag + 1: + BOOST_ASSERT(d.ws.wr_block_ == &d); + d.ws.wr_block_ = nullptr; + d.cb.consume( + bytes_transferred - d.fh_buf.size()); + d.fh_buf.consume(d.fh_buf.size()); + d.fh.op = detail::opcode::cont; + // Allow outgoing control frames to + // be sent in between message frames + if( d.ws.close_op_.maybe_invoke() || + d.ws.rd_op_.maybe_invoke() || + d.ws.ping_op_.maybe_invoke()) + { + d.step = do_maybe_suspend; + return d.ws.get_io_service().post( + std::move(*this)); + } + d.ws.wr_block_ = &d; + goto go_nomask_frag; + + //---------------------------------------------------------------------- + + case do_mask_nofrag: + { + BOOST_ASSERT(d.ws.wr_block_ == &d); + d.remain = buffer_size(d.cb); + d.fh.fin = d.fin; + d.fh.len = d.remain; + d.fh.key = d.ws.maskgen_(); + detail::prepare_key(d.key, d.fh.key); + detail::write( + d.fh_buf, d.fh); + auto const n = + clamp(d.remain, d.ws.wr_.buf_size); + auto const b = + buffer(d.ws.wr_.buf.get(), n); + buffer_copy(b, d.cb); + detail::mask_inplace(b, d.key); + d.remain -= n; + d.ws.wr_.cont = ! d.fin; + // Send frame header and partial payload + d.step = d.remain == 0 ? + do_upcall : do_mask_nofrag + 1; + return boost::asio::async_write( + d.ws.stream_, buffer_cat(d.fh_buf.data(), + b), std::move(*this)); + } + + case do_mask_nofrag + 1: + { + d.cb.consume(d.ws.wr_.buf_size); + auto const n = + clamp(d.remain, d.ws.wr_.buf_size); + auto const b = + buffer(d.ws.wr_.buf.get(), n); + buffer_copy(b, d.cb); + detail::mask_inplace(b, d.key); + d.remain -= n; + // Send partial payload + if(d.remain == 0) + d.step = do_upcall; + return boost::asio::async_write( + d.ws.stream_, b, std::move(*this)); + } + + //---------------------------------------------------------------------- + + go_mask_frag: + case do_mask_frag: + { + BOOST_ASSERT(d.ws.wr_block_ == &d); + auto const n = clamp( + d.remain, d.ws.wr_.buf_size); + d.remain -= n; + d.fh.len = n; + d.fh.key = d.ws.maskgen_(); + d.fh.fin = d.fin ? d.remain == 0 : false; + detail::prepare_key(d.key, d.fh.key); + auto const b = buffer( + d.ws.wr_.buf.get(), n); + buffer_copy(b, d.cb); + detail::mask_inplace(b, d.key); + detail::write( + d.fh_buf, d.fh); + d.ws.wr_.cont = ! d.fin; + // Send frame + d.step = d.remain == 0 ? + do_upcall : do_mask_frag + 1; + return boost::asio::async_write( + d.ws.stream_, buffer_cat( + d.fh_buf.data(), b), + std::move(*this)); + } + + case do_mask_frag + 1: + BOOST_ASSERT(d.ws.wr_block_ == &d); + d.ws.wr_block_ = nullptr; + d.cb.consume( + bytes_transferred - d.fh_buf.size()); + d.fh_buf.consume(d.fh_buf.size()); + d.fh.op = detail::opcode::cont; + // Allow outgoing control frames to + // be sent in between message frames: + if( d.ws.close_op_.maybe_invoke() || + d.ws.rd_op_.maybe_invoke() || + d.ws.ping_op_.maybe_invoke()) + { + d.step = do_maybe_suspend; + d.ws.get_io_service().post( + std::move(*this)); + return; + } + d.ws.wr_block_ = &d; + goto go_mask_frag; + + //---------------------------------------------------------------------- + + go_deflate: + case do_deflate: + { + BOOST_ASSERT(d.ws.wr_block_ == &d); + auto b = buffer(d.ws.wr_.buf.get(), + d.ws.wr_.buf_size); + auto const more = detail::deflate( + d.ws.pmd_->zo, b, d.cb, d.fin, ec); + d.ws.failed_ = !!ec; + if(d.ws.failed_) + goto upcall; + auto const n = buffer_size(b); + if(n == 0) + { + // The input was consumed, but there + // is no output due to compression + // latency. + BOOST_ASSERT(! d.fin); + BOOST_ASSERT(buffer_size(d.cb) == 0); + + // We can skip the dispatch if the + // asynchronous initiation function is + // not on call stack but its hard to + // figure out so be safe and dispatch. + d.step = do_upcall; + d.ws.get_io_service().post(std::move(*this)); + return; + } + if(d.fh.mask) + { + d.fh.key = d.ws.maskgen_(); + detail::prepared_key key; + detail::prepare_key(key, d.fh.key); + detail::mask_inplace(b, key); + } + d.fh.fin = ! more; + d.fh.len = n; + detail::fh_streambuf fh_buf; + detail::write(fh_buf, d.fh); + d.ws.wr_.cont = ! d.fin; + // Send frame + d.step = more ? + do_deflate + 1 : do_deflate + 2; + boost::asio::async_write(d.ws.stream_, + buffer_cat(fh_buf.data(), b), + std::move(*this)); + return; + } + + case do_deflate + 1: + BOOST_ASSERT(d.ws.wr_block_ == &d); + d.ws.wr_block_ = nullptr; + d.fh.op = detail::opcode::cont; + d.fh.rsv1 = false; + // Allow outgoing control frames to + // be sent in between message frames: + if( d.ws.close_op_.maybe_invoke() || + d.ws.rd_op_.maybe_invoke() || + d.ws.ping_op_.maybe_invoke()) + { + d.step = do_maybe_suspend; + d.ws.get_io_service().post( + std::move(*this)); + return; + } + d.ws.wr_block_ = &d; + goto go_deflate; + + case do_deflate + 2: + BOOST_ASSERT(d.ws.wr_block_ == &d); + if(d.fh.fin && ( + (d.ws.role_ == role_type::client && + d.ws.pmd_config_.client_no_context_takeover) || + (d.ws.role_ == role_type::server && + d.ws.pmd_config_.server_no_context_takeover))) + d.ws.pmd_->zo.reset(); + goto upcall; + + //---------------------------------------------------------------------- + + case do_maybe_suspend: + if(d.ws.wr_block_) + { + // suspend + BOOST_ASSERT(d.ws.wr_block_ != &d); + d.step = do_maybe_suspend + 1; + d.ws.wr_op_.emplace(std::move(*this)); + return; + } + d.ws.wr_block_ = &d; + if(d.ws.failed_ || d.ws.wr_close_) + { + // call handler + return d.ws.get_io_service().post( + bind_handler(std::move(*this), + boost::asio::error::operation_aborted, 0)); + } + d.step = d.entry_state; + goto loop; + + case do_maybe_suspend + 1: + BOOST_ASSERT(! d.ws.wr_block_); + d.ws.wr_block_ = &d; + d.step = do_maybe_suspend + 2; + // The current context is safe but might not be + // the same as the one for this operation (since + // we are being called from a write operation). + // Call post to make sure we are invoked the same + // way as the final handler for this operation. + d.ws.get_io_service().post(bind_handler( + std::move(*this), ec, 0)); + return; + + case do_maybe_suspend + 2: + BOOST_ASSERT(d.ws.wr_block_ == &d); + if(d.ws.failed_ || d.ws.wr_close_) + { + // call handler + ec = boost::asio::error::operation_aborted; goto upcall; } + d.step = d.entry_state; + goto loop; + + //---------------------------------------------------------------------- + + case do_upcall: + goto upcall; } upcall: if(d.ws.wr_block_ == &d) d.ws.wr_block_ = nullptr; - d.ws.rd_op_.maybe_invoke() || + d.ws.close_op_.maybe_invoke() || + d.ws.rd_op_.maybe_invoke() || d.ws.ping_op_.maybe_invoke(); d_.invoke(ec); } +//------------------------------------------------------------------------------ + template -template -typename async_completion< - WriteHandler, void(error_code)>::result_type -stream:: -async_write_frame(bool fin, - ConstBufferSequence const& bs, WriteHandler&& handler) +template +class stream::write_op { - static_assert(is_AsyncStream::value, - "AsyncStream requirements not met"); - static_assert(beast::is_ConstBufferSequence< - ConstBufferSequence>::value, - "ConstBufferSequence requirements not met"); - beast::async_completion< - WriteHandler, void(error_code) - > completion{handler}; - write_frame_op{completion.handler, - *this, fin, bs}; - return completion.result.get(); + struct data : op + { + int step = 0; + stream& ws; + consuming_buffers cb; + std::size_t remain; + + data(Handler&, stream& ws_, + Buffers const& bs) + : ws(ws_) + , cb(bs) + , remain(boost::asio::buffer_size(cb)) + { + } + }; + + handler_ptr d_; + +public: + write_op(write_op&&) = default; + write_op(write_op const&) = default; + + template + explicit + write_op(DeducedHandler&& h, + stream& ws, Args&&... args) + : d_(std::forward(h), + ws, std::forward(args)...) + { + } + + void operator()(error_code ec); + + friend + void* asio_handler_allocate( + std::size_t size, write_op* op) + { + using boost::asio::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(op->d_.handler())); + } + + friend + void asio_handler_deallocate( + void* p, std::size_t size, write_op* op) + { + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->d_.handler())); + } + + friend + bool asio_handler_is_continuation(write_op* op) + { + using boost::asio::asio_handler_is_continuation; + return op->d_->step > 2 || + asio_handler_is_continuation( + std::addressof(op->d_.handler())); + } + + template + friend + void asio_handler_invoke(Function&& f, write_op* op) + { + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->d_.handler())); + } +}; + +template +template +void +stream:: +write_op:: +operator()(error_code ec) +{ + auto& d = *d_; + switch(d.step) + { + case 2: + d.step = 3; + BEAST_FALLTHROUGH; + case 3: + case 0: + { + auto const n = d.remain; + d.remain -= n; + auto const fin = d.remain <= 0; + if(fin) + d.step = d.step ? 4 : 1; + else + d.step = d.step ? 3 : 2; + auto const pb = buffer_prefix(n, d.cb); + d.cb.consume(n); + return d.ws.async_write_frame( + fin, pb, std::move(*this)); + } + + case 1: + case 4: + break; + } + d_.invoke(ec); } +//------------------------------------------------------------------------------ + template template void stream:: write_frame(bool fin, ConstBufferSequence const& buffers) { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, "SyncStream requirements not met"); - static_assert(beast::is_ConstBufferSequence< + static_assert(beast::is_const_buffer_sequence< ConstBufferSequence>::value, "ConstBufferSequence requirements not met"); error_code ec; write_frame(fin, buffers, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); } template @@ -589,9 +640,9 @@ stream:: write_frame(bool fin, ConstBufferSequence const& buffers, error_code& ec) { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, "SyncStream requirements not met"); - static_assert(beast::is_ConstBufferSequence< + static_assert(beast::is_const_buffer_sequence< ConstBufferSequence>::value, "ConstBufferSequence requirements not met"); using beast::detail::clamp; @@ -610,8 +661,9 @@ write_frame(bool fin, } fh.rsv2 = false; fh.rsv3 = false; - fh.op = wr_.cont ? opcode::cont : wr_opcode_; - fh.mask = role_ == detail::role_type::client; + fh.op = wr_.cont ? + detail::opcode::cont : wr_opcode_; + fh.mask = role_ == role_type::client; auto remain = buffer_size(buffers); if(wr_.compress) { @@ -623,7 +675,7 @@ write_frame(bool fin, wr_.buf.get(), wr_.buf_size); auto const more = detail::deflate( pmd_->zo, b, cb, fin, ec); - failed_ = ec != 0; + failed_ = !!ec; if(failed_) return; auto const n = buffer_size(b); @@ -647,22 +699,22 @@ write_frame(bool fin, fh.fin = ! more; fh.len = n; detail::fh_streambuf fh_buf; - detail::write(fh_buf, fh); + detail::write(fh_buf, fh); wr_.cont = ! fin; boost::asio::write(stream_, buffer_cat(fh_buf.data(), b), ec); - failed_ = ec != 0; + failed_ = !!ec; if(failed_) return; if(! more) break; - fh.op = opcode::cont; + fh.op = detail::opcode::cont; fh.rsv1 = false; } if(fh.fin && ( - (role_ == detail::role_type::client && + (role_ == role_type::client && pmd_config_.client_no_context_takeover) || - (role_ == detail::role_type::server && + (role_ == role_type::server && pmd_config_.server_no_context_takeover))) pmd_->zo.reset(); return; @@ -675,11 +727,11 @@ write_frame(bool fin, fh.fin = fin; fh.len = remain; detail::fh_streambuf fh_buf; - detail::write(fh_buf, fh); + detail::write(fh_buf, fh); wr_.cont = ! fin; boost::asio::write(stream_, buffer_cat(fh_buf.data(), buffers), ec); - failed_ = ec != 0; + failed_ = !!ec; if(failed_) return; } @@ -696,17 +748,17 @@ write_frame(bool fin, fh.len = n; fh.fin = fin ? remain == 0 : false; detail::fh_streambuf fh_buf; - detail::write(fh_buf, fh); + detail::write(fh_buf, fh); wr_.cont = ! fin; boost::asio::write(stream_, buffer_cat(fh_buf.data(), - prepare_buffers(n, cb)), ec); - failed_ = ec != 0; + buffer_prefix(n, cb)), ec); + failed_ = !!ec; if(failed_) return; if(remain == 0) break; - fh.op = opcode::cont; + fh.op = detail::opcode::cont; cb.consume(n); } } @@ -721,7 +773,7 @@ write_frame(bool fin, detail::prepared_key key; detail::prepare_key(key, fh.key); detail::fh_streambuf fh_buf; - detail::write(fh_buf, fh); + detail::write(fh_buf, fh); consuming_buffers< ConstBufferSequence> cb{buffers}; { @@ -734,7 +786,7 @@ write_frame(bool fin, wr_.cont = ! fin; boost::asio::write(stream_, buffer_cat(fh_buf.data(), b), ec); - failed_ = ec != 0; + failed_ = !!ec; if(failed_) return; } @@ -747,7 +799,7 @@ write_frame(bool fin, remain -= n; detail::mask_inplace(b, key); boost::asio::write(stream_, b, ec); - failed_ = ec != 0; + failed_ = !!ec; if(failed_) return; } @@ -772,162 +824,59 @@ write_frame(bool fin, fh.fin = fin ? remain == 0 : false; wr_.cont = ! fh.fin; detail::fh_streambuf fh_buf; - detail::write(fh_buf, fh); + detail::write(fh_buf, fh); boost::asio::write(stream_, buffer_cat(fh_buf.data(), b), ec); - failed_ = ec != 0; + failed_ = !!ec; if(failed_) return; if(remain == 0) break; - fh.op = opcode::cont; + fh.op = detail::opcode::cont; cb.consume(n); } return; } } -//------------------------------------------------------------------------------ - -template -template -class stream::write_op -{ - struct data : op - { - bool cont; - stream& ws; - consuming_buffers cb; - std::size_t remain; - int state = 0; - - data(Handler& handler, stream& ws_, - Buffers const& bs) - : cont(beast_asio_helpers:: - is_continuation(handler)) - , ws(ws_) - , cb(bs) - , remain(boost::asio::buffer_size(cb)) - { - } - }; - - handler_ptr d_; - -public: - write_op(write_op&&) = default; - write_op(write_op const&) = default; - - template - explicit - write_op(DeducedHandler&& h, - stream& ws, Args&&... args) - : d_(std::forward(h), - ws, std::forward(args)...) - { - (*this)(error_code{}, false); - } - - void operator()(error_code ec, bool again = true); - - friend - void* asio_handler_allocate( - std::size_t size, write_op* op) - { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); - } - - friend - void asio_handler_deallocate( - void* p, std::size_t size, write_op* op) - { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); - } - - friend - bool asio_handler_is_continuation(write_op* op) - { - return op->d_->cont; - } - - template - friend - void asio_handler_invoke(Function&& f, write_op* op) - { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); - } -}; - -template -template -void -stream:: -write_op:: -operator()(error_code ec, bool again) -{ - auto& d = *d_; - d.cont = d.cont || again; - if(! ec) - { - switch(d.state) - { - case 0: - { - auto const n = d.remain; - d.remain -= n; - auto const fin = d.remain <= 0; - if(fin) - d.state = 99; - auto const pb = prepare_buffers(n, d.cb); - d.cb.consume(n); - d.ws.async_write_frame(fin, pb, std::move(*this)); - return; - } - - case 99: - break; - } - } - d_.invoke(ec); -} - template template -typename async_completion< - WriteHandler, void(error_code)>::result_type +async_return_type< + WriteHandler, void(error_code)> stream:: -async_write(ConstBufferSequence const& bs, WriteHandler&& handler) +async_write_frame(bool fin, + ConstBufferSequence const& bs, WriteHandler&& handler) { - static_assert(is_AsyncStream::value, + static_assert(is_async_stream::value, "AsyncStream requirements not met"); - static_assert(beast::is_ConstBufferSequence< + static_assert(beast::is_const_buffer_sequence< ConstBufferSequence>::value, "ConstBufferSequence requirements not met"); - beast::async_completion< - WriteHandler, void(error_code)> completion{handler}; - write_op{ - completion.handler, *this, bs}; - return completion.result.get(); + async_completion init{handler}; + write_frame_op>{init.completion_handler, + *this, fin, bs}({}, 0, false); + return init.result.get(); } +//------------------------------------------------------------------------------ + template template void stream:: write(ConstBufferSequence const& buffers) { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, "SyncStream requirements not met"); - static_assert(beast::is_ConstBufferSequence< + static_assert(beast::is_const_buffer_sequence< ConstBufferSequence>::value, "ConstBufferSequence requirements not met"); error_code ec; write(buffers, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); } template @@ -936,14 +885,36 @@ void stream:: write(ConstBufferSequence const& buffers, error_code& ec) { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, "SyncStream requirements not met"); - static_assert(beast::is_ConstBufferSequence< + static_assert(beast::is_const_buffer_sequence< ConstBufferSequence>::value, "ConstBufferSequence requirements not met"); write_frame(true, buffers, ec); } +template +template +async_return_type< + WriteHandler, void(error_code)> +stream:: +async_write( + ConstBufferSequence const& bs, WriteHandler&& handler) +{ + static_assert(is_async_stream::value, + "AsyncStream requirements not met"); + static_assert(beast::is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + async_completion init{handler}; + write_op>{ + init.completion_handler, *this, bs}( + error_code{}); + return init.result.get(); +} + } // websocket } // beast diff --git a/src/beast/include/beast/websocket/option.hpp b/src/beast/include/beast/websocket/option.hpp index 005ae7b238..258f5565bc 100644 --- a/src/beast/include/beast/websocket/option.hpp +++ b/src/beast/include/beast/websocket/option.hpp @@ -10,8 +10,8 @@ #include #include -#include #include +#include #include #include #include @@ -22,177 +22,6 @@ namespace beast { namespace websocket { -/** Automatic fragmentation option. - - Determines if outgoing message payloads are broken up into - multiple pieces. - - When the automatic fragmentation size is turned on, outgoing - message payloads are broken up into multiple frames no larger - than the write buffer size. - - The default setting is to fragment messages. - - @note Objects of this type are used with - @ref beast::websocket::stream::set_option. - - @par Example - Setting the automatic fragmentation option: - @code - ... - websocket::stream stream(ios); - stream.set_option(auto_fragment{true}); - @endcode -*/ -#if GENERATING_DOCS -using auto_fragment = implementation_defined; -#else -struct auto_fragment -{ - bool value; - - explicit - auto_fragment(bool v) - : value(v) - { - } -}; -#endif - -/** HTTP decorator option. - - The decorator transforms the HTTP requests and responses used - when requesting or responding to the WebSocket Upgrade. This may - be used to set or change header fields. For example to set the - Server or User-Agent fields. The default setting applies no - transformation to the HTTP message. - - The context in which the decorator is called depends on the - type of operation performed: - - @li For synchronous operations, the implementation will call the - decorator before the operation unblocks. - - @li For asynchronous operations, the implementation guarantees - that calls to the decorator will be made from the same implicit - or explicit strand used to call the asynchronous initiation - function. - - The default setting is no decorator. - - @note Objects of this type are used with - @ref beast::websocket::stream::set_option. - - @par Example - Setting the decorator. - @code - struct identity - { - template - void - operator()(http::message& m) - { - if(isRequest) - m.fields.replace("User-Agent", "MyClient"); - else - m.fields.replace("Server", "MyServer"); - } - }; - ... - websocket::stream ws(ios); - ws.set_option(decorate(identity{})); - @endcode -*/ -#if GENERATING_DOCS -using decorate = implementation_defined; -#else -using decorate = detail::decorator_type; -#endif - -/** Keep-alive option. - - Determines if the connection is closed after a failed upgrade - request. - - This setting only affects the behavior of HTTP requests that - implicitly or explicitly ask for a keepalive. For HTTP requests - that indicate the connection should be closed, the connection is - closed as per rfc7230. - - The default setting is to close connections after a failed - upgrade request. - - @note Objects of this type are used with - @ref beast::websocket::stream::set_option. - - @par Example - Setting the keep alive option. - @code - ... - websocket::stream ws(ios); - ws.set_option(keep_alive{8192}); - @endcode -*/ -#if GENERATING_DOCS -using keep_alive = implementation_defined; -#else -struct keep_alive -{ - bool value; - - explicit - keep_alive(bool v) - : value(v) - { - } -}; -#endif - -/** Message type option. - - This controls the opcode set for outgoing messages. Valid - choices are opcode::binary or opcode::text. The setting is - only applied at the start when a caller begins a new message. - Changing the opcode after a message is started will only - take effect after the current message being sent is complete. - - The default setting is opcode::text. - - @note Objects of this type are used with - @ref beast::websocket::stream::set_option. - - @par Example - Setting the message type to binary. - @code - ... - websocket::stream ws(ios); - ws.set_option(message_type{opcode::binary}); - @endcode -*/ -#if GENERATING_DOCS -using message_type = implementation_defined; -#else -struct message_type -{ - opcode value; - - explicit - message_type(opcode op) - { - if(op != opcode::binary && op != opcode::text) - throw beast::detail::make_exception( - "bad opcode", __FILE__, __LINE__); - value = op; - } -}; -#endif - -namespace detail { - -using ping_cb = std::function; - -} // detail - /** permessage-deflate extension options. These settings control the permessage-deflate extension, @@ -234,183 +63,6 @@ struct permessage_deflate int memLevel = 4; }; -/** Ping callback option. - - Sets the callback to be invoked whenever a ping or pong is - received during a call to one of the following functions: - - @li @ref beast::websocket::stream::read - @li @ref beast::websocket::stream::read_frame - @li @ref beast::websocket::stream::async_read - @li @ref beast::websocket::stream::async_read_frame - - Unlike completion handlers, the callback will be invoked - for each received ping and pong during a call to any - synchronous or asynchronous read function. The operation is - passive, with no associated error code, and triggered by reads. - - The signature of the callback must be: - @code - void - callback( - bool is_pong, // `true` if this is a pong - ping_data const& payload // Payload of the pong frame - ); - @endcode - - The value of `is_pong` will be `true` if a pong control frame - is received, and `false` if a ping control frame is received. - - If the read operation receiving a ping or pong frame is an - asynchronous operation, the callback will be invoked using - the same method as that used to invoke the final handler. - - @note Objects of this type are used with - @ref beast::websocket::stream::set_option. - To remove the ping callback, construct the option with - no parameters: `set_option(ping_callback{})` -*/ -#if GENERATING_DOCS -using ping_callback = implementation_defined; -#else -struct ping_callback -{ - detail::ping_cb value; - - ping_callback() = default; - ping_callback(ping_callback&&) = default; - ping_callback(ping_callback const&) = default; - - explicit - ping_callback(detail::ping_cb f) - : value(std::move(f)) - { - } -}; -#endif - -/** Read buffer size option. - - Sets the size of the read buffer used by the implementation to - receive frames. The read buffer is needed when permessage-deflate - is used. - - Lowering the size of the buffer can decrease the memory requirements - for each connection, while increasing the size of the buffer can reduce - the number of calls made to the next layer to read data. - - The default setting is 4096. The minimum value is 8. - - @note Objects of this type are used with - @ref beast::websocket::stream::set_option. - - @par Example - Setting the read buffer size. - @code - ... - websocket::stream ws(ios); - ws.set_option(read_buffer_size{16 * 1024}); - @endcode -*/ -#if GENERATING_DOCS -using read_buffer_size = implementation_defined; -#else -struct read_buffer_size -{ - std::size_t value; - - explicit - read_buffer_size(std::size_t n) - : value(n) - { - if(n < 8) - throw beast::detail::make_exception( - "read buffer size is too small", __FILE__, __LINE__); - } -}; -#endif - -/** Maximum incoming message size option. - - Sets the largest permissible incoming message size. Message - frame fields indicating a size that would bring the total - message size over this limit will cause a protocol failure. - - The default setting is 16 megabytes. A value of zero indicates - a limit of the maximum value of a `std::uint64_t`. - - @note Objects of this type are used with - @ref beast::websocket::stream::set_option. - - @par Example - Setting the maximum read message size. - @code - ... - websocket::stream ws(ios); - ws.set_option(read_message_max{65536}); - @endcode -*/ -#if GENERATING_DOCS -using read_message_max = implementation_defined; -#else -struct read_message_max -{ - std::size_t value; - - explicit - read_message_max(std::size_t n) - : value(n) - { - } -}; -#endif - -/** Write buffer size option. - - Sets the size of the write buffer used by the implementation to - send frames. The write buffer is needed when masking payload data - in the client role, compressing frames, or auto-fragmenting message - data. - - Lowering the size of the buffer can decrease the memory requirements - for each connection, while increasing the size of the buffer can reduce - the number of calls made to the next layer to write data. - - The default setting is 4096. The minimum value is 8. - - The write buffer size can only be changed when the stream is not - open. Undefined behavior results if the option is modified after a - successful WebSocket handshake. - - @note Objects of this type are used with - @ref beast::websocket::stream::set_option. - - @par Example - Setting the write buffer size. - @code - ... - websocket::stream ws(ios); - ws.set_option(write_buffer_size{8192}); - @endcode -*/ -#if GENERATING_DOCS -using write_buffer_size = implementation_defined; -#else -struct write_buffer_size -{ - std::size_t value; - - explicit - write_buffer_size(std::size_t n) - : value(n) - { - if(n < 8) - throw beast::detail::make_exception( - "write buffer size is too small", __FILE__, __LINE__); - } -}; -#endif - } // websocket } // beast diff --git a/src/beast/include/beast/websocket/rfc6455.hpp b/src/beast/include/beast/websocket/rfc6455.hpp index 3acb11e53d..bb819c8749 100644 --- a/src/beast/include/beast/websocket/rfc6455.hpp +++ b/src/beast/include/beast/websocket/rfc6455.hpp @@ -10,83 +10,138 @@ #include #include -#include +#include +#include #include #include namespace beast { namespace websocket { -/** WebSocket frame header opcodes. */ -enum class opcode : std::uint8_t -{ - cont = 0, - text = 1, - binary = 2, - rsv3 = 3, - rsv4 = 4, - rsv5 = 5, - rsv6 = 6, - rsv7 = 7, - close = 8, - ping = 9, - pong = 10, - crsvb = 11, - crsvc = 12, - crsvd = 13, - crsve = 14, - crsvf = 15 -}; +/** Returns `true` if the specified HTTP request is a WebSocket Upgrade. + + This function returns `true` when the passed HTTP Request + indicates a WebSocket Upgrade. It does not validate the + contents of the fields: it just trivially accepts requests + which could only possibly be a valid or invalid WebSocket + Upgrade message. + + Callers who wish to manually read HTTP requests in their + server implementation can use this function to determine if + the request should be routed to an instance of + @ref websocket::stream. + + @par Example + @code + void handle_connection(boost::asio::ip::tcp::socket& sock) + { + beast::flat_buffer buffer; + beast::http::request req; + beast::http::read(sock, buffer, req); + if(beast::websocket::is_upgrade(req)) + { + beast::websocket::stream ws{std::move(sock)}; + ws.accept(req); + } + } + @endcode + + @param req The HTTP Request object to check. + + @return `true` if the request is a WebSocket Upgrade. +*/ +template +bool +is_upgrade(beast::http::header> const& req); /** Close status codes. These codes accompany close frames. @see RFC 6455 7.4.1 Defined Status Codes - */ -#if GENERATING_DOCS -enum close_code -#else -namespace close_code { -using value = std::uint16_t; -enum -#endif +enum close_code : std::uint16_t { - /// used internally to mean "no error" - none = 0, - + /// Normal closure; the connection successfully completed whatever purpose for which it was created. normal = 1000, + + /// The endpoint is going away, either because of a server failure or because the browser is navigating away from the page that opened the connection. going_away = 1001, + + /// The endpoint is terminating the connection due to a protocol error. protocol_error = 1002, + /// The connection is being terminated because the endpoint received data of a type it cannot accept (for example, a text-only endpoint received binary data). unknown_data = 1003, - /// Indicates a received close frame has no close code - //no_code = 1005, // TODO - - /// Indicates the connection was closed without receiving a close frame - no_close = 1006, - + /// The endpoint is terminating the connection because a message was received that contained inconsistent data (e.g., non-UTF-8 data within a text message). bad_payload = 1007, + + /// The endpoint is terminating the connection because it received a message that violates its policy. This is a generic status code, used when codes 1003 and 1009 are not suitable. policy_error = 1008, + + /// The endpoint is terminating the connection because a data frame was received that is too large. too_big = 1009, + + /// The client is terminating the connection because it expected the server to negotiate one or more extension, but the server didn't. needs_extension = 1010, + + /// The server is terminating the connection because it encountered an unexpected condition that prevented it from fulfilling the request. internal_error = 1011, + /// The server is terminating the connection because it is restarting. service_restart = 1012, + + /// The server is terminating the connection due to a temporary condition, e.g. it is overloaded and is casting off some of its clients. try_again_later = 1013, - reserved1 = 1004, - no_status = 1005, // illegal on wire - abnormal = 1006, // illegal on wire - reserved2 = 1015, + //---- + // + // The following are illegal on the wire + // - last = 5000 // satisfy warnings + /** Used internally to mean "no error" + + This code is reserved and may not be sent. + */ + none = 0, + + /** Reserved for future use by the WebSocket standard. + + This code is reserved and may not be sent. + */ + reserved1 = 1004, + + /** No status code was provided even though one was expected. + + This code is reserved and may not be sent. + */ + no_status = 1005, + + /** Connection was closed without receiving a close frame + + This code is reserved and may not be sent. + */ + abnormal = 1006, + + /** Reserved for future use by the WebSocket standard. + + This code is reserved and may not be sent. + */ + reserved2 = 1014, + + /** Reserved for future use by the WebSocket standard. + + This code is reserved and may not be sent. + */ + reserved3 = 1015 + + // + //---- + + //last = 5000 // satisfy warnings }; -#if ! GENERATING_DOCS -} // close_code -#endif /// The type representing the reason string in a close frame. using reason_string = static_string<123, char>; @@ -102,7 +157,7 @@ using ping_data = static_string<125, char>; struct close_reason { /// The close code. - close_code::value code = close_code::none; + std::uint16_t code = close_code::none; /// The optional utf8-encoded reason string. reason_string reason; @@ -115,25 +170,29 @@ struct close_reason close_reason() = default; /// Construct from a code. - close_reason(close_code::value code_) + close_reason(std::uint16_t code_) : code(code_) { } - /// Construct from a reason. code is close_code::normal. - template - close_reason(char const (&reason_)[N]) + /// Construct from a reason string. code is @ref close_code::normal. + close_reason(string_view s) : code(close_code::normal) - , reason(reason_) + , reason(s) { } - /// Construct from a code and reason. - template - close_reason(close_code::value code_, - char const (&reason_)[N]) + /// Construct from a reason string literal. code is @ref close_code::normal. + close_reason(char const* s) + : code(close_code::normal) + , reason(s) + { + } + + /// Construct from a close code and reason string. + close_reason(close_code code_, string_view s) : code(code_) - , reason(reason_) + , reason(s) { } @@ -147,4 +206,6 @@ struct close_reason } // websocket } // beast +#include + #endif diff --git a/src/beast/include/beast/websocket/ssl.hpp b/src/beast/include/beast/websocket/ssl.hpp index d10dafdf44..ca4f039c9b 100644 --- a/src/beast/include/beast/websocket/ssl.hpp +++ b/src/beast/include/beast/websocket/ssl.hpp @@ -12,7 +12,6 @@ #include #include #include -#include namespace beast { namespace websocket { diff --git a/src/beast/include/beast/websocket/stream.hpp b/src/beast/include/beast/websocket/stream.hpp index 5f58042af4..4c494890c0 100644 --- a/src/beast/include/beast/websocket/stream.hpp +++ b/src/beast/include/beast/websocket/stream.hpp @@ -9,34 +9,60 @@ #define BEAST_WEBSOCKET_STREAM_HPP #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 #include +#include #include +#include namespace beast { namespace websocket { -/** Information about a WebSocket frame. +namespace detail { +class frame_test; +} - This information is provided to callers during frame - read operations. +/// The type of object holding HTTP Upgrade requests +using request_type = http::request; + +/// The type of object holding HTTP Upgrade responses +using response_type = http::response; + +/** The type of received control frame. + + Values of this type are passed to the control frame + callback set using @ref stream::control_callback. */ -struct frame_info +enum class frame_type { - /// Indicates the type of message (binary or text). - opcode op; + /// A close frame was received + close, - /// `true` if this is the last frame in the current message. - bool fin; + /// A ping frame was received + ping, + + /// A pong frame was received + pong }; //-------------------------------------------------------------------- @@ -46,12 +72,14 @@ struct frame_info The @ref stream class template provides asynchronous and blocking message-oriented functionality necessary for clients and servers to utilize the WebSocket protocol. + + For asynchronous operations, the application must ensure + that they are are all performed within the same implicit + or explicit strand. @par Thread Safety @e Distinct @e objects: Safe.@n - @e Shared @e objects: Unsafe. The application must ensure that - all asynchronous operations are performed within the same - implicit or explicit strand. + @e Shared @e objects: Unsafe. @par Example @@ -59,35 +87,190 @@ struct frame_info you would write: @code - websocket::stream ws(io_service); + websocket::stream ws{io_service}; @endcode Alternatively, you can write: @code - ip::tcp::socket sock(io_service); - websocket::stream ws(sock); + ip::tcp::socket sock{io_service}; + websocket::stream ws{sock}; @endcode @tparam NextLayer The type representing the next layer, to which data will be read and written during operations. For synchronous - operations, the type must support the @b `SyncStream` concept. + operations, the type must support the @b SyncStream concept. For asynchronous operations, the type must support the - @b `AsyncStream` concept. + @b AsyncStream concept. @note A stream object must not be moved or destroyed while there are pending asynchronous operations associated with it. @par Concepts - @b `AsyncStream`, - @b `Decorator`, - @b `DynamicBuffer`, - @b `SyncStream` + @b AsyncStream, + @b DynamicBuffer, + @b SyncStream */ template -class stream : public detail::stream_base +class stream { + friend class detail::frame_test; friend class stream_test; - dynabuf_readstream stream_; + buffered_read_stream stream_; + + /// Identifies the role of a WebSockets stream. + enum class role_type + { + /// Stream is operating as a client. + client, + + /// Stream is operating as a server. + server + }; + + friend class frame_test; + + using control_cb_type = + std::function; + + struct op {}; + + detail::maskgen maskgen_; // source of mask keys + std::size_t rd_msg_max_ = + 16 * 1024 * 1024; // max message size + bool wr_autofrag_ = true; // auto fragment + std::size_t wr_buf_size_ = 4096; // write buffer size + std::size_t rd_buf_size_ = 4096; // read buffer size + detail::opcode wr_opcode_ = + detail::opcode::text; // outgoing message type + control_cb_type ctrl_cb_; // control callback + role_type role_; // server or client + bool failed_; // the connection failed + + bool wr_close_; // sent close frame + op* wr_block_; // op currenly writing + + ping_data* ping_data_; // where to put the payload + detail::pausation rd_op_; // paused read op + detail::pausation wr_op_; // paused write op + detail::pausation ping_op_; // paused ping op + detail::pausation close_op_; // paused close op + close_reason cr_; // set from received close frame + + // State information for the message being received + // + struct rd_t + { + // opcode of current message being read + detail::opcode op; + + // `true` if the next frame is a continuation. + bool cont; + + // Checks that test messages are valid utf8 + detail::utf8_checker utf8; + + // Size of the current message so far. + std::uint64_t size; + + // Size of the read buffer. + // This gets set to the read buffer size option at the + // beginning of sending a message, so that the option can be + // changed mid-send without affecting the current message. + std::size_t buf_size; + + // The read buffer. Used for compression and masking. + std::unique_ptr buf; + }; + + rd_t rd_; + + // State information for the message being sent + // + struct wr_t + { + // `true` if next frame is a continuation, + // `false` if next frame starts a new message + bool cont; + + // `true` if this message should be auto-fragmented + // This gets set to the auto-fragment option at the beginning + // of sending a message, so that the option can be changed + // mid-send without affecting the current message. + bool autofrag; + + // `true` if this message should be compressed. + // This gets set to the compress option at the beginning of + // of sending a message, so that the option can be changed + // mid-send without affecting the current message. + bool compress; + + // Size of the write buffer. + // This gets set to the write buffer size option at the + // beginning of sending a message, so that the option can be + // changed mid-send without affecting the current message. + std::size_t buf_size; + + // The write buffer. Used for compression and masking. + // The buffer is allocated or reallocated at the beginning of + // sending a message. + std::unique_ptr buf; + }; + + wr_t wr_; + + // State information for the permessage-deflate extension + struct pmd_t + { + // `true` if current read message is compressed + bool rd_set; + + zlib::deflate_stream zo; + zlib::inflate_stream zi; + }; + + // If not engaged, then permessage-deflate is not + // enabled for the currently active session. + std::unique_ptr pmd_; + + // Local options for permessage-deflate + permessage_deflate pmd_opts_; + + // Offer for clients, negotiated result for servers + detail::pmd_offer pmd_config_; + + void + open(role_type role); + + void + close(); + + template + std::size_t + read_fh1(detail::frame_header& fh, + DynamicBuffer& db, close_code& code); + + template + void + read_fh2(detail::frame_header& fh, + DynamicBuffer& db, close_code& code); + + // Called before receiving the first frame of each message + void + rd_begin(); + + // Called before sending the first frame of each message + // + void + wr_begin(); + + template + void + write_close(DynamicBuffer& db, close_reason const& rc); + + template + void + write_ping(DynamicBuffer& db, + detail::opcode op, ping_data const& data); public: /// The type of the next layer. @@ -96,14 +279,9 @@ public: /// The type of the lowest layer. using lowest_layer_type = - #if GENERATING_DOCS - implementation_defined; - #else - typename beast::detail::get_lowest_layer< - next_layer_type>::type; - #endif + typename get_lowest_layer::type; - /** Move-construct a stream. + /** Move constructor If @c NextLayer is move constructible, this function will move-construct a new stream from the existing stream. @@ -113,17 +291,17 @@ public: */ stream(stream&&) = default; - /** Move assignment. + /** Move assignment - If `NextLayer` is move constructible, this function - will move-construct a new stream from the existing stream. + If `NextLayer` is move assignable, this function + will move-assign a new stream from the existing stream. @note The behavior of move assignment on or from streams with active or pending operations is undefined. */ stream& operator=(stream&&) = default; - /** Construct a WebSocket stream. + /** Constructor This constructor creates a websocket stream and initializes the next layer object. @@ -138,133 +316,21 @@ public: explicit stream(Args&&... args); - /** Destructor. + /** Destructor @note A stream object must not be destroyed while there are pending asynchronous operations associated with it. */ ~stream() = default; - /** Set options on the stream. + /** Return the `io_service` associated with the stream - The application must ensure that calls to set options - are performed within the same implicit or explicit strand. - - @param args One or more stream options to set. - */ -#if GENERATING_DOCS - template - void - set_option(Args&&... args) -#else - template - void - set_option(A1&& a1, A2&& a2, An&&... an) -#endif - { - set_option(std::forward(a1)); - set_option(std::forward(a2), - std::forward(an)...); - } - - /// Set the automatic fragment size option - void - set_option(auto_fragment const& o) - { - wr_autofrag_ = o.value; - } - - /** Set the decorator used for HTTP messages. - - The value for this option is a callable type with two - optional signatures: - - @code - void(request_type&); - - void(response_type&); - @endcode - - If a matching signature is provided, the callable type - will be invoked with the HTTP request or HTTP response - object as appropriate. When a signature is omitted, - a default consisting of the string Beast followed by - the version number is used. - */ - void -#if GENERATING_DOCS - set_option(implementation_defined o) -#else - set_option(detail::decorator_type const& o) -#endif - { - d_ = o; - } - - /// Set the keep-alive option - void - set_option(keep_alive const& o) - { - keep_alive_ = o.value; - } - - /// Set the outgoing message type - void - set_option(message_type const& o) - { - wr_opcode_ = o.value; - } - - /// Set the permessage-deflate extension options - void - set_option(permessage_deflate const& o); - - /// Get the permessage-deflate extension options - void - get_option(permessage_deflate& o) - { - o = pmd_opts_; - } - - /// Set the ping callback - void - set_option(ping_callback o) - { - ping_cb_ = std::move(o.value); - } - - /// Set the read buffer size - void - set_option(read_buffer_size const& o) - { - rd_buf_size_ = o.value; - // VFALCO What was the thinking here? - //stream_.capacity(o.value); - } - - /// Set the maximum incoming message size allowed - void - set_option(read_message_max const& o) - { - rd_msg_max_ = o.value; - } - - /// Set the size of the write buffer - void - set_option(write_buffer_size const& o) - { - wr_buf_size_ = o.value; - } - - /** Get the io_service associated with the stream. - - This function may be used to obtain the io_service object + This function may be used to obtain the `io_service` object that the stream uses to dispatch handlers for asynchronous operations. @return A reference to the io_service object that the stream - will use to dispatch handlers. Ownership is not transferred - to the caller. + will use to dispatch handlers. */ boost::asio::io_service& get_io_service() @@ -272,13 +338,13 @@ public: return stream_.get_io_service(); } - /** Get a reference to the next layer. + /** Get a reference to the next layer This function returns a reference to the next layer in a stack of stream layers. @return A reference to the next layer in the stack of - stream layers. Ownership is not transferred to the caller. + stream layers. */ next_layer_type& next_layer() @@ -286,13 +352,13 @@ public: return stream_.next_layer(); } - /** Get a reference to the next layer. + /** Get a reference to the next layer This function returns a reference to the next layer in a stack of stream layers. @return A reference to the next layer in the stack of - stream layers. Ownership is not transferred to the caller. + stream layers. */ next_layer_type const& next_layer() const @@ -300,13 +366,13 @@ public: return stream_.next_layer(); } - /** Get a reference to the lowest layer. + /** Get a reference to the lowest layer This function returns a reference to the lowest layer in a stack of stream layers. @return A reference to the lowest layer in the stack of - stream layers. Ownership is not transferred to the caller. + stream layers. */ lowest_layer_type& lowest_layer() @@ -314,7 +380,7 @@ public: return stream_.lowest_layer(); } - /** Get a reference to the lowest layer. + /** Get a reference to the lowest layer This function returns a reference to the lowest layer in a stack of stream layers. @@ -328,6 +394,272 @@ public: return stream_.lowest_layer(); } + /// Set the permessage-deflate extension options + void + set_option(permessage_deflate const& o); + + /// Get the permessage-deflate extension options + void + get_option(permessage_deflate& o) + { + o = pmd_opts_; + } + + /** Set the automatic fragmentation option. + + Determines if outgoing message payloads are broken up into + multiple pieces. + + When the automatic fragmentation size is turned on, outgoing + message payloads are broken up into multiple frames no larger + than the write buffer size. + + The default setting is to fragment messages. + + @param v A `bool` indicating if auto fragmentation should be on. + + @par Example + Setting the automatic fragmentation option: + @code + ws.auto_fragment(true); + @endcode + */ + void + auto_fragment(bool v) + { + wr_autofrag_ = v; + } + + /// Returns `true` if the automatic fragmentation option is set. + bool + auto_fragment() const + { + return wr_autofrag_; + } + + /** Set the binary message option. + + This controls whether or not outgoing message opcodes + are set to binary or text. The setting is only applied + at the start when a caller begins a new message. Changing + the opcode after a message is started will only take effect + after the current message being sent is complete. + + The default setting is to send text messages. + + @param v `true` if outgoing messages should indicate + binary, or `false` if they should indicate text. + + @par Example + Setting the message type to binary. + @code + ws.binary(true); + @endcode + */ + void + binary(bool v) + { + wr_opcode_ = v ? + detail::opcode::binary : + detail::opcode::text; + } + + /// Returns `true` if the binary message option is set. + bool + binary() const + { + return wr_opcode_ == detail::opcode::binary; + } + + /** Set the control frame callback. + + Sets the callback to be invoked whenever a ping, pong, + or close control frame is received during a call to one + of the following functions: + + @li @ref beast::websocket::stream::read + @li @ref beast::websocket::stream::read_frame + @li @ref beast::websocket::stream::async_read + @li @ref beast::websocket::stream::async_read_frame + + Unlike completion handlers, the callback will be invoked + for each control frame during a call to any synchronous + or asynchronous read function. The operation is passive, + with no associated error code, and triggered by reads. + + The signature of the callback must be: + @code + void + callback( + frame_type kind, // The type of frame + string_view payload // The payload in the frame + ); + @endcode + + For close frames, the close reason code may be obtained by + calling the function @ref reason. + + If the read operation which receives the control frame is + an asynchronous operation, the callback will be invoked using + the same method as that used to invoke the final handler. + + @note It is not necessary to send a close frame upon receipt + of a close frame. The implementation does this automatically. + Attempting to send a close frame after a close frame is + received will result in undefined behavior. + + @param cb The callback to set. + */ + void + control_callback( + std::function cb) + { + ctrl_cb_ = std::move(cb); + } + + /** Set the read buffer size option. + + Sets the size of the read buffer used by the implementation to + receive frames. The read buffer is needed when permessage-deflate + is used. + + Lowering the size of the buffer can decrease the memory requirements + for each connection, while increasing the size of the buffer can reduce + the number of calls made to the next layer to read data. + + The default setting is 4096. The minimum value is 8. + + @param n The size of the read buffer. + + @throws std::invalid_argument If the buffer size is less than 8. + + @par Example + Setting the read buffer size. + @code + ws.read_buffer_size(16 * 1024); + @endcode + */ + void + read_buffer_size(std::size_t n) + { + if(n < 8) + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "read buffer size underflow"}); + rd_buf_size_ = n; + } + + /// Returns the read buffer size setting. + std::size_t + read_buffer_size() const + { + return rd_buf_size_; + } + + /** Set the maximum incoming message size option. + + Sets the largest permissible incoming message size. Message + frame fields indicating a size that would bring the total + message size over this limit will cause a protocol failure. + + The default setting is 16 megabytes. A value of zero indicates + a limit of the maximum value of a `std::uint64_t`. + + @par Example + Setting the maximum read message size. + @code + ws.read_message_max(65536); + @endcode + + @param n The limit on the size of incoming messages. + */ + void + read_message_max(std::size_t n) + { + rd_msg_max_ = n; + } + + /// Returns the maximum incoming message size setting. + std::size_t + read_message_max() const + { + return rd_msg_max_; + } + + /** Set the write buffer size option. + + Sets the size of the write buffer used by the implementation to + send frames. The write buffer is needed when masking payload data + in the client role, compressing frames, or auto-fragmenting message + data. + + Lowering the size of the buffer can decrease the memory requirements + for each connection, while increasing the size of the buffer can reduce + the number of calls made to the next layer to write data. + + The default setting is 4096. The minimum value is 8. + + The write buffer size can only be changed when the stream is not + open. Undefined behavior results if the option is modified after a + successful WebSocket handshake. + + @par Example + Setting the write buffer size. + @code + ws.write_buffer_size(8192); + @endcode + + @param n The size of the write buffer in bytes. + */ + void + write_buffer_size(std::size_t n) + { + if(n < 8) + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "write buffer size underflow"}); + wr_buf_size_ = n; + }; + + /// Returns the size of the write buffer. + std::size_t + write_buffer_size() const + { + return wr_buf_size_; + } + + /** Set the text message option. + + This controls whether or not outgoing message opcodes + are set to binary or text. The setting is only applied + at the start when a caller begins a new message. Changing + the opcode after a message is started will only take effect + after the current message being sent is complete. + + The default setting is to send text messages. + + @param v `true` if outgoing messages should indicate + text, or `false` if they should indicate binary. + + @par Example + Setting the message type to text. + @code + ws.text(true); + @endcode + */ + void + text(bool v) + { + wr_opcode_ = v ? + detail::opcode::text : + detail::opcode::binary; + } + + /// Returns `true` if the text message option is set. + bool + text() const + { + return wr_opcode_ == detail::opcode::text; + } + /** Returns the close reason received from the peer. This is only valid after a read completes with error::closed. @@ -338,27 +670,56 @@ public: return cr_; } + /** Returns `true` if the latest message data indicates binary. + + This function informs the caller of whether the last + received message frame represents a message with the + binary opcode. + + If there is no last message frame, the return value is + undefined. + */ + bool + got_binary() + { + return rd_.op == detail::opcode::binary; + } + + /** Returns `true` if the latest message data indicates text. + + This function informs the caller of whether the last + received message frame represents a message with the + text opcode. + + If there is no last message frame, the return value is + undefined. + */ + bool + got_text() + { + return ! got_binary(); + } + /** Read and respond to a WebSocket HTTP Upgrade request. - This function is used to synchronously read a HTTP WebSocket - Upgrade request and send the HTTP response. The call blocks until - one of the following conditions is true: + This function is used to synchronously read an HTTP WebSocket + Upgrade request and send the HTTP response. The call blocks + until one of the following conditions is true: - @li A HTTP request finishes receiving, and a HTTP response finishes - sending. + @li The request is received and the response finishes sending. @li An error occurs on the stream. - This function is implemented in terms of one or more calls to the - next layer's `read_some` and `write_some` functions. + This function is implemented in terms of one or more calls to + the next layer's `read_some` and `write_some` functions. - If the stream receives a valid HTTP WebSocket Upgrade request, a - HTTP response is sent back indicating a successful upgrade. When this - call returns, the stream is then ready to send and receive WebSocket - protocol frames and messages. + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When this call returns, the stream is then ready to send and + receive WebSocket protocol frames and messages. - If the HTTP Upgrade request is invalid or cannot be satisfied, a - HTTP response is sent indicating the reason and status code + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code (typically 400, "Bad Request"). This counts as a failure. @throws system_error Thrown on failure. @@ -368,25 +729,61 @@ public: /** Read and respond to a WebSocket HTTP Upgrade request. - This function is used to synchronously read a HTTP WebSocket - Upgrade request and send the HTTP response. The call blocks until - one of the following conditions is true: + This function is used to synchronously read an HTTP WebSocket + Upgrade request and send the HTTP response. The call blocks + until one of the following conditions is true: - @li A HTTP request finishes receiving, and a HTTP response finishes - sending. + @li The request is received and the response finishes sending. @li An error occurs on the stream. - This function is implemented in terms of one or more calls to the - next layer's `read_some` and `write_some` functions. + This function is implemented in terms of one or more calls to + the next layer's `read_some` and `write_some` functions. - If the stream receives a valid HTTP WebSocket Upgrade request, a - HTTP response is sent back indicating a successful upgrade. When this - call returns, the stream is then ready to send and receive WebSocket - protocol frames and messages. + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When this call returns, the stream is then ready to send and + receive WebSocket protocol frames and messages. - If the HTTP Upgrade request is invalid or cannot be satisfied, a - HTTP response is sent indicating the reason and status code + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure. + + @param decorator A function object which will be called to modify + the HTTP response object delivered by the implementation. This + could be used to set the Server field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + response_type& res + ); @endcode + + @throws system_error Thrown on failure. + */ + template + void + accept_ex(ResponseDecorator const& decorator); + + /** Read and respond to a WebSocket HTTP Upgrade request. + + This function is used to synchronously read an HTTP WebSocket + Upgrade request and send the HTTP response. The call blocks + until one of the following conditions is true: + + @li The request is received and the response finishes sending. + + @li An error occurs on the stream. + + This function is implemented in terms of one or more calls to + the next layer's `read_some` and `write_some` functions. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When this call returns, the stream is then ready to send and + receive WebSocket protocol frames and messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code (typically 400, "Bad Request"). This counts as a failure. @param ec Set to indicate what error occurred, if any. @@ -394,38 +791,578 @@ public: void accept(error_code& ec); + /** Read and respond to a WebSocket HTTP Upgrade request. + + This function is used to synchronously read an HTTP WebSocket + Upgrade request and send the HTTP response. The call blocks + until one of the following conditions is true: + + @li The request is received and the response finishes sending. + + @li An error occurs on the stream. + + This function is implemented in terms of one or more calls to + the next layer's `read_some` and `write_some` functions. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When this call returns, the stream is then ready to send and + receive WebSocket protocol frames and messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure. + + @param decorator A function object which will be called to modify + the HTTP response object delivered by the implementation. This + could be used to set the Server field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + response_type& res + ); @endcode + + @param ec Set to indicate what error occurred, if any. + */ + template + void + accept_ex(ResponseDecorator const& decorator, + error_code& ec); + + /** Read and respond to a WebSocket HTTP Upgrade request. + + This function is used to synchronously read an HTTP WebSocket + Upgrade request and send the HTTP response. The call blocks + until one of the following conditions is true: + + @li The request is received and the response finishes sending. + + @li An error occurs on the stream. + + This function is implemented in terms of one or more calls to + the next layer's `read_some` and `write_some` functions. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When this call returns, the stream is then ready to send and + receive WebSocket protocol frames and messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure. + + @param buffers Caller provided data that has already been + received on the stream. The implementation will copy the + caller provided data before the function returns. + + @throws system_error Thrown on failure. + */ + template +#if BEAST_DOXYGEN + void +#else + typename std::enable_if::value>::type +#endif + accept(ConstBufferSequence const& buffers); + + /** Read and respond to a WebSocket HTTP Upgrade request. + + This function is used to synchronously read an HTTP WebSocket + Upgrade request and send the HTTP response. The call blocks + until one of the following conditions is true: + + @li The request is received and the response finishes sending. + + @li An error occurs on the stream. + + This function is implemented in terms of one or more calls to + the next layer's `read_some` and `write_some` functions. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When this call returns, the stream is then ready to send and + receive WebSocket protocol frames and messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure. + + @param buffers Caller provided data that has already been + received on the stream. The implementation will copy the + caller provided data before the function returns. + + @param decorator A function object which will be called to modify + the HTTP response object delivered by the implementation. This + could be used to set the Server field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + response_type& res + ); @endcode + + @throws system_error Thrown on failure. + */ + template +#if BEAST_DOXYGEN + void +#else + typename std::enable_if::value>::type +#endif + accept_ex(ConstBufferSequence const& buffers, + ResponseDecorator const& decorator); + + /** Read and respond to a WebSocket HTTP Upgrade request. + + This function is used to synchronously read an HTTP WebSocket + Upgrade request and send the HTTP response. The call blocks + until one of the following conditions is true: + + @li The request is received and the response finishes sending. + + @li An error occurs on the stream. + + This function is implemented in terms of one or more calls to + the next layer's `read_some` and `write_some` functions. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When this call returns, the stream is then ready to send and + receive WebSocket protocol frames and messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure. + + @param buffers Caller provided data that has already been + received on the stream. The implementation will copy the + caller provided data before the function returns. + + @param ec Set to indicate what error occurred, if any. + */ + template +#if BEAST_DOXYGEN + void +#else + typename std::enable_if::value>::type +#endif + accept(ConstBufferSequence const& buffers, error_code& ec); + + /** Read and respond to a WebSocket HTTP Upgrade request. + + This function is used to synchronously read an HTTP WebSocket + Upgrade request and send the HTTP response. The call blocks + until one of the following conditions is true: + + @li The request is received and the response finishes sending. + + @li An error occurs on the stream. + + This function is implemented in terms of one or more calls to + the next layer's `read_some` and `write_some` functions. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When this call returns, the stream is then ready to send and + receive WebSocket protocol frames and messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure. + + @param buffers Caller provided data that has already been + received on the stream. The implementation will copy the + caller provided data before the function returns. + + @param decorator A function object which will be called to modify + the HTTP response object delivered by the implementation. This + could be used to set the Server field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + response_type& res + ); @endcode + + @param ec Set to indicate what error occurred, if any. + */ + template +#if BEAST_DOXYGEN + void +#else + typename std::enable_if::value>::type +#endif + accept_ex(ConstBufferSequence const& buffers, + ResponseDecorator const& decorator, + error_code& ec); + + /** Respond to a WebSocket HTTP Upgrade request + + This function is used to synchronously send the HTTP response + to an HTTP request possibly containing a WebSocket Upgrade. + The call blocks until one of the following conditions is true: + + @li The response finishes sending. + + @li An error occurs on the stream. + + This function is implemented in terms of one or more calls to + the next layer's `read_some` and `write_some` functions. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When this call returns, the stream is then ready to send and + receive WebSocket protocol frames and messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure. + + @param req An object containing the HTTP Upgrade request. + Ownership is not transferred, the implementation will not + access this object from other threads. + + @throws system_error Thrown on failure. + */ + template + void + accept(http::header> const& req); + + /** Respond to a WebSocket HTTP Upgrade request + + This function is used to synchronously send the HTTP response + to an HTTP request possibly containing a WebSocket Upgrade. + The call blocks until one of the following conditions is true: + + @li The response finishes sending. + + @li An error occurs on the stream. + + This function is implemented in terms of one or more calls to + the next layer's `read_some` and `write_some` functions. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When this call returns, the stream is then ready to send and + receive WebSocket protocol frames and messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure. + + @param req An object containing the HTTP Upgrade request. + Ownership is not transferred, the implementation will not + access this object from other threads. + + @param decorator A function object which will be called to modify + the HTTP response object delivered by the implementation. This + could be used to set the Server field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + response_type& res + ); @endcode + + @throws system_error Thrown on failure. + */ + template + void + accept_ex(http::header> const& req, + ResponseDecorator const& decorator); + + /** Respond to a WebSocket HTTP Upgrade request + + This function is used to synchronously send the HTTP response + to an HTTP request possibly containing a WebSocket Upgrade. + The call blocks until one of the following conditions is true: + + @li The response finishes sending. + + @li An error occurs on the stream. + + This function is implemented in terms of one or more calls to + the next layer's `read_some` and `write_some` functions. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When this call returns, the stream is then ready to send and + receive WebSocket protocol frames and messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure. + + @param req An object containing the HTTP Upgrade request. + Ownership is not transferred, the implementation will not + access this object from other threads. + + @param ec Set to indicate what error occurred, if any. + */ + template + void + accept(http::header> const& req, + error_code& ec); + + /** Respond to a WebSocket HTTP Upgrade request + + This function is used to synchronously send the HTTP response + to an HTTP request possibly containing a WebSocket Upgrade. + The call blocks until one of the following conditions is true: + + @li The response finishes sending. + + @li An error occurs on the stream. + + This function is implemented in terms of one or more calls to + the next layer's `read_some` and `write_some` functions. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When this call returns, the stream is then ready to send and + receive WebSocket protocol frames and messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure. + + @param req An object containing the HTTP Upgrade request. + Ownership is not transferred, the implementation will not + access this object from other threads. + + @param decorator A function object which will be called to modify + the HTTP response object delivered by the implementation. This + could be used to set the Server field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + response_type& res + ); @endcode + + @param ec Set to indicate what error occurred, if any. + */ + template + void + accept_ex(http::header> const& req, + ResponseDecorator const& decorator, + error_code& ec); + + /** Respond to a WebSocket HTTP Upgrade request + + This function is used to synchronously send the HTTP response + to an HTTP request possibly containing a WebSocket Upgrade. + The call blocks until one of the following conditions is true: + + @li The response finishes sending. + + @li An error occurs on the stream. + + This function is implemented in terms of one or more calls to + the next layer's `read_some` and `write_some` functions. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When this call returns, the stream is then ready to send and + receive WebSocket protocol frames and messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure. + + @param req An object containing the HTTP Upgrade request. + Ownership is not transferred, the implementation will not + access this object from other threads. + + @param buffers Caller provided data that has already been + received on the stream. This must not include the octets + corresponding to the HTTP Upgrade request. The implementation + will copy the caller provided data before the function returns. + + @throws system_error Thrown on failure. + */ + template + void + accept(http::header> const& req, + ConstBufferSequence const& buffers); + + /** Respond to a WebSocket HTTP Upgrade request + + This function is used to synchronously send the HTTP response + to an HTTP request possibly containing a WebSocket Upgrade. + The call blocks until one of the following conditions is true: + + @li The response finishes sending. + + @li An error occurs on the stream. + + This function is implemented in terms of one or more calls to + the next layer's `read_some` and `write_some` functions. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When this call returns, the stream is then ready to send and + receive WebSocket protocol frames and messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure. + + @param req An object containing the HTTP Upgrade request. + Ownership is not transferred, the implementation will not + access this object from other threads. + + @param buffers Caller provided data that has already been + received on the stream. This must not include the octets + corresponding to the HTTP Upgrade request. The implementation + will copy the caller provided data before the function returns. + + @param decorator A function object which will be called to modify + the HTTP response object delivered by the implementation. This + could be used to set the Server field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + response_type& res + ); @endcode + + @throws system_error Thrown on failure. + */ + template + void + accept_ex(http::header> const& req, + ConstBufferSequence const& buffers, + ResponseDecorator const& decorator); + + /** Respond to a WebSocket HTTP Upgrade request + + This function is used to synchronously send the HTTP response + to an HTTP request possibly containing a WebSocket Upgrade. + The call blocks until one of the following conditions is true: + + @li The response finishes sending. + + @li An error occurs on the stream. + + This function is implemented in terms of one or more calls to + the next layer's `read_some` and `write_some` functions. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When this call returns, the stream is then ready to send and + receive WebSocket protocol frames and messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure. + + @param req An object containing the HTTP Upgrade request. + Ownership is not transferred, the implementation will not + access this object from other threads. + + @param buffers Caller provided data that has already been + received on the stream. This must not include the octets + corresponding to the HTTP Upgrade request. The implementation + will copy the caller provided data before the function returns. + + @param ec Set to indicate what error occurred, if any. + */ + template + void + accept(http::header const& req, + ConstBufferSequence const& buffers, error_code& ec); + + /** Respond to a WebSocket HTTP Upgrade request + + This function is used to synchronously send the HTTP response + to an HTTP request possibly containing a WebSocket Upgrade. + The call blocks until one of the following conditions is true: + + @li The response finishes sending. + + @li An error occurs on the stream. + + This function is implemented in terms of one or more calls to + the next layer's `read_some` and `write_some` functions. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When this call returns, the stream is then ready to send and + receive WebSocket protocol frames and messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure. + + @param req An object containing the HTTP Upgrade request. + Ownership is not transferred, the implementation will not + access this object from other threads. + + @param buffers Caller provided data that has already been + received on the stream. This must not include the octets + corresponding to the HTTP Upgrade request. The implementation + will copy the caller provided data before the function returns. + + @param decorator A function object which will be called to modify + the HTTP response object delivered by the implementation. This + could be used to set the Server field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + response_type& res + ); @endcode + + @param ec Set to indicate what error occurred, if any. + */ + template + void + accept_ex(http::header> const& req, + ConstBufferSequence const& buffers, + ResponseDecorator const& decorator, + error_code& ec); + /** Start reading and responding to a WebSocket HTTP Upgrade request. - This function is used to asynchronously read a HTTP WebSocket + This function is used to asynchronously read an HTTP WebSocket Upgrade request and send the HTTP response. The function call always returns immediately. The asynchronous operation will continue until one of the following conditions is true: - @li A HTTP request finishes receiving, and a HTTP response finishes - sending. + @li The request is received and the response finishes sending. @li An error occurs on the stream. - This operation is implemented in terms of one or more calls to the - next layer's `async_read_some` and `async_write_some` functions, and - is known as a composed operation. The program must ensure - that the stream performs no other operations until this operation - completes. + This operation is implemented in terms of one or more calls to + the next layer's `async_read_some` and `async_write_some` + functions, and is known as a composed operation. The + program must ensure that the stream performs no other + asynchronous operations until this operation completes. - If the stream receives a valid HTTP WebSocket Upgrade request, a - HTTP response is sent back indicating a successful upgrade. When - this call returns, the stream is then ready to send and receive - WebSocket protocol frames and messages. + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When the completion handler is invoked, the stream is then + ready to send and receive WebSocket protocol frames and + messages. - If the HTTP Upgrade request is invalid or cannot be satisfied, a - HTTP response is sent indicating the reason and status code - (typically 400, "Bad Request"). This counts as a failure. + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure, and + the completion handler will be invoked with a suitable error + code set. - @param handler The handler to be called when the request completes. - Copies will be made of the handler as required. The equivalent - function signature of the handler must be: + @param handler The handler to be called when the request + completes. Copies will be made of the handler as required. The + equivalent function signature of the handler must be: @code void handler( - error_code const& error // result of operation + error_code const& ec // Result of operation ); @endcode Regardless of whether the asynchronous operation completes immediately or not, the handler will not be invoked from within @@ -433,112 +1370,101 @@ public: manner equivalent to using `boost::asio::io_service::post`. */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN void_or_deduced #else - typename async_completion< - AcceptHandler, void(error_code)>::result_type + async_return_type< + AcceptHandler, void(error_code)> #endif async_accept(AcceptHandler&& handler); - /** Read and respond to a WebSocket HTTP Upgrade request. - - This function is used to synchronously read a HTTP WebSocket - Upgrade request and send the HTTP response. The call blocks until - one of the following conditions is true: - - @li A HTTP request finishes receiving, and a HTTP response finishes - sending. - - @li An error occurs on the stream. - - This function is implemented in terms of one or more calls to the - next layer's `read_some` and `write_some` functions. - - If the stream receives a valid HTTP WebSocket Upgrade request, a - HTTP response is sent back indicating a successful upgrade. When - this call returns, the stream is then ready to send and receive - WebSocket protocol frames and messages. - - If the HTTP Upgrade request is invalid or cannot be satisfied, a - HTTP response is sent indicating the reason and status code - (typically 400, "Bad Request"). This counts as a failure. - - @param buffers Caller provided data that has already been - received on the stream. This may be used for implementations - allowing multiple protocols on the same stream. The - buffered data will first be applied to the handshake, and - then to received WebSocket frames. The implementation will - copy the caller provided data before the function returns. - - @throws system_error Thrown on failure. - */ - template - void - accept(ConstBufferSequence const& buffers); - - /** Read and respond to a WebSocket HTTP Upgrade request. - - This function is used to synchronously read a HTTP WebSocket - Upgrade request and send the HTTP response. The call blocks until - one of the following conditions is true: - - @li A HTTP request finishes receiving, and a HTTP response finishes - sending. - - @li An error occurs on the stream. - - This function is implemented in terms of one or more calls to the - next layer's `read_some` and `write_some` functions. - - If the stream receives a valid HTTP WebSocket Upgrade request, a - HTTP response is sent back indicating a successful upgrade. When - this call returns, the stream is then ready to send and receive - WebSocket protocol frames and messages. - - If the HTTP Upgrade request is invalid or cannot be satisfied, a - HTTP response is sent indicating the reason and status code - (typically 400, "Bad Request"). This counts as a failure. - - @param buffers Caller provided data that has already been - received on the stream. This may be used for implementations - allowing multiple protocols on the same stream. The - buffered data will first be applied to the handshake, and - then to received WebSocket frames. The implementation will - copy the caller provided data before the function returns. - - @param ec Set to indicate what error occurred, if any. - */ - template - void - accept(ConstBufferSequence const& buffers, error_code& ec); - /** Start reading and responding to a WebSocket HTTP Upgrade request. - This function is used to asynchronously read a HTTP WebSocket + This function is used to asynchronously read an HTTP WebSocket Upgrade request and send the HTTP response. The function call always returns immediately. The asynchronous operation will continue until one of the following conditions is true: - @li A HTTP request finishes receiving, and a HTTP response finishes - sending. + @li The request is received and the response finishes sending. @li An error occurs on the stream. - This operation is implemented in terms of one or more calls to the - next layer's `async_read_some` and `async_write_some` functions, and - is known as a composed operation. The program must ensure - that the stream performs no other operations until this operation - completes. + This operation is implemented in terms of one or more calls to + the next layer's `async_read_some` and `async_write_some` + functions, and is known as a composed operation. The + program must ensure that the stream performs no other + asynchronous operations until this operation completes. - If the stream receives a valid HTTP WebSocket Upgrade request, a - HTTP response is sent back indicating a successful upgrade. When - this call returns, the stream is then ready to send and receive - WebSocket protocol frames and messages. + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When the completion handler is invoked, the stream is then + ready to send and receive WebSocket protocol frames and + messages. - If the HTTP Upgrade request is invalid or cannot be satisfied, a - HTTP response is sent indicating the reason and status code - (typically 400, "Bad Request"). This counts as a failure. + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure, and + the completion handler will be invoked with a suitable error + code set. + + @param decorator A function object which will be called to modify + the HTTP response object delivered by the implementation. This + could be used to set the Server field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + response_type& res + ); @endcode + + @param handler The handler to be called when the request + completes. Copies will be made of the handler as required. The + equivalent function signature of the handler must be: + @code void handler( + error_code const& ec // Result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using `boost::asio::io_service::post`. + */ + template +#if BEAST_DOXYGEN + void_or_deduced +#else + async_return_type< + AcceptHandler, void(error_code)> +#endif + async_accept_ex(ResponseDecorator const& decorator, + AcceptHandler&& handler); + + /** Start reading and responding to a WebSocket HTTP Upgrade request. + + This function is used to asynchronously read an HTTP WebSocket + Upgrade request and send the HTTP response. The function call + always returns immediately. The asynchronous operation will + continue until one of the following conditions is true: + + @li The request is received and the response finishes sending. + + @li An error occurs on the stream. + + This operation is implemented in terms of one or more calls to + the next layer's `async_read_some` and `async_write_some` + functions, and is known as a composed operation. The + program must ensure that the stream performs no other + asynchronous operations until this operation completes. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When the completion handler is invoked, the stream is then + ready to send and receive WebSocket protocol frames and + messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure, and + the completion handler will be invoked with a suitable error + code set. @param buffers Caller provided data that has already been received on the stream. This may be used for implementations @@ -547,11 +1473,11 @@ public: then to received WebSocket frames. The implementation will copy the caller provided data before the function returns. - @param handler The handler to be called when the request completes. - Copies will be made of the handler as required. The equivalent - function signature of the handler must be: + @param handler The handler to be called when the request + completes. Copies will be made of the handler as required. The + equivalent function signature of the handler must be: @code void handler( - error_code const& error // result of operation + error_code const& ec // Result of operation ); @endcode Regardless of whether the asynchronous operation completes immediately or not, the handler will not be invoked from within @@ -559,140 +1485,355 @@ public: manner equivalent to using `boost::asio::io_service::post`. */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN void_or_deduced #else - typename async_completion< - AcceptHandler, void(error_code)>::result_type + typename std::enable_if< + ! http::detail::is_header::value, + async_return_type>::type #endif async_accept(ConstBufferSequence const& buffers, AcceptHandler&& handler); - /** Respond to a WebSocket HTTP Upgrade request + /** Start reading and responding to a WebSocket HTTP Upgrade request. - This function is used to synchronously send the HTTP response to - a HTTP request possibly containing a WebSocket Upgrade request. - The call blocks until one of the following conditions is true: + This function is used to asynchronously read an HTTP WebSocket + Upgrade request and send the HTTP response. The function call + always returns immediately. The asynchronous operation will + continue until one of the following conditions is true: - @li A HTTP response finishes sending. + @li The request is received and the response finishes sending. @li An error occurs on the stream. - This function is implemented in terms of one or more calls to the - next layer's `write_some` functions. + This operation is implemented in terms of one or more calls to + the next layer's `async_read_some` and `async_write_some` + functions, and is known as a composed operation. The + program must ensure that the stream performs no other + asynchronous operations until this operation completes. - If the passed HTTP request is a valid HTTP WebSocket Upgrade - request, a HTTP response is sent back indicating a successful - upgrade. When this call returns, the stream is then ready to send - and receive WebSocket protocol frames and messages. + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When the completion handler is invoked, the stream is then + ready to send and receive WebSocket protocol frames and + messages. - If the HTTP request is invalid or cannot be satisfied, a HTTP - response is sent indicating the reason and status code (typically - 400, "Bad Request"). This counts as a failure. + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure, and + the completion handler will be invoked with a suitable error + code set. - @param request An object containing the HTTP Upgrade request. - Ownership is not transferred, the implementation will not access - this object from other threads. + @param buffers Caller provided data that has already been + received on the stream. This may be used for implementations + allowing multiple protocols on the same stream. The + buffered data will first be applied to the handshake, and + then to received WebSocket frames. The implementation will + copy the caller provided data before the function returns. - @throws system_error Thrown on failure. - */ - // VFALCO TODO This should also take a DynamicBuffer with any leftover bytes. - template - void - accept(http::request const& request); + @param decorator A function object which will be called to modify + the HTTP response object delivered by the implementation. This + could be used to set the Server field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + response_type& res + ); @endcode - /** Respond to a WebSocket HTTP Upgrade request - - This function is used to synchronously send the HTTP response to - a HTTP request possibly containing a WebSocket Upgrade request. - The call blocks until one of the following conditions is true: - - @li A HTTP response finishes sending. - - @li An error occurs on the stream. - - This function is implemented in terms of one or more calls to the - next layer's `write_some` functions. - - If the passed HTTP request is a valid HTTP WebSocket Upgrade - request, a HTTP response is sent back indicating a successful - upgrade. When this call returns, the stream is then ready to send - and receive WebSocket protocol frames and messages. - - If the HTTP request is invalid or cannot be satisfied, a HTTP - response is sent indicating the reason and status code (typically - 400, "Bad Request"). This counts as a failure. - - @param request An object containing the HTTP Upgrade request. - Ownership is not transferred, the implementation will not access - this object from other threads. - - @param ec Set to indicate what error occurred, if any. - */ - template - void - accept(http::request const& request, - error_code& ec); - - /** Start responding to a WebSocket HTTP Upgrade request. - - This function is used to asynchronously send the HTTP response - to a HTTP request possibly containing a WebSocket Upgrade request. - The function call always returns immediately. The asynchronous - operation will continue until one of the following conditions is - true: - - @li A HTTP response finishes sending. - - @li An error occurs on the stream. - - This operation is implemented in terms of one or more calls to the - next layer's `async_write_some` functions, and is known as a - composed operation. The program must ensure that the - stream performs no other operations until this operation completes. - - If the passed HTTP request is a valid HTTP WebSocket Upgrade - request, a HTTP response is sent back indicating a successful - upgrade. When this asynchronous operation completes, the stream is - then ready to send and receive WebSocket protocol frames and messages. - - If the HTTP request is invalid or cannot be satisfied, a HTTP - response is sent indicating the reason and status code (typically - 400, "Bad Request"). This counts as a failure. - - @param request An object containing the HTTP Upgrade request. - Ownership is not transferred, the implementation will not access - this object from other threads. - - @param handler The handler to be called when the request completes. - Copies will be made of the handler as required. The equivalent - function signature of the handler must be: + @param handler The handler to be called when the request + completes. Copies will be made of the handler as required. The + equivalent function signature of the handler must be: @code void handler( - error_code const& error // result of operation + error_code const& ec // Result of operation ); @endcode Regardless of whether the asynchronous operation completes immediately or not, the handler will not be invoked from within this function. Invocation of the handler will be performed in a manner equivalent to using `boost::asio::io_service::post`. */ - template -#if GENERATING_DOCS + template +#if BEAST_DOXYGEN void_or_deduced #else - typename async_completion< - AcceptHandler, void(error_code)>::result_type + typename std::enable_if< + ! http::detail::is_header::value, + async_return_type>::type #endif - async_accept(http::request const& request, - AcceptHandler&& handler); + async_accept_ex(ConstBufferSequence const& buffers, + ResponseDecorator const& decorator, + AcceptHandler&& handler); - /** Send a HTTP WebSocket Upgrade request and receive the response. + /** Start responding to a WebSocket HTTP Upgrade request. + + This function is used to asynchronously send the HTTP response + to an HTTP request possibly containing a WebSocket Upgrade + request. The function call always returns immediately. The + asynchronous operation will continue until one of the following + conditions is true: + + @li The response finishes sending. + + @li An error occurs on the stream. + + This operation is implemented in terms of one or more calls to + the next layer's `async_write_some` functions, and is known as + a composed operation. The program must ensure that the + stream performs no other operations until this operation + completes. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When the completion handler is invoked, the stream is then + ready to send and receive WebSocket protocol frames and + messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure, and + the completion handler will be invoked with a suitable error + code set. + + @param req An object containing the HTTP Upgrade request. + Ownership is not transferred, the implementation will not access + this object from other threads. + + @param handler The handler to be called when the request + completes. Copies will be made of the handler as required. The + equivalent function signature of the handler must be: + @code void handler( + error_code const& ec // Result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using `boost::asio::io_service::post`. + */ + template +#if BEAST_DOXYGEN + void_or_deduced +#else + async_return_type< + AcceptHandler, void(error_code)> +#endif + async_accept(http::header> const& req, + AcceptHandler&& handler); + + /** Start responding to a WebSocket HTTP Upgrade request. + + This function is used to asynchronously send the HTTP response + to an HTTP request possibly containing a WebSocket Upgrade + request. The function call always returns immediately. The + asynchronous operation will continue until one of the following + conditions is true: + + @li The response finishes sending. + + @li An error occurs on the stream. + + This operation is implemented in terms of one or more calls to + the next layer's `async_write_some` functions, and is known as + a composed operation. The program must ensure that the + stream performs no other operations until this operation + completes. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When the completion handler is invoked, the stream is then + ready to send and receive WebSocket protocol frames and + messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure, and + the completion handler will be invoked with a suitable error + code set. + + @param req An object containing the HTTP Upgrade request. + Ownership is not transferred, the implementation will not access + this object from other threads. + + @param decorator A function object which will be called to modify + the HTTP response object delivered by the implementation. This + could be used to set the Server field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + response_type& res + ); @endcode + + @param handler The handler to be called when the request + completes. Copies will be made of the handler as required. The + equivalent function signature of the handler must be: + @code void handler( + error_code const& ec // Result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using `boost::asio::io_service::post`. + */ + template +#if BEAST_DOXYGEN + void_or_deduced +#else + async_return_type< + AcceptHandler, void(error_code)> +#endif + async_accept_ex(http::header> const& req, + ResponseDecorator const& decorator, + AcceptHandler&& handler); + + /** Start responding to a WebSocket HTTP Upgrade request. + + This function is used to asynchronously send the HTTP response + to an HTTP request possibly containing a WebSocket Upgrade + request. The function call always returns immediately. The + asynchronous operation will continue until one of the following + conditions is true: + + @li The response finishes sending. + + @li An error occurs on the stream. + + This operation is implemented in terms of one or more calls to + the next layer's `async_write_some` functions, and is known as + a composed operation. The program must ensure that the + stream performs no other operations until this operation + completes. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When the completion handler is invoked, the stream is then + ready to send and receive WebSocket protocol frames and + messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure, and + the completion handler will be invoked with a suitable error + code set. + + @param req An object containing the HTTP Upgrade request. + Ownership is not transferred, the implementation will not access + this object from other threads. + + @param buffers Caller provided data that has already been + received on the stream. This may be used for implementations + allowing multiple protocols on the same stream. The + buffered data will first be applied to the handshake, and + then to received WebSocket frames. The implementation will + copy the caller provided data before the function returns. + + @param handler The handler to be called when the request + completes. Copies will be made of the handler as required. The + equivalent function signature of the handler must be: + @code void handler( + error_code const& ec // Result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using `boost::asio::io_service::post`. + */ + template +#if BEAST_DOXYGEN + void_or_deduced +#else + async_return_type< + AcceptHandler, void(error_code)> +#endif + async_accept(http::header> const& req, + ConstBufferSequence const& buffers, + AcceptHandler&& handler); + + /** Start responding to a WebSocket HTTP Upgrade request. + + This function is used to asynchronously send the HTTP response + to an HTTP request possibly containing a WebSocket Upgrade + request. The function call always returns immediately. The + asynchronous operation will continue until one of the following + conditions is true: + + @li The response finishes sending. + + @li An error occurs on the stream. + + This operation is implemented in terms of one or more calls to + the next layer's `async_write_some` functions, and is known as + a composed operation. The program must ensure that the + stream performs no other operations until this operation + completes. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When the completion handler is invoked, the stream is then + ready to send and receive WebSocket protocol frames and + messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure, and + the completion handler will be invoked with a suitable error + code set. + + @param req An object containing the HTTP Upgrade request. + Ownership is not transferred, the implementation will not access + this object from other threads. + + @param buffers Caller provided data that has already been + received on the stream. This may be used for implementations + allowing multiple protocols on the same stream. The + buffered data will first be applied to the handshake, and + then to received WebSocket frames. The implementation will + copy the caller provided data before the function returns. + + @param decorator A function object which will be called to modify + the HTTP response object delivered by the implementation. This + could be used to set the Server field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + response_type& res + ); @endcode + + @param handler The handler to be called when the request + completes. Copies will be made of the handler as required. The + equivalent function signature of the handler must be: + @code void handler( + error_code const& ec // Result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using `boost::asio::io_service::post`. + */ + template +#if BEAST_DOXYGEN + void_or_deduced +#else + async_return_type< + AcceptHandler, void(error_code)> +#endif + async_accept_ex(http::header> const& req, + ConstBufferSequence const& buffers, + ResponseDecorator const& decorator, + AcceptHandler&& handler); + + /** Send an HTTP WebSocket Upgrade request and receive the response. This function is used to synchronously send the WebSocket upgrade HTTP request. The call blocks until one of the following conditions is true: - @li A HTTP request finishes sending and a HTTP response finishes - receiving. + @li The request is sent and the response is received. @li An error occurs on the stream @@ -706,18 +1847,18 @@ public: @param host The name of the remote host, required by the HTTP protocol. - @param resource The requesting URI, which may not be empty, + @param target The Request Target, which may not be empty, required by the HTTP protocol. @throws system_error Thrown on failure. @par Example @code - websocket::stream ws(io_service); + websocket::stream ws{io_service}; ... try { - ws.upgrade("localhost", "/"); + ws.handshake("localhost", "/"); } catch(...) { @@ -726,17 +1867,62 @@ public: @endcode */ void - handshake(boost::string_ref const& host, - boost::string_ref const& resource); + handshake(string_view host, string_view target); - /** Send a HTTP WebSocket Upgrade request and receive the response. + /** Send an HTTP WebSocket Upgrade request and receive the response. This function is used to synchronously send the WebSocket upgrade HTTP request. The call blocks until one of the following conditions is true: - @li A HTTP request finishes sending and a HTTP response finishes - receiving. + @li The request is sent and the response is received. + + @li An error occurs on the stream + + This function is implemented in terms of one or more calls to the + next layer's `read_some` and `write_some` functions. + + The operation is successful if the received HTTP response indicates + a successful HTTP Upgrade (represented by a Status-Code of 101, + "switching protocols"). + + @param res The HTTP Upgrade response returned by the remote + endpoint. + + @param host The name of the remote host, + required by the HTTP protocol. + + @param target The Request Target, which may not be empty, + required by the HTTP protocol. + + @throws system_error Thrown on failure. + + @par Example + @code + websocket::stream ws{io_service}; + ... + try + { + response_type res; + ws.handshake(res, "localhost", "/"); + } + catch(...) + { + // An error occurred. + } + @endcode + */ + void + handshake(response_type& res, + string_view host, string_view target); + + /** Send an HTTP WebSocket Upgrade request and receive the response. + + This function is used to synchronously send the WebSocket + upgrade HTTP request. The call blocks until one of the + following conditions is true: + + @li The request is sent and the response is received. @li An error occurs on the stream @@ -750,17 +1936,136 @@ public: @param host The name of the remote host, required by the HTTP protocol. - @param resource The requesting URI, which may not be empty, + @param target The Request Target, which may not be empty, + required by the HTTP protocol. + + @param decorator A function object which will be called to modify + the HTTP request object generated by the implementation. This + could be used to set the User-Agent field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + request_type& req + ); @endcode + + @throws system_error Thrown on failure. + + @par Example + @code + websocket::stream ws{io_service}; + ... + try + { + ws.handshake("localhost", "/", + [](request_type& req) + { + req.set(field::user_agent, "Beast"); + }); + } + catch(...) + { + // An error occurred. + } + @endcode + */ + template + void + handshake_ex(string_view host, string_view target, + RequestDecorator const& decorator); + + /** Send an HTTP WebSocket Upgrade request and receive the response. + + This function is used to synchronously send the WebSocket + upgrade HTTP request. The call blocks until one of the + following conditions is true: + + @li The request is sent and the response is received. + + @li An error occurs on the stream + + This function is implemented in terms of one or more calls to the + next layer's `read_some` and `write_some` functions. + + The operation is successful if the received HTTP response indicates + a successful HTTP Upgrade (represented by a Status-Code of 101, + "switching protocols"). + + @param res The HTTP Upgrade response returned by the remote + endpoint. + + @param host The name of the remote host, + required by the HTTP protocol. + + @param target The Request Target, which may not be empty, + required by the HTTP protocol. + + @param decorator A function object which will be called to modify + the HTTP request object generated by the implementation. This + could be used to set the User-Agent field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + request_type& req + ); @endcode + + @throws system_error Thrown on failure. + + @par Example + @code + websocket::stream ws{io_service}; + ... + try + { + response_type res; + ws.handshake(res, "localhost", "/", + [](request_type& req) + { + req.set(field::user_agent, "Beast"); + }); + } + catch(...) + { + // An error occurred. + } + @endcode + */ + template + void + handshake_ex(response_type& res, + string_view host, string_view target, + RequestDecorator const& decorator); + + /** Send an HTTP WebSocket Upgrade request and receive the response. + + This function is used to synchronously send the WebSocket + upgrade HTTP request. The call blocks until one of the + following conditions is true: + + @li The request is sent and the response is received. + + @li An error occurs on the stream + + This function is implemented in terms of one or more calls to the + next layer's `read_some` and `write_some` functions. + + The operation is successful if the received HTTP response indicates + a successful HTTP Upgrade (represented by a Status-Code of 101, + "switching protocols"). + + @param host The name of the remote host, + required by the HTTP protocol. + + @param target The Request Target, which may not be empty, required by the HTTP protocol. @param ec Set to indicate what error occurred, if any. @par Example @code - websocket::stream ws(io_service); + websocket::stream ws{io_service}; ... error_code ec; - ws.upgrade(host, resource, ec); + ws.handshake(host, target, ec); if(ec) { // An error occurred. @@ -768,8 +2073,171 @@ public: @endcode */ void - handshake(boost::string_ref const& host, - boost::string_ref const& resource, error_code& ec); + handshake(string_view host, + string_view target, error_code& ec); + + /** Send an HTTP WebSocket Upgrade request and receive the response. + + This function is used to synchronously send the WebSocket + upgrade HTTP request. The call blocks until one of the + following conditions is true: + + @li The request is sent and the response is received. + + @li An error occurs on the stream + + This function is implemented in terms of one or more calls to the + next layer's `read_some` and `write_some` functions. + + The operation is successful if the received HTTP response indicates + a successful HTTP Upgrade (represented by a Status-Code of 101, + "switching protocols"). + + @param host The name of the remote host, + required by the HTTP protocol. + + @param target The Request Target, which may not be empty, + required by the HTTP protocol. + + @param ec Set to indicate what error occurred, if any. + + @param res The HTTP Upgrade response returned by the remote + endpoint. If `ec is set, the return value is undefined. + + @par Example + @code + websocket::stream ws{io_service}; + ... + error_code ec; + response_type res; + ws.handshake(res, host, target, ec); + if(ec) + { + // An error occurred. + } + @endcode + */ + void + handshake(response_type& res, + string_view host, string_view target, error_code& ec); + + /** Send an HTTP WebSocket Upgrade request and receive the response. + + This function is used to synchronously send the WebSocket + upgrade HTTP request. The call blocks until one of the + following conditions is true: + + @li The request is sent and the response is received. + + @li An error occurs on the stream + + This function is implemented in terms of one or more calls to the + next layer's `read_some` and `write_some` functions. + + The operation is successful if the received HTTP response indicates + a successful HTTP Upgrade (represented by a Status-Code of 101, + "switching protocols"). + + @param host The name of the remote host, + required by the HTTP protocol. + + @param target The Request Target, which may not be empty, + required by the HTTP protocol. + + @param decorator A function object which will be called to modify + the HTTP request object generated by the implementation. This + could be used to set the User-Agent field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + request_type& req + ); @endcode + + @param ec Set to indicate what error occurred, if any. + + @par Example + @code + websocket::stream ws{io_service}; + ... + error_code ec; + ws.handshake("localhost", "/", + [](request_type& req) + { + req.set(field::user_agent, "Beast"); + }, + ec); + if(ec) + { + // An error occurred. + } + @endcode + */ + template + void + handshake_ex(string_view host, + string_view target, RequestDecorator const& decorator, + error_code& ec); + + /** Send an HTTP WebSocket Upgrade request and receive the response. + + This function is used to synchronously send the WebSocket + upgrade HTTP request. The call blocks until one of the + following conditions is true: + + @li The request is sent and the response is received. + + @li An error occurs on the stream + + This function is implemented in terms of one or more calls to the + next layer's `read_some` and `write_some` functions. + + The operation is successful if the received HTTP response indicates + a successful HTTP Upgrade (represented by a Status-Code of 101, + "switching protocols"). + + @param res The HTTP Upgrade response returned by the remote + endpoint. + + @param host The name of the remote host, + required by the HTTP protocol. + + @param target The Request Target, which may not be empty, + required by the HTTP protocol. + + @param decorator A function object which will be called to modify + the HTTP request object generated by the implementation. This + could be used to set the User-Agent field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + request_type& req + ); @endcode + + @param ec Set to indicate what error occurred, if any. + + @par Example + @code + websocket::stream ws{io_service}; + ... + error_code ec; + response_type res; + ws.handshake(res, "localhost", "/", + [](request_type& req) + { + req.set(field::user_agent, "Beast"); + }, + ec); + if(ec) + { + // An error occurred. + } + @endcode + */ + template + void + handshake_ex(response_type& res, + string_view host, string_view target, + RequestDecorator const& decorator, error_code& ec); /** Start an asynchronous operation to send an upgrade request and receive the response. @@ -779,10 +2247,9 @@ public: operation will continue until one of the following conditions is true: - @li A HTTP request finishes sending and a HTTP response finishes - receiving. + @li The request is sent and the response is received. - @li An error occurs on the stream. + @li An error occurs on the stream This operation is implemented in terms of one or more calls to the next layer's `async_read_some` and `async_write_some` functions, and @@ -797,15 +2264,15 @@ public: @param host The name of the remote host, required by the HTTP protocol. Copies may be made as needed. - @param resource The requesting URI, which may not be empty, - required by the HTTP protocol. Copies may be made as - needed. + @param target The Request Target, which may not be empty, + required by the HTTP protocol. Copies of this parameter may + be made as needed. - @param h The handler to be called when the request completes. + @param handler The handler to be called when the request completes. Copies will be made of the handler as required. The equivalent function signature of the handler must be: @code void handler( - error_code const& error // result of operation + error_code const& ec // Result of operation ); @endcode Regardless of whether the asynchronous operation completes immediately or not, the handler will not be invoked from within @@ -813,14 +2280,194 @@ public: manner equivalent to using `boost::asio::io_service::post`. */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN void_or_deduced #else - typename async_completion< - HandshakeHandler, void(error_code)>::result_type + async_return_type< + HandshakeHandler, void(error_code)> #endif - async_handshake(boost::string_ref const& host, - boost::string_ref const& resource, HandshakeHandler&& h); + async_handshake(string_view host, + string_view target, HandshakeHandler&& handler); + + /** Start an asynchronous operation to send an upgrade request and receive the response. + + This function is used to asynchronously send the HTTP WebSocket + upgrade request and receive the HTTP WebSocket Upgrade response. + This function call always returns immediately. The asynchronous + operation will continue until one of the following conditions is + true: + + @li The request is sent and the response is received. + + @li An error occurs on the stream + + This operation is implemented in terms of one or more calls to the + next layer's `async_read_some` and `async_write_some` functions, and + is known as a composed operation. The program must ensure + that the stream performs no other operations until this operation + completes. + + The operation is successful if the received HTTP response indicates + a successful HTTP Upgrade (represented by a Status-Code of 101, + "switching protocols"). + + @param res The HTTP Upgrade response returned by the remote + endpoint. The caller must ensure this object is valid for at + least until the completion handler is invoked. + + @param host The name of the remote host, required by + the HTTP protocol. Copies may be made as needed. + + @param target The Request Target, which may not be empty, + required by the HTTP protocol. Copies of this parameter may + be made as needed. + + @param handler The handler to be called when the request completes. + Copies will be made of the handler as required. The equivalent + function signature of the handler must be: + @code void handler( + error_code const& ec // Result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using `boost::asio::io_service::post`. + */ + template +#if BEAST_DOXYGEN + void_or_deduced +#else + async_return_type< + HandshakeHandler, void(error_code)> +#endif + async_handshake(response_type& res, + string_view host, string_view target, + HandshakeHandler&& handler); + + /** Start an asynchronous operation to send an upgrade request and receive the response. + + This function is used to asynchronously send the HTTP WebSocket + upgrade request and receive the HTTP WebSocket Upgrade response. + This function call always returns immediately. The asynchronous + operation will continue until one of the following conditions is + true: + + @li The request is sent and the response is received. + + @li An error occurs on the stream + + This operation is implemented in terms of one or more calls to the + next layer's `async_read_some` and `async_write_some` functions, and + is known as a composed operation. The program must ensure + that the stream performs no other operations until this operation + completes. + + The operation is successful if the received HTTP response indicates + a successful HTTP Upgrade (represented by a Status-Code of 101, + "switching protocols"). + + @param host The name of the remote host, required by + the HTTP protocol. Copies may be made as needed. + + @param target The Request Target, which may not be empty, + required by the HTTP protocol. Copies of this parameter may + be made as needed. + + @param decorator A function object which will be called to modify + the HTTP request object generated by the implementation. This + could be used to set the User-Agent field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + request_type& req + ); @endcode + + @param handler The handler to be called when the request completes. + Copies will be made of the handler as required. The equivalent + function signature of the handler must be: + @code void handler( + error_code const& ec // Result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using `boost::asio::io_service::post`. + */ + template +#if BEAST_DOXYGEN + void_or_deduced +#else + async_return_type< + HandshakeHandler, void(error_code)> +#endif + async_handshake_ex(string_view host, + string_view target, RequestDecorator const& decorator, + HandshakeHandler&& handler); + + /** Start an asynchronous operation to send an upgrade request and receive the response. + + This function is used to asynchronously send the HTTP WebSocket + upgrade request and receive the HTTP WebSocket Upgrade response. + This function call always returns immediately. The asynchronous + operation will continue until one of the following conditions is + true: + + @li The request is sent and the response is received. + + @li An error occurs on the stream + + This operation is implemented in terms of one or more calls to the + next layer's `async_read_some` and `async_write_some` functions, and + is known as a composed operation. The program must ensure + that the stream performs no other operations until this operation + completes. + + The operation is successful if the received HTTP response indicates + a successful HTTP Upgrade (represented by a Status-Code of 101, + "switching protocols"). + + @param res The HTTP Upgrade response returned by the remote + endpoint. The caller must ensure this object is valid for at + least until the completion handler is invoked. + + @param host The name of the remote host, required by + the HTTP protocol. Copies may be made as needed. + + @param target The Request Target, which may not be empty, + required by the HTTP protocol. Copies of this parameter may + be made as needed. + + @param decorator A function object which will be called to modify + the HTTP request object generated by the implementation. This + could be used to set the User-Agent field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + request_type& req + ); @endcode + + @param handler The handler to be called when the request completes. + Copies will be made of the handler as required. The equivalent + function signature of the handler must be: + @code void handler( + error_code const& ec // Result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using `boost::asio::io_service::post`. + */ + template +#if BEAST_DOXYGEN + void_or_deduced +#else + async_return_type< + HandshakeHandler, void(error_code)> +#endif + async_handshake_ex(response_type& res, + string_view host, string_view target, + RequestDecorator const& decorator, + HandshakeHandler&& handler); /** Send a WebSocket close frame. @@ -915,7 +2562,7 @@ public: function signature of the handler must be: @code void handler( - error_code const& error // Result of operation + error_code const& ec // Result of operation ); @endcode Regardless of whether the asynchronous operation completes @@ -924,11 +2571,11 @@ public: manner equivalent to using `boost::asio::io_service::post`. */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN void_or_deduced #else - typename async_completion< - CloseHandler, void(error_code)>::result_type + async_return_type< + CloseHandler, void(error_code)> #endif async_close(close_reason const& cr, CloseHandler&& handler); @@ -997,7 +2644,7 @@ public: function signature of the handler must be: @code void handler( - error_code const& error // Result of operation + error_code const& ec // Result of operation ); @endcode Regardless of whether the asynchronous operation completes @@ -1006,11 +2653,11 @@ public: manner equivalent to using `boost::asio::io_service::post`. */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN void_or_deduced #else - typename async_completion< - WriteHandler, void(error_code)>::result_type + async_return_type< + WriteHandler, void(error_code)> #endif async_ping(ping_data const& payload, WriteHandler&& handler); @@ -1094,7 +2741,7 @@ public: function signature of the handler must be: @code void handler( - error_code const& error // Result of operation + error_code const& ec // Result of operation ); @endcode Regardless of whether the asynchronous operation completes @@ -1103,11 +2750,11 @@ public: manner equivalent to using `boost::asio::io_service::post`. */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN void_or_deduced #else - typename async_completion< - WriteHandler, void(error_code)>::result_type + async_return_type< + WriteHandler, void(error_code)> #endif async_pong(ping_data const& payload, WriteHandler&& handler); @@ -1123,33 +2770,32 @@ public: This call is implemented in terms of one or more calls to the stream's `read_some` and `write_some` operations. - Upon a success, op is set to either binary or text depending on - the message type, and the input area of the stream buffer will - hold all the message payload bytes (which may be zero in length). + Upon a success, the input area of the stream buffer will + hold the received message payload bytes (which may be zero + in length). The functions @ref got_binary and @ref got_text + may be used to query the stream and determine the type + of the last received message. During reads, the implementation handles control frames as follows: @li A pong frame is sent when a ping frame is received. - @li The @ref ping_callback is invoked when a ping frame + @li The @ref control_callback is invoked when a ping frame or pong frame is received. @li The WebSocket close procedure is started if a close frame is received. In this case, the operation will eventually complete with the error set to @ref error::closed. - @param op A value to receive the message type. - This object must remain valid until the handler is called. - - @param dynabuf A dynamic buffer to hold the message data after + @param buffer A dynamic buffer to hold the message data after any masking or decompression has been applied. @throws system_error Thrown on failure. */ template void - read(opcode& op, DynamicBuffer& dynabuf); + read(DynamicBuffer& buffer); /** Read a message from the stream. @@ -1163,14 +2809,16 @@ public: This call is implemented in terms of one or more calls to the stream's `read_some` and `write_some` operations. - Upon a success, op is set to either binary or text depending on - the message type, and the input area of the stream buffer will - hold all the message payload bytes (which may be zero in length). + Upon a success, the input area of the stream buffer will + hold the received message payload bytes (which may be zero + in length). The functions @ref got_binary and @ref got_text + may be used to query the stream and determine the type + of the last received message. During reads, the implementation handles control frames as follows: - @li The @ref ping_callback is invoked when a ping frame + @li The @ref control_callback is invoked when a ping frame or pong frame is received. @li A pong frame is sent when a ping frame is received. @@ -1179,17 +2827,14 @@ public: is received. In this case, the operation will eventually complete with the error set to @ref error::closed. - @param op A value to receive the message type. - This object must remain valid until the handler is called. - - @param dynabuf A dynamic buffer to hold the message data after + @param buffer A dynamic buffer to hold the message data after any masking or decompression has been applied. @param ec Set to indicate what error occurred, if any. */ template void - read(opcode& op, DynamicBuffer& dynabuf, error_code& ec); + read(DynamicBuffer& buffer, error_code& ec); /** Start an asynchronous operation to read a message from the stream. @@ -1208,14 +2853,16 @@ public: ensure that the stream performs no other reads until this operation completes. - Upon a success, op is set to either binary or text depending on - the message type, and the input area of the stream buffer will - hold all the message payload bytes (which may be zero in length). + Upon a success, the input area of the stream buffer will + hold the received message payload bytes (which may be zero + in length). The functions @ref got_binary and @ref got_text + may be used to query the stream and determine the type + of the last received message. During reads, the implementation handles control frames as follows: - @li The @ref ping_callback is invoked when a ping frame + @li The @ref control_callback is invoked when a ping frame or pong frame is received. @li A pong frame is sent when a ping frame is received. @@ -1230,10 +2877,7 @@ public: read and asynchronous write operation pending simultaneously (a user initiated call to @ref async_close counts as a write). - @param op A value to receive the message type. - This object must remain valid until the handler is called. - - @param dynabuf A dynamic buffer to hold the message data after + @param buffer A dynamic buffer to hold the message data after any masking or decompression has been applied. This object must remain valid until the handler is called. @@ -1242,7 +2886,7 @@ public: function signature of the handler must be: @code void handler( - error_code const& error // Result of operation + error_code const& ec // Result of operation ); @endcode Regardless of whether the asynchronous operation completes @@ -1251,13 +2895,13 @@ public: manner equivalent to using `boost::asio::io_service::post`. */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN void_or_deduced #else - typename async_completion< - ReadHandler, void(error_code)>::result_type + async_return_type< + ReadHandler, void(error_code)> #endif - async_read(opcode& op, DynamicBuffer& dynabuf, ReadHandler&& handler); + async_read(DynamicBuffer& buffer, ReadHandler&& handler); /** Read a message frame from the stream. @@ -1272,17 +2916,10 @@ public: This call is implemented in terms of one or more calls to the stream's `read_some` and `write_some` operations. - Upon success, `fi` is filled out to reflect the message payload - contents. `op` is set to binary or text, and the `fin` flag - indicates if all the message data has been read in. To read the - entire message, callers should keep calling @ref read_frame - until `fi.fin == true`. A message with no payload will have - `fi.fin == true`, and zero bytes placed into the stream buffer. - During reads, the implementation handles control frames as follows: - @li The @ref ping_callback is invoked when a ping frame + @li The @ref control_callback is invoked when a ping frame or pong frame is received. @li A pong frame is sent when a ping frame is received. @@ -1291,16 +2928,16 @@ public: is received. In this case, the operation will eventually complete with the error set to @ref error::closed. - @param fi An object to store metadata about the message. - - @param dynabuf A dynamic buffer to hold the message data after + @param buffer A dynamic buffer to hold the message data after any masking or decompression has been applied. + @return `true` if this is the last frame of the message. + @throws system_error Thrown on failure. */ template - void - read_frame(frame_info& fi, DynamicBuffer& dynabuf); + bool + read_frame(DynamicBuffer& buffer); /** Read a message frame from the stream. @@ -1315,17 +2952,10 @@ public: This call is implemented in terms of one or more calls to the stream's `read_some` and `write_some` operations. - Upon success, `fi` is filled out to reflect the message payload - contents. `op` is set to binary or text, and the `fin` flag - indicates if all the message data has been read in. To read the - entire message, callers should keep calling @ref read_frame - until `fi.fin == true`. A message with no payload will have - `fi.fin == true`, and zero bytes placed into the stream buffer. - During reads, the implementation handles control frames as follows: - @li The @ref ping_callback is invoked when a ping frame + @li The @ref control_callback is invoked when a ping frame or pong frame is received. @li A pong frame is sent when a ping frame is received. @@ -1334,16 +2964,16 @@ public: is received. In this case, the operation will eventually complete with the error set to @ref error::closed. - @param fi An object to store metadata about the message. - - @param dynabuf A dynamic buffer to hold the message data after + @param buffer A dynamic buffer to hold the message data after any masking or decompression has been applied. @param ec Set to indicate what error occurred, if any. + + @return `true` if this is the last frame of the message. */ template - void - read_frame(frame_info& fi, DynamicBuffer& dynabuf, error_code& ec); + bool + read_frame(DynamicBuffer& buffer, error_code& ec); /** Start an asynchronous operation to read a message frame from the stream. @@ -1362,18 +2992,10 @@ public: ensure that the stream performs no other reads until this operation completes. - Upon a successful completion, `fi` is filled out to reflect the - message payload contents. `op` is set to binary or text, and the - `fin` flag indicates if all the message data has been read in. - To read the entire message, callers should keep calling - @ref read_frame until `fi.fin == true`. A message with no payload - will have `fi.fin == true`, and zero bytes placed into the stream - buffer. - During reads, the implementation handles control frames as follows: - @li The @ref ping_callback is invoked when a ping frame + @li The @ref control_callback is invoked when a ping frame or pong frame is received. @li A pong frame is sent when a ping frame is received. @@ -1388,10 +3010,7 @@ public: read and asynchronous write operation pending simultaneously (a user initiated call to @ref async_close counts as a write). - @param fi An object to store metadata about the message. - This object must remain valid until the handler is called. - - @param dynabuf A dynamic buffer to hold the message data after + @param buffer A dynamic buffer to hold the message data after any masking or decompression has been applied. This object must remain valid until the handler is called. @@ -1400,7 +3019,8 @@ public: function signature of the handler must be: @code void handler( - error_code const& error // Result of operation + error_code const& ec, // Result of operation + bool fin // `true` if this is the last frame ); @endcode Regardless of whether the asynchronous operation completes @@ -1409,14 +3029,12 @@ public: manner equivalent to using boost::asio::io_service::post(). */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN void_or_deduced #else - typename async_completion< - ReadHandler, void(error_code)>::result_type + async_return_type #endif - async_read_frame(frame_info& fi, - DynamicBuffer& dynabuf, ReadHandler&& handler); + async_read_frame(DynamicBuffer& buffer, ReadHandler&& handler); /** Write a message to the stream. @@ -1431,7 +3049,7 @@ public: This operation is implemented in terms of one or more calls to the next layer's `write_some` function. - The current setting of the @ref message_type option controls + The current setting of the @ref binary option controls whether the message opcode is set to text or binary. If the @ref auto_fragment option is set, the message will be split into one or more frames as necessary. The actual payload contents @@ -1466,7 +3084,7 @@ public: This operation is implemented in terms of one or more calls to the next layer's `write_some` function. - The current setting of the @ref message_type option controls + The current setting of the @ref binary option controls whether the message opcode is set to text or binary. If the @ref auto_fragment option is set, the message will be split into one or more frames as necessary. The actual payload contents @@ -1508,7 +3126,7 @@ public: stream::async_write, stream::async_write_frame, or stream::async_close). - The current setting of the @ref message_type option controls + The current setting of the @ref binary option controls whether the message opcode is set to text or binary. If the @ref auto_fragment option is set, the message will be split into one or more frames as necessary. The actual payload contents @@ -1526,7 +3144,7 @@ public: function signature of the handler must be: @code void handler( - error_code const& error // Result of operation + error_code const& ec // Result of operation ); @endcode Regardless of whether the asynchronous operation completes @@ -1535,11 +3153,11 @@ public: manner equivalent to using `boost::asio::io_service::post`. */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN void_or_deduced #else - typename async_completion< - WriteHandler, void(error_code)>::result_type + async_return_type< + WriteHandler, void(error_code)> #endif async_write(ConstBufferSequence const& buffers, WriteHandler&& handler); @@ -1561,8 +3179,8 @@ public: If this is the beginning of a new message, the message opcode will be set to text or binary as per the current setting of - the @ref message_type option. The actual payload sent - may be transformed as per the WebSocket protocol settings. + the @ref binary option. The actual payload sent may be + transformed as per the WebSocket protocol settings. @param fin `true` if this is the last frame in the message. @@ -1593,8 +3211,8 @@ public: If this is the beginning of a new message, the message opcode will be set to text or binary as per the current setting of - the @ref message_type option. The actual payload sent - may be transformed as per the WebSocket protocol settings. + the @ref binary option. The actual payload sent may be + transformed as per the WebSocket protocol settings. @param fin `true` if this is the last frame in the message. @@ -1630,8 +3248,8 @@ public: If this is the beginning of a new message, the message opcode will be set to text or binary as per the current setting of - the @ref message_type option. The actual payload sent - may be transformed as per the WebSocket protocol settings. + the @ref binary option. The actual payload sent may be + transformed as per the WebSocket protocol settings. @param fin A bool indicating whether or not the frame is the last frame in the corresponding WebSockets message. @@ -1647,21 +3265,21 @@ public: Copies will be made of the handler as required. The equivalent function signature of the handler must be: @code void handler( - error_code const& error // result of operation + error_code const& ec // Result of operation ); @endcode */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN void_or_deduced #else - typename async_completion< - WriteHandler, void(error_code)>::result_type + async_return_type< + WriteHandler, void(error_code)> #endif async_write_frame(bool fin, ConstBufferSequence const& buffers, WriteHandler&& handler); private: - template class accept_op; + template class accept_op; template class close_op; template class handshake_op; template class ping_op; @@ -1671,22 +3289,56 @@ private: template class read_op; template class read_frame_op; + static + void + default_decorate_req(request_type&) + { + } + + static + void + default_decorate_res(response_type&) + { + } + void reset(); - http::request - build_request(boost::string_ref const& host, - boost::string_ref const& resource, - std::string& key); - - template - http::response - build_response(http::request const& req); - - template + template void - do_response(http::response const& resp, - boost::string_ref const& key, error_code& ec); + do_accept(Decorator const& decorator, + error_code& ec); + + template + void + do_accept(http::header> const& req, + Decorator const& decorator, error_code& ec); + + template + void + do_handshake(response_type* res_p, + string_view host, + string_view target, + RequestDecorator const& decorator, + error_code& ec); + + template + request_type + build_request(detail::sec_ws_key_type& key, + string_view host, + string_view target, + Decorator const& decorator); + + template + response_type + build_response(http::header> const& req, + Decorator const& decorator); + + void + do_response(http::header const& resp, + detail::sec_ws_key_type const& key, error_code& ec); }; } // websocket diff --git a/src/beast/include/beast/zlib.hpp b/src/beast/include/beast/zlib.hpp index 16bbb78fcc..8fb12d5705 100644 --- a/src/beast/include/beast/zlib.hpp +++ b/src/beast/include/beast/zlib.hpp @@ -11,6 +11,8 @@ #include #include +#include #include +#include #endif diff --git a/src/beast/include/beast/zlib/deflate_stream.hpp b/src/beast/include/beast/zlib/deflate_stream.hpp index aae355d06a..b94e9e3539 100644 --- a/src/beast/include/beast/zlib/deflate_stream.hpp +++ b/src/beast/include/beast/zlib/deflate_stream.hpp @@ -4,6 +4,23 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // + +#ifndef BEAST_ZLIB_DEFLATE_STREAM_HPP +#define BEAST_ZLIB_DEFLATE_STREAM_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace zlib { + // This is a derivative work based on Zlib, copyright below: /* Copyright (C) 1995-2013 Jean-loup Gailly and Mark Adler @@ -32,22 +49,6 @@ (zlib format), rfc1951 (deflate format) and rfc1952 (gzip format). */ -#ifndef BEAST_ZLIB_DEFLATE_STREAM_HPP -#define BEAST_ZLIB_DEFLATE_STREAM_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace zlib { - /** Raw deflate compressor. This is a port of zlib's "deflate" functionality to C++. @@ -136,7 +137,7 @@ public: of bytes needed to store the result of compressing a block of data based on the current compression level and strategy. - @param bytes The size of the uncompressed data. + @param sourceLen The size of the uncompressed data. @return The maximum number of resulting compressed bytes. */ diff --git a/src/beast/include/beast/zlib/detail/deflate_stream.hpp b/src/beast/include/beast/zlib/detail/deflate_stream.hpp index 50358076cd..b51baff1b0 100644 --- a/src/beast/include/beast/zlib/detail/deflate_stream.hpp +++ b/src/beast/include/beast/zlib/detail/deflate_stream.hpp @@ -39,7 +39,10 @@ #include #include #include +#include +#include #include +#include #include #include #include @@ -132,7 +135,7 @@ protected: static std::uint16_t constexpr maxMatch = 258; // Can't change minMatch without also changing code, see original zlib - static_assert(minMatch==3, ""); + BOOST_STATIC_ASSERT(minMatch == 3); // end of block literal code static std::uint16_t constexpr END_BLOCK = 256; @@ -645,9 +648,10 @@ protected: init(); } + template static - unsigned - bi_reverse(unsigned code, int len); + Unsigned + bi_reverse(Unsigned code, unsigned len); template static @@ -665,7 +669,7 @@ protected: template std::size_t doUpperBound (std::size_t sourceLen) const; template void doTune (int good_length, int max_lazy, int nice_length, int max_chain); template void doParams (z_params& zs, int level, Strategy strategy, error_code& ec); - template void doWrite (z_params& zs, Flush flush, error_code& ec); + template void doWrite (z_params& zs, boost::optional flush, error_code& ec); template void doDictionary (Byte const* dict, uInt dictLength, error_code& ec); template void doPrime (int bits, int value, error_code& ec); template void doPending (unsigned* value, int* bits); @@ -741,12 +745,15 @@ protected: //-------------------------------------------------------------------------- // Reverse the first len bits of a code +template inline -unsigned +Unsigned deflate_stream:: -bi_reverse(unsigned code, int len) +bi_reverse(Unsigned code, unsigned len) { - unsigned res = 0; + BOOST_STATIC_ASSERT(std::is_unsigned::value); + BOOST_ASSERT(len <= 8 * sizeof(unsigned)); + Unsigned res = 0; do { res |= code & 1; @@ -807,7 +814,7 @@ deflate_stream::get_lut() -> //std::uint16_t bl_count[maxBits+1]; // Initialize the mapping length (0..255) -> length code (0..28) - int length = 0; + std::uint8_t length = 0; for(std::uint8_t code = 0; code < lengthCodes-1; ++code) { tables.base_length[code] = length; @@ -815,7 +822,7 @@ deflate_stream::get_lut() -> for(unsigned n = 0; n < run; ++n) tables.length_code[length++] = code; } - BOOST_ASSERT(length == 256); + BOOST_ASSERT(length == 0); // Note that the length 255 (match length 258) can be represented // in two different ways: code 284 + 5 bits or code 285, so we // overwrite length_code[255] to use the best encoding: @@ -894,19 +901,17 @@ doReset( if(windowBits == 8) windowBits = 9; - using beast::detail::make_exception; - if(level < 0 || level > 9) - throw make_exception( - "invalid level", __FILE__, __LINE__); + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "invalid level"}); if(windowBits < 8 || windowBits > 15) - throw make_exception( - "invalid windowBits", __FILE__, __LINE__); + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "invalid windowBits"}); if(memLevel < 1 || memLevel > MAX_MEM_LEVEL) - throw make_exception( - "invalid memLevel", __FILE__, __LINE__); + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "invalid memLevel"}); w_bits_ = windowBits; @@ -998,7 +1003,7 @@ doParams(z_params& zs, int level, Strategy strategy, error_code& ec) // Flush the last buffer: doWrite(zs, Flush::block, ec); if(ec == error::need_buffers && pending_ == 0) - ec = {}; + ec.assign(0, ec.category()); } if(level_ != level) { @@ -1011,10 +1016,14 @@ doParams(z_params& zs, int level, Strategy strategy, error_code& ec) strategy_ = strategy; } +// VFALCO boost::optional param is a workaround for +// gcc "maybe uninitialized" warning +// https://github.com/vinniefalco/Beast/issues/532 +// template void deflate_stream:: -doWrite(z_params& zs, Flush flush, error_code& ec) +doWrite(z_params& zs, boost::optional flush, error_code& ec) { maybe_init(); @@ -1050,8 +1059,9 @@ doWrite(z_params& zs, Flush flush, error_code& ec) return; } } - else if(zs.avail_in == 0 && flush <= old_flush && - flush != Flush::finish) + else if(zs.avail_in == 0 && ( + old_flush && flush <= *old_flush + ) && flush != Flush::finish) { /* Make sure there is something to do and avoid duplicate consecutive * flushes. For repeated and useless calls with Flush::finish, we keep @@ -1078,14 +1088,14 @@ doWrite(z_params& zs, Flush flush, error_code& ec) switch(strategy_) { case Strategy::huffman: - bstate = deflate_huff(zs, flush); + bstate = deflate_huff(zs, flush.get()); break; case Strategy::rle: - bstate = deflate_rle(zs, flush); + bstate = deflate_rle(zs, flush.get()); break; default: { - bstate = (this->*(get_config(level_).func))(zs, flush); + bstate = (this->*(get_config(level_).func))(zs, flush.get()); break; } } @@ -1273,7 +1283,8 @@ init() if(! buf_ || buf_size_ != needed) { - buf_.reset(new std::uint8_t[needed]); + buf_ = boost::make_unique_noinit< + std::uint8_t[]>(needed); buf_size_ = needed; } @@ -1436,7 +1447,7 @@ gen_bitlen(tree_desc *desc) std::uint16_t f; // frequency int overflow = 0; // number of elements with bit length too large - std::fill(&bl_count_[0], &bl_count_[maxBits+1], 0); + std::fill(&bl_count_[0], &bl_count_[maxBits+1], std::uint16_t{0}); /* In a first pass, compute the optimal bit lengths (which may * overflow in the case of the bit length tree). @@ -1615,7 +1626,7 @@ scan_tree( int prevlen = -1; // last emitted length int curlen; // length of current code int nextlen = tree[0].dl; // length of next code - int count = 0; // repeat count of the current code + std::uint16_t count = 0; // repeat count of the current code int max_count = 7; // max repeat count int min_count = 4; // min repeat count @@ -2283,18 +2294,18 @@ fill_window(z_params& zs) if(high_water_ < window_size_) { std::uint32_t curr = strstart_ + (std::uint32_t)(lookahead_); - std::uint32_t init; + std::uint32_t winit; if(high_water_ < curr) { /* Previous high water mark below current data -- zero kWinInit bytes or up to end of window, whichever is less. */ - init = window_size_ - curr; - if(init > kWinInit) - init = kWinInit; - std::memset(window_ + curr, 0, (unsigned)init); - high_water_ = curr + init; + winit = window_size_ - curr; + if(winit > kWinInit) + winit = kWinInit; + std::memset(window_ + curr, 0, (unsigned)winit); + high_water_ = curr + winit; } else if(high_water_ < (std::uint32_t)curr + kWinInit) { @@ -2302,11 +2313,11 @@ fill_window(z_params& zs) plus kWinInit -- zero out to current data plus kWinInit, or up to end of window, whichever is less. */ - init = (std::uint32_t)curr + kWinInit - high_water_; - if(init > window_size_ - high_water_) - init = window_size_ - high_water_; - std::memset(window_ + high_water_, 0, (unsigned)init); - high_water_ += init; + winit = (std::uint32_t)curr + kWinInit - high_water_; + if(winit > window_size_ - high_water_) + winit = window_size_ - high_water_; + std::memset(window_ + high_water_, 0, (unsigned)winit); + high_water_ += winit; } } } @@ -2626,8 +2637,8 @@ f_fast(z_params& zs, Flush flush) -> } if(match_length_ >= minMatch) { - tr_tally_dist(strstart_ - match_start_, - match_length_ - minMatch, bflush); + tr_tally_dist(static_cast(strstart_ - match_start_), + static_cast(match_length_ - minMatch), bflush); lookahead_ -= match_length_; @@ -2762,8 +2773,9 @@ f_slow(z_params& zs, Flush flush) -> /* Do not insert strings in hash table beyond this. */ uInt max_insert = strstart_ + lookahead_ - minMatch; - tr_tally_dist(strstart_ -1 - prev_match_, - prev_length_ - minMatch, bflush); + tr_tally_dist( + static_cast(strstart_ -1 - prev_match_), + static_cast(prev_length_ - minMatch), bflush); /* Insert in hash table all strings up to the end of the match. * strstart-1 and strstart are already inserted. If there is not @@ -2887,7 +2899,9 @@ f_rle(z_params& zs, Flush flush) -> /* Emit match if have run of minMatch or longer, else emit literal */ if(match_length_ >= minMatch) { - tr_tally_dist(1, match_length_ - minMatch, bflush); + tr_tally_dist(std::uint16_t{1}, + static_cast(match_length_ - minMatch), + bflush); lookahead_ -= match_length_; strstart_ += match_length_; diff --git a/src/beast/include/beast/zlib/detail/inflate_stream.hpp b/src/beast/include/beast/zlib/detail/inflate_stream.hpp index fb2d4ce074..3337278679 100644 --- a/src/beast/include/beast/zlib/detail/inflate_stream.hpp +++ b/src/beast/include/beast/zlib/detail/inflate_stream.hpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -233,8 +234,8 @@ inflate_stream:: doReset(int windowBits) { if(windowBits < 8 || windowBits > 15) - throw beast::detail::make_exception( - "windowBits out of range", __FILE__, __LINE__); + BOOST_THROW_EXCEPTION(std::domain_error{ + "windowBits out of range"}); w_.reset(windowBits); bi_.flush(); @@ -708,8 +709,8 @@ doWrite(z_params& zs, Flush flush, error_code& ec) case SYNC: default: - throw beast::detail::make_exception( - "stream error", __FILE__, __LINE__); + BOOST_THROW_EXCEPTION(std::logic_error{ + "stream error"}); } } } @@ -935,8 +936,8 @@ inflate_table( auto const not_enough = [] { - throw beast::detail::make_exception( - "insufficient output size when inflating tables", __FILE__, __LINE__); + BOOST_THROW_EXCEPTION(std::logic_error{ + "insufficient output size when inflating tables"}); }; // check available table space @@ -1066,10 +1067,10 @@ get_fixed_tables() -> std::uint16_t lens[320]; std::uint16_t work[288]; - std::fill(&lens[ 0], &lens[144], 8); - std::fill(&lens[144], &lens[256], 9); - std::fill(&lens[256], &lens[280], 7); - std::fill(&lens[280], &lens[288], 8); + std::fill(&lens[ 0], &lens[144], std::uint16_t{8}); + std::fill(&lens[144], &lens[256], std::uint16_t{9}); + std::fill(&lens[256], &lens[280], std::uint16_t{7}); + std::fill(&lens[280], &lens[288], std::uint16_t{8}); { error_code ec; @@ -1077,7 +1078,7 @@ get_fixed_tables() -> inflate_table(build::lens, lens, 288, &next, &lenbits, work, ec); if(ec) - throw std::logic_error{ec.message()}; + BOOST_THROW_EXCEPTION(std::logic_error{ec.message()}); } // VFALCO These fixups are from ZLib @@ -1089,11 +1090,11 @@ get_fixed_tables() -> { error_code ec; auto next = &dist_[0]; - std::fill(&lens[0], &lens[32], 5); + std::fill(&lens[0], &lens[32], std::uint16_t{5}); inflate_table(build::dists, lens, 32, &next, &distbits, work, ec); if(ec) - throw std::logic_error{ec.message()}; + BOOST_THROW_EXCEPTION(std::logic_error{ec.message()}); } } }; diff --git a/src/beast/include/beast/zlib/detail/window.hpp b/src/beast/include/beast/zlib/detail/window.hpp index 2a277710b8..a671b2342f 100644 --- a/src/beast/include/beast/zlib/detail/window.hpp +++ b/src/beast/include/beast/zlib/detail/window.hpp @@ -36,6 +36,7 @@ #define BEAST_ZLIB_DETAIL_WINDOW_HPP #include +#include #include #include #include @@ -126,7 +127,8 @@ window:: write(std::uint8_t const* in, std::size_t n) { if(! p_) - p_.reset(new std::uint8_t[capacity_]); + p_ = boost::make_unique< + std::uint8_t[]>(capacity_); if(n >= capacity_) { i_ = 0; diff --git a/src/beast/include/beast/zlib/error.hpp b/src/beast/include/beast/zlib/error.hpp index d593917baa..7ab179536d 100644 --- a/src/beast/include/beast/zlib/error.hpp +++ b/src/beast/include/beast/zlib/error.hpp @@ -4,6 +4,16 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // + +#ifndef BEAST_ZLIB_ERROR_HPP +#define BEAST_ZLIB_ERROR_HPP + +#include +#include + +namespace beast { +namespace zlib { + // This is a derivative work based on Zlib, copyright below: /* Copyright (C) 1995-2013 Jean-loup Gailly and Mark Adler @@ -32,15 +42,6 @@ (zlib format), rfc1951 (deflate format) and rfc1952 (gzip format). */ -#ifndef BEAST_ZLIB_ERROR_HPP -#define BEAST_ZLIB_ERROR_HPP - -#include -#include - -namespace beast { -namespace zlib { - /** Error codes returned by the codec. */ enum class error diff --git a/src/beast/include/beast/zlib/impl/error.ipp b/src/beast/include/beast/zlib/impl/error.ipp index f5cf12082d..50b28423d2 100644 --- a/src/beast/include/beast/zlib/impl/error.ipp +++ b/src/beast/include/beast/zlib/impl/error.ipp @@ -58,7 +58,7 @@ public: const char* name() const noexcept override { - return "zlib"; + return "beast.zlib"; } std::string @@ -85,7 +85,7 @@ public: case error::general: default: - return "zlib error"; + return "beast.zlib error"; } } diff --git a/src/beast/include/beast/zlib/inflate_stream.hpp b/src/beast/include/beast/zlib/inflate_stream.hpp index e6c42021e6..dd4df6bb7e 100644 --- a/src/beast/include/beast/zlib/inflate_stream.hpp +++ b/src/beast/include/beast/zlib/inflate_stream.hpp @@ -4,6 +4,13 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // + +#ifndef BEAST_ZLIB_INFLATE_STREAM_HPP +#define BEAST_ZLIB_INFLATE_STREAM_HPP + +#include +#include + // This is a derivative work based on Zlib, copyright below: /* Copyright (C) 1995-2013 Jean-loup Gailly and Mark Adler @@ -32,12 +39,6 @@ (zlib format), rfc1951 (deflate format) and rfc1952 (gzip format). */ -#ifndef BEAST_ZLIB_INFLATE_STREAM_HPP -#define BEAST_ZLIB_INFLATE_STREAM_HPP - -#include -#include - namespace beast { namespace zlib { @@ -181,7 +182,7 @@ public: `Flush::trees` is used, and when `write` avoids the allocation of memory for a sliding window when `Flush::finsih` is used. - If a preset dictionary is needed after this call (see @ref dictionary below), + If a preset dictionary is needed after this call, `write` sets `zs.adler` to the Adler-32 checksum of the dictionary chosen by the compressor and returns `error::need_dictionary`; otherwise it sets `zs.adler` to the Adler-32 checksum of all output produced so far (that is, diff --git a/src/beast/include/beast/zlib/zlib.hpp b/src/beast/include/beast/zlib/zlib.hpp index b06f679e19..217bb191ce 100644 --- a/src/beast/include/beast/zlib/zlib.hpp +++ b/src/beast/include/beast/zlib/zlib.hpp @@ -4,6 +4,14 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // + +#ifndef BEAST_ZLIB_ZLIB_HPP +#define BEAST_ZLIB_ZLIB_HPP + +#include +#include +#include + // This is a derivative work based on Zlib, copyright below: /* Copyright (C) 1995-2013 Jean-loup Gailly and Mark Adler @@ -32,13 +40,6 @@ (zlib format), rfc1951 (deflate format) and rfc1952 (gzip format). */ -#ifndef BEAST_ZLIB_ZLIB_HPP -#define BEAST_ZLIB_ZLIB_HPP - -#include -#include -#include - namespace beast { namespace zlib { diff --git a/src/beast/scripts/blacklist.supp b/src/beast/scripts/blacklist.supp index 08968f04de..09bc1abed2 100644 --- a/src/beast/scripts/blacklist.supp +++ b/src/beast/scripts/blacklist.supp @@ -14,26 +14,16 @@ # Try if at all possible to filter only functions using fun:regex # Remember you must use mangled symbol names with fun:regex - -#### Compile time filters for ubsan #### +# boost/lexical_cast.hpp:1625:43: runtime error: downcast of address 0x7fbb4fffbce8 which does not point to an object of type 'buffer_t' (aka 'parser_buf >, char>') +# Fixed in Boost 1.63.0 https://svn.boost.org/trac/boost/ticket/12889 +# +fun:*shl_input_streamable* ## The well known ubsan failure in libstdc++ extant for years :) # Line 96:24: runtime error: load of value 4294967221, which is not a valid value for type 'std::_Ios_Fmtflags' -fun:*_Ios_Fmtflags* +# +#fun:*_Ios_Fmtflags* # boost/any.hpp:259:16: runtime error: downcast of address 0x000004392e70 which does not point to an object of type 'any::holder' -fun:*any_cast* - -# boost/lexical_cast.hpp:1625:43: runtime error: downcast of address 0x7fbb4fffbce8 which does not point to an object of type 'buffer_t' (aka 'parser_buf >, char>') -fun:*shl_input_streamable* - - - - -#### Compile time filters for asan #### - - -#### Compile time filters for msan #### - - -#### Compile time filters for tsan #### +# +#fun:*any_cast* diff --git a/src/beast/scripts/build-and-test.sh b/src/beast/scripts/build-and-test.sh index e62e93aa8f..c09f13d037 100755 --- a/src/beast/scripts/build-and-test.sh +++ b/src/beast/scripts/build-and-test.sh @@ -9,6 +9,8 @@ shopt -s globstar ################################## ENVIRONMENT ################################# +DO_VALGRIND=${DO_VALGRIND:-false} + # If not CI, then set some defaults if [[ "${CI:-}" == "" ]]; then TRAVIS_BRANCH=${TRAVIS_BRANCH:-feature} @@ -43,6 +45,9 @@ elif [[ $(uname -s) == "Linux" ]]; then if [[ "${TRAVIS}" == "true" && ${NUM_PROCESSORS:=2} > ${num_jobs} ]]; then num_jobs=$NUM_PROCESSORS fi + #if [[ "$TRAVIS" == "true" ]] && (( "$num_jobs" > 1)); then + # num_jobs=$((num_jobs - 1)) + #fi fi echo "using toolset: $CC" @@ -137,7 +142,7 @@ if [[ $VARIANT == "coverage" ]]; then lcov --no-external -c -i -d . -o baseline.info > /dev/null # Perform test - if [[ $MAIN_BRANCH == "1" ]]; then + if [[ $MAIN_BRANCH == "1" && "$DO_VALGRIND" = true ]]; then run_tests_with_valgrind # skip slow autobahn tests #run_autobahn_test_suite @@ -156,10 +161,14 @@ if [[ $VARIANT == "coverage" ]]; then lcov -e "lcov-all.info" "$PWD/include/beast/*" -o lcov.info > /dev/null ~/.local/bin/codecov -X gcov - cat lcov.info | node_modules/.bin/coveralls + #cat lcov.info | node_modules/.bin/coveralls # Clean up these stragglers so BOOST_ROOT cache is clean find $BOOST_ROOT/bin.v2 -name "*.gcda" | xargs rm -f else - run_tests_with_debugger + if [[ $MAIN_BRANCH == "1" && "$DO_VALGRIND" = true ]]; then + run_tests_with_valgrind + else + run_tests_with_debugger + fi fi diff --git a/src/beast/scripts/field.txt b/src/beast/scripts/field.txt new file mode 100644 index 0000000000..9f15ab16c7 --- /dev/null +++ b/src/beast/scripts/field.txt @@ -0,0 +1,351 @@ +Accept +Accept-Additions +Accept-Charset +Accept-Datetime +Accept-Encoding +Accept-Features +Accept-Language +Accept-Patch +Accept-Post +Accept-Ranges +Access-Control +Access-Control-Allow-Credentials +Access-Control-Allow-Headers +Access-Control-Allow-Methods +Access-Control-Allow-Origin +Access-Control-Max-Age +Access-Control-Request-Headers +Access-Control-Request-Method +Age +A-IM +Allow +ALPN +Also-Control +Alternate-Recipient +Alternates +Alt-Svc +Alt-Used +Apparently-To +Apply-To-Redirect-Ref +Approved +Archive +Archived-At +Article-Names +Article-Updates +Authentication-Control +Authentication-Info +Authentication-Results +Authorization +Autoforwarded +Autosubmitted +Auto-Submitted +Base +Bcc +Body +Cache-Control +CalDAV-Timezones +Cancel-Key +Cancel-Lock +Cc +C-Ext +Close +C-Man +Comments +Compliance +Connection +Content-Alternative +Content-Base +Content-Description +Content-Disposition +Content-Duration +Content-Encoding +Content-features +Content-ID +Content-Identifier +Content-Language +Content-Length +Content-Location +Content-MD5 +Content-Range +Content-Return +Content-Script-Type +Content-Style-Type +Content-Transfer-Encoding +Content-Type +Content-Version +Control +Conversion +Conversion-With-Loss +Cookie +Cookie2 +C-Opt +Cost +C-PEP +C-PEP-Info +DASL +Date +Date-Received +DAV +Default-Style +Deferred-Delivery +Delivery-Date +Delta-Base +Depth +Derived-From +Destination +Differential-ID +Digest +Discarded-X400-IPMS-Extensions +Discarded-X400-MTS-Extensions +Disclose-Recipients +Disposition-Notification-Options +Disposition-Notification-To +Distribution +DKIM-Signature +DL-Expansion-History +Downgraded-Bcc +Downgraded-Cc +Downgraded-Disposition-Notification-To +Downgraded-Final-Recipient +Downgraded-From +Downgraded-In-Reply-To +Downgraded-Mail-From +Downgraded-Message-Id +Downgraded-Original-Recipient +Downgraded-Rcpt-To +Downgraded-References +Downgraded-Reply-To +Downgraded-Resent-Bcc +Downgraded-Resent-Cc +Downgraded-Resent-From +Downgraded-Resent-Reply-To +Downgraded-Resent-Sender +Downgraded-Resent-To +Downgraded-Return-Path +Downgraded-Sender +Downgraded-To +EDIINT-Features +Eesst-Version +Encoding +Encrypted +Errors-To +ETag +Expect +Expires +Expiry-Date +Ext +Followup-To +Forwarded +From +Generate-Delivery-Report +GetProfile +Hobareg +Host +HTTP2-Settings +If +If-Match +If-Modified-Since +If-None-Match +If-Range +If-Schedule-Tag-Match +If-Unmodified-Since +IM +Importance +Incomplete-Copy +Injection-Date +Injection-Info +In-Reply-To +Jabber-ID +Keep-Alive +Keywords +Label +Language +Last-Modified +Latest-Delivery-Time +Lines +Link +List-Archive +List-Help +List-ID +List-Owner +List-Post +List-Subscribe +List-Unsubscribe +List-Unsubscribe-Post +Location +Lock-Token +Man +Max-Forwards +Memento-Datetime +Message-Context +Message-ID +Message-Type +Meter +Method-Check +Method-Check-Expires +MIME-Version +MMHS-Acp127-Message-Identifier +MMHS-Authorizing-Users +MMHS-Codress-Message-Indicator +MMHS-Copy-Precedence +MMHS-Exempted-Address +MMHS-Extended-Authorisation-Info +MMHS-Handling-Instructions +MMHS-Message-Instructions +MMHS-Message-Type +MMHS-Originator-PLAD +MMHS-Originator-Reference +MMHS-Other-Recipients-Indicator-CC +MMHS-Other-Recipients-Indicator-To +MMHS-Primary-Precedence +MMHS-Subject-Indicator-Codes +MT-Priority +Negotiate +Newsgroups +NNTP-Posting-Date +NNTP-Posting-Host +Non-Compliance +Obsoletes +Opt +Optional +Optional-WWW-Authenticate +Ordering-Type +Organization +Origin +Original-Encoded-Information-Types +Original-From +Original-Message-ID +Original-Recipient +Original-Sender +Original-Subject +Originator-Return-Address +Overwrite +P3P +Path +PEP +Pep-Info +PICS-Label +Position +Posting-Version +Pragma +Prefer +Preference-Applied +Prevent-NonDelivery-Report +Priority +Privicon +ProfileObject +Protocol +Protocol-Info +Protocol-Query +Protocol-Request +Proxy-Authenticate +Proxy-Authentication-Info +Proxy-Authorization +Proxy-Connection +Proxy-Features +Proxy-Instruction +Public +Public-Key-Pins +Public-Key-Pins-Report-Only +Range +Received +Received-SPF +Redirect-Ref +References +Referer +Referer-Root +Relay-Version +Reply-By +Reply-To +Require-Recipient-Valid-Since +Resent-Bcc +Resent-Cc +Resent-Date +Resent-From +Resent-Message-ID +Resent-Reply-To +Resent-Sender +Resent-To +Resolution-Hint +Resolver-Location +Retry-After +Return-Path +Safe +Schedule-Reply +Schedule-Tag +Security-Scheme +Sec-WebSocket-Accept +Sec-WebSocket-Extensions +Sec-WebSocket-Key +Sec-WebSocket-Protocol +Sec-WebSocket-Version +See-Also +Sender +Sensitivity +Server +Set-Cookie +Set-Cookie2 +SetProfile +SIO-Label +SIO-Label-History +SLUG +SoapAction +Solicitation +Status-URI +Strict-Transport-Security +Subject +SubOK +Subst +Summary +Supersedes +Surrogate-Capability +Surrogate-Control +TCN +TE +Timeout +Title +To +Topic +Trailer +Transfer-Encoding +TTL +UA-Color +UA-Media +UA-Pixels +UA-Resolution +UA-Windowpixels +Upgrade +Urgency +URI +User-Agent +Variant-Vary +Vary +VBR-Info +Version +Via +Want-Digest +Warning +WWW-Authenticate +X400-Content-Identifier +X400-Content-Return +X400-Content-Type +X400-MTS-Identifier +X400-Originator +X400-Received +X400-Recipients +X400-Trace +X-Archived-At +X-Device-Accept +X-Device-Accept-Charset +X-Device-Accept-Encoding +X-Device-Accept-Language +X-Device-User-Agent +X-Frame-Options +X-Mittente +X-PGP-Sig +Xref +X-Ricevuta +X-Riferimento-Message-ID +X-TipoRicevuta +X-Trasporto +X-VerificaSicurezza diff --git a/src/beast/scripts/install-boost.sh b/src/beast/scripts/install-boost.sh index 8365c2d544..fcee32d5ab 100755 --- a/src/beast/scripts/install-boost.sh +++ b/src/beast/scripts/install-boost.sh @@ -7,8 +7,8 @@ # When testing you can force a boost build by clearing travis caches: # https://travis-ci.org/ripple/rippled/caches set -eu -if [ ! -d "$BOOST_ROOT/lib" ] -then +#if [ ! -d "$BOOST_ROOT" ] +#then wget $BOOST_URL -O /tmp/boost.tar.gz cd `dirname $BOOST_ROOT` rm -fr ${BOOST_ROOT} @@ -21,7 +21,7 @@ then ./bootstrap.sh --prefix=$BOOST_ROOT && \ ./b2 -d1 $params && \ ./b2 -d0 $params install -else - echo "Using cached boost at $BOOST_ROOT" -fi +#else +# echo "Using cached boost at $BOOST_ROOT" +#fi diff --git a/src/beast/scripts/install-dependencies.sh b/src/beast/scripts/install-dependencies.sh index 4f0b7cb722..d6b31dd3dc 100755 --- a/src/beast/scripts/install-dependencies.sh +++ b/src/beast/scripts/install-dependencies.sh @@ -34,7 +34,7 @@ if [[ ! -x cmake/bin/cmake && -d cmake ]]; then rm -fr cmake fi if [[ ! -d cmake && ${BUILD_SYSTEM:-} == cmake ]]; then - CMAKE_URL="http://www.cmake.org/files/v3.5/cmake-3.5.2-Linux-x86_64.tar.gz" + CMAKE_URL="http://www.cmake.org/files/v3.8/cmake-3.8.0-Linux-x86_64.tar.gz" mkdir cmake && wget --no-check-certificate -O - ${CMAKE_URL} | tar --strip-components=1 -xz -C cmake fi @@ -54,6 +54,7 @@ ls -lah ~/.npm || mkdir ~/.npm # Make sure we own it chown -Rc $USER ~/.npm # We use this so we can filter the subtrees from our coverage report +pip install --user requests==2.13.0 pip install --user https://github.com/codecov/codecov-python/archive/master.zip pip install --user autobahntestsuite @@ -70,6 +71,6 @@ mkdir -p $LCOV_ROOT cd $HOME/lcov-1.12 && make install PREFIX=$LCOV_ROOT # Install coveralls reporter -cd $HERE -mkdir -p node_modules -npm install coveralls +#cd $HERE +#mkdir -p node_modules +#npm install coveralls diff --git a/src/beast/scripts/make_field.sh b/src/beast/scripts/make_field.sh new file mode 100644 index 0000000000..8e54c13720 --- /dev/null +++ b/src/beast/scripts/make_field.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +export LC_COLLATE=C + +echo "// string constants" +echo ' "",' +cat $1 | sort -f | uniq | sed 's/^/ \"/; s/$/\",/' +echo + +echo "enum class field : unsigned short" +echo "{" +echo " unknown = 0," +echo +#cat $1 | uniq | sort -f | sed 's/./\L&/g; s/^/\t/; s/$/,/' +cat $1 | sort -f | uniq | sed 's/\(.*\)/ \L\1,/; s/-/_/g' +echo "};" +echo + +echo "// pairs" +#cat $1 | uniq | sort -f | sed 's/\(.*\)/\tmatch\(field::\L\1, \"\E\1\"\);/; s/-/_/' +cat $1 | sort -f | uniq | perl -nE 'chomp; $a=lc($_); $a=~s/-/_/g; say " match(field::$a, \"$_\");";' | tr -d "\015" + diff --git a/src/beast/scripts/valgrind.supp b/src/beast/scripts/valgrind.supp index 8ad196a389..1d1ddc0b54 100644 --- a/src/beast/scripts/valgrind.supp +++ b/src/beast/scripts/valgrind.supp @@ -8,3 +8,22 @@ Memcheck:Cond fun:_ZN5beast4zlib6detail14deflate_stream11fill_windowIvEEvRNS0_8z_paramsE } +{ + Ignore OpenSSL malloc + Memcheck:Leak + fun:malloc + fun:CRYPTO_malloc + obj:*libcrypto* +} +{ + Ignore OpenSSL realloc + Memcheck:Leak + fun:realloc + fun:CRYPTO_realloc + obj:*libcrypto* +} +{ + OpenSSL Non-Purify + Memcheck:Cond + obj:*libcrypto* +} diff --git a/src/beast/test/CMakeLists.txt b/src/beast/test/CMakeLists.txt index ff15cb4d4f..d939ddbd06 100644 --- a/src/beast/test/CMakeLists.txt +++ b/src/beast/test/CMakeLists.txt @@ -1,27 +1,38 @@ # Part of Beast -GroupSources(extras/beast extras) -GroupSources(include/beast beast) -GroupSources(test "/") +add_subdirectory (core) +add_subdirectory (http) +add_subdirectory (websocket) +add_subdirectory (zlib) -add_executable (lib-tests - ${BEAST_INCLUDES} - ${EXTRAS_INCLUDES} - ../extras/beast/unit_test/main.cpp - config.cpp - core.cpp - http.cpp - version.cpp - websocket.cpp - zlib.cpp -) +if ((NOT "${VARIANT}" STREQUAL "coverage") AND + (NOT "${VARIANT}" STREQUAL "ubasan")) -if (NOT WIN32) - target_link_libraries(lib-tests ${Boost_LIBRARIES} Threads::Threads) -else() - target_link_libraries(lib-tests ${Boost_LIBRARIES}) -endif() - -if (MINGW) - set_target_properties(lib-tests PROPERTIES COMPILE_FLAGS "-Wa,-mbig-obj") + add_subdirectory (benchmarks) + add_subdirectory (common) + add_subdirectory (server) + + GroupSources(extras/beast extras) + GroupSources(include/beast beast) + + GroupSources(test "/") + + add_executable (lib-tests + ${BEAST_INCLUDES} + ${EXTRAS_INCLUDES} + ../extras/beast/unit_test/main.cpp + config.cpp + core.cpp + exemplars.cpp + http.cpp + version.cpp + websocket.cpp + zlib.cpp + ) + + target_link_libraries(lib-tests Beast ${Boost_PROGRAM_OPTIONS_LIBRARY}) + + if (MINGW) + set_target_properties(lib-tests PROPERTIES COMPILE_FLAGS "-Wa,-mbig-obj") + endif() endif() diff --git a/src/beast/test/Jamfile b/src/beast/test/Jamfile index 018ec76f7f..01d339cc18 100644 --- a/src/beast/test/Jamfile +++ b/src/beast/test/Jamfile @@ -7,97 +7,31 @@ import os ; -compile config.cpp : : ; -compile core.cpp : : ; -compile http.cpp : : ; -compile version.cpp : : ; -compile websocket.cpp : : ; -compile zlib.cpp : : ; +compile config.cpp : coverage:no + ubasan:no : ; -unit-test core-tests : - ../extras/beast/unit_test/main.cpp - core/async_completion.cpp - core/bind_handler.cpp - core/buffer_cat.cpp - core/buffer_concepts.cpp - core/buffers_adapter.cpp - core/clamp.cpp - core/consuming_buffers.cpp - core/dynabuf_readstream.cpp - core/error.cpp - core/handler_alloc.cpp - core/handler_concepts.cpp - core/handler_ptr.cpp - core/placeholders.cpp - core/prepare_buffer.cpp - core/prepare_buffers.cpp - core/static_streambuf.cpp - core/static_string.cpp - core/stream_concepts.cpp - core/streambuf.cpp - core/to_string.cpp - core/write_dynabuf.cpp - core/base64.cpp - core/empty_base_optimization.cpp - core/get_lowest_layer.cpp - core/is_call_possible.cpp - core/sha1.cpp - ; +compile core.cpp : coverage:no + ubasan:no : ; -unit-test http-tests : - ../extras/beast/unit_test/main.cpp - http/basic_dynabuf_body.cpp - http/basic_fields.cpp - http/basic_parser_v1.cpp - http/concepts.cpp - http/empty_body.cpp - http/fields.cpp - http/header_parser_v1.cpp - http/message.cpp - http/parse.cpp - http/parse_error.cpp - http/parser_v1.cpp - http/read.cpp - http/reason.cpp - http/rfc7230.cpp - http/streambuf_body.cpp - http/string_body.cpp - http/write.cpp - http/chunk_encode.cpp - ; +compile exemplars.cpp : coverage:no + ubasan:no : ; -unit-test bench-tests : - ../extras/beast/unit_test/main.cpp - http/nodejs_parser.cpp - http/parser_bench.cpp - ; +compile http.cpp : coverage:no + ubasan:no : ; -unit-test websocket-tests : - ../extras/beast/unit_test/main.cpp - websocket/error.cpp - websocket/option.cpp - websocket/rfc6455.cpp - websocket/stream.cpp - websocket/teardown.cpp - websocket/frame.cpp - websocket/mask.cpp - websocket/utf8_checker.cpp - ; +compile version.cpp : coverage:no + ubasan:no : ; -unit-test zlib-tests : - ../extras/beast/unit_test/main.cpp - zlib/zlib-1.2.8/adler32.c - zlib/zlib-1.2.8/compress.c - zlib/zlib-1.2.8/crc32.c - zlib/zlib-1.2.8/deflate.c - zlib/zlib-1.2.8/infback.c - zlib/zlib-1.2.8/inffast.c - zlib/zlib-1.2.8/inflate.c - zlib/zlib-1.2.8/inftrees.c - zlib/zlib-1.2.8/trees.c - zlib/zlib-1.2.8/uncompr.c - zlib/zlib-1.2.8/zutil.c - zlib/deflate_stream.cpp - zlib/error.cpp - zlib/inflate_stream.cpp - ; +compile websocket.cpp : coverage:no + ubasan:no : ; + +compile zlib.cpp : coverage:no + ubasan:no : ; + +build-project benchmarks ; +build-project common ; +build-project core ; +build-project http ; +build-project server ; +build-project websocket ; +build-project zlib ; diff --git a/src/beast/test/benchmarks/CMakeLists.txt b/src/beast/test/benchmarks/CMakeLists.txt new file mode 100644 index 0000000000..f6c0afcbc6 --- /dev/null +++ b/src/beast/test/benchmarks/CMakeLists.txt @@ -0,0 +1,25 @@ +# Part of Beast + +GroupSources(extras/beast extras) +GroupSources(include/beast beast) + +GroupSources(test/benchmarks "/") +GroupSources(test/http "/") + +add_executable (benchmarks + ${BEAST_INCLUDES} + ${EXTRAS_INCLUDES} + ../../extras/beast/unit_test/main.cpp + ../http/message_fuzz.hpp + nodejs_parser.hpp + buffers.cpp + nodejs_parser.cpp + parser.cpp + utf8_checker.cpp +) + +target_link_libraries(benchmarks + Beast + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ) diff --git a/src/beast/test/benchmarks/Jamfile b/src/beast/test/benchmarks/Jamfile new file mode 100644 index 0000000000..a3ff3bd6da --- /dev/null +++ b/src/beast/test/benchmarks/Jamfile @@ -0,0 +1,17 @@ +# +# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +# + +unit-test benchmarks : + ../../extras/beast/unit_test/main.cpp + buffers.cpp + nodejs_parser.cpp + parser.cpp + utf8_checker.cpp + : + coverage:no + ubasan:no + ; diff --git a/src/beast/test/benchmarks/buffers.cpp b/src/beast/test/benchmarks/buffers.cpp new file mode 100644 index 0000000000..5cc99e6507 --- /dev/null +++ b/src/beast/test/benchmarks/buffers.cpp @@ -0,0 +1,238 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { + +class buffers_test : public beast::unit_test::suite +{ +public: + using size_type = std::uint64_t; + + class timer + { + using clock_type = + std::chrono::system_clock; + + clock_type::time_point when_; + + public: + using duration = + clock_type::duration; + + timer() + : when_(clock_type::now()) + { + } + + duration + elapsed() const + { + return clock_type::now() - when_; + } + }; + + inline + size_type + throughput(std::chrono::duration< + double> const& elapsed, size_type items) + { + using namespace std::chrono; + return static_cast( + 1 / (elapsed/items).count()); + } + + template + static + std::size_t + fill(MutableBufferSequence const& buffers) + { + std::size_t n = 0; + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + for(boost::asio::mutable_buffer buffer : buffers) + { + std::fill( + buffer_cast(buffer), + buffer_cast(buffer) + + buffer_size(buffer), '\0'); + n += buffer_size(buffer); + } + return n; + } + + template + size_type + do_prepares(std::size_t repeat, + std::size_t count, std::size_t size) + { + timer t; + size_type total = 0; + for(auto i = repeat; i--;) + { + DynamicBuffer b; + for(auto j = count; j--;) + { + auto const n = fill(b.prepare(size)); + b.commit(n); + total += n; + } + } + return throughput(t.elapsed(), total); + } + + template + size_type + do_hints(std::size_t repeat, + std::size_t count, std::size_t size) + { + timer t; + size_type total = 0; + for(auto i = repeat; i--;) + { + DynamicBuffer b; + for(auto j = count; j--;) + { + for(auto remain = size; remain;) + { + auto const n = fill(b.prepare( + read_size(b, remain))); + b.commit(n); + remain -= n; + total += n; + } + } + } + return throughput(t.elapsed(), total); + } + + template + size_type + do_random(std::size_t repeat, + std::size_t count, std::size_t size) + { + timer t; + size_type total = 0; + for(auto i = repeat; i--;) + { + DynamicBuffer b; + for(auto j = count; j--;) + { + auto const n = fill(b.prepare( + 1 + (rand()%(2*size)))); + b.commit(n); + total += n; + } + } + return throughput(t.elapsed(), total); + } + + static + inline + void + do_trials_1(bool) + { + } + + template + void + do_trials_1(bool print, F0&& f, FN... fn) + { + timer t; + using namespace std::chrono; + static size_type constexpr den = 1024 * 1024; + if(print) + { + log << std::right << std::setw(10) << + ((f() + (den / 2)) / den) << " MB/s"; + log.flush(); + } + else + { + f(); + } + do_trials_1(print, fn...); + } + + template + void + do_trials(string_view name, + std::size_t trials, F0&& f0, FN... fn) + { + using namespace std::chrono; + // warm-up + do_trials_1(false, f0, fn...); + do_trials_1(false, f0, fn...); + while(trials--) + { + timer t; + log << std::left << std::setw(24) << name << ":"; + log.flush(); + do_trials_1(true, f0, fn...); + log << " " << + duration_cast(t.elapsed()).count() << "ms"; + log << std::endl; + } + } + + void + run() override + { + static std::size_t constexpr trials = 1; + static std::size_t constexpr repeat = 250; + std::vector> params; + params.emplace_back(1024, 1024); + params.emplace_back(512, 4096); + params.emplace_back(256, 32768); + log << std::endl; + for(auto const& param : params) + { + auto const count = param.first; + auto const size = param.second; + auto const s = std::string("count=") + std::to_string(count) + + ", size=" + std::to_string(size); + log << std::left << std::setw(24) << s << " " << + std::right << std::setw(15) << "prepare" << + std::right << std::setw(15) << "with hint" << + std::right << std::setw(15) << "random" << + std::endl; + do_trials("multi_buffer", trials, + [&](){ return do_prepares(repeat, count, size); } + ,[&](){ return do_hints (repeat, count, size); } + ,[&](){ return do_random (repeat, count, size); } + ); + do_trials("flat_buffer", trials, + [&](){ return do_prepares(repeat, count, size); } + ,[&](){ return do_hints (repeat, count, size); } + ,[&](){ return do_random (repeat, count, size); } + ); + do_trials("boost::asio::streambuf", trials, + [&](){ return do_prepares(repeat, count, size); } + ,[&](){ return do_hints (repeat, count, size); } + ,[&](){ return do_random (repeat, count, size); } + ); + log << std::endl; + } + pass(); + } +}; + +BEAST_DEFINE_TESTSUITE(buffers,benchmarks,beast); + +} // beast diff --git a/src/beast/test/http/nodejs-parser/AUTHORS b/src/beast/test/benchmarks/nodejs-parser/AUTHORS similarity index 100% rename from src/beast/test/http/nodejs-parser/AUTHORS rename to src/beast/test/benchmarks/nodejs-parser/AUTHORS diff --git a/src/beast/test/http/nodejs-parser/LICENSE-MIT b/src/beast/test/benchmarks/nodejs-parser/LICENSE-MIT similarity index 100% rename from src/beast/test/http/nodejs-parser/LICENSE-MIT rename to src/beast/test/benchmarks/nodejs-parser/LICENSE-MIT diff --git a/src/beast/test/http/nodejs-parser/README.md b/src/beast/test/benchmarks/nodejs-parser/README.md similarity index 100% rename from src/beast/test/http/nodejs-parser/README.md rename to src/beast/test/benchmarks/nodejs-parser/README.md diff --git a/src/beast/test/http/nodejs-parser/http_parser.c b/src/beast/test/benchmarks/nodejs-parser/http_parser.c similarity index 100% rename from src/beast/test/http/nodejs-parser/http_parser.c rename to src/beast/test/benchmarks/nodejs-parser/http_parser.c diff --git a/src/beast/test/http/nodejs-parser/http_parser.h b/src/beast/test/benchmarks/nodejs-parser/http_parser.h similarity index 100% rename from src/beast/test/http/nodejs-parser/http_parser.h rename to src/beast/test/benchmarks/nodejs-parser/http_parser.h diff --git a/src/beast/test/http/nodejs_parser.cpp b/src/beast/test/benchmarks/nodejs_parser.cpp similarity index 64% rename from src/beast/test/http/nodejs_parser.cpp rename to src/beast/test/benchmarks/nodejs_parser.cpp index 633872fa92..39bef4542a 100644 --- a/src/beast/test/http/nodejs_parser.cpp +++ b/src/beast/test/benchmarks/nodejs_parser.cpp @@ -5,13 +5,25 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifdef _MSC_VER +#if defined(__GNUC__) && (__GNUC__ >= 7) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" +#endif + +#include + +#ifdef BOOST_MSVC # pragma warning (push) # pragma warning (disable: 4127) // conditional expression is constant # pragma warning (disable: 4244) // integer conversion, possible loss of data #endif + #include "nodejs-parser/http_parser.c" -#ifdef _MSC_VER + +#ifdef BOOST_MSVC # pragma warning (pop) #endif +#if defined(__GNUC__) && (__GNUC__ >= 7) +#pragma GCC diagnostic pop +#endif diff --git a/src/beast/test/http/nodejs_parser.hpp b/src/beast/test/benchmarks/nodejs_parser.hpp similarity index 58% rename from src/beast/test/http/nodejs_parser.hpp rename to src/beast/test/benchmarks/nodejs_parser.hpp index 83ee3665ad..043c58349e 100644 --- a/src/beast/test/http/nodejs_parser.hpp +++ b/src/beast/test/benchmarks/nodejs_parser.hpp @@ -12,10 +12,11 @@ #include #include -#include #include +#include #include #include +#include #include #include #include @@ -174,7 +175,7 @@ public: error_code ec; auto const used = write(data, size, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); return used; } @@ -189,7 +190,7 @@ public: error_code ec; auto const used = write(buffers, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); return used; } @@ -204,12 +205,15 @@ public: error_code ec; write_eof(ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); } void write_eof(error_code& ec); + void + check_header(); + private: Derived& impl() @@ -217,231 +221,6 @@ private: return *static_cast(this); } - template - class has_on_start_t - { - template().on_start(), std::true_type{})> - static R check(int); - template - static std::false_type check(...); - using type = decltype(check(0)); - public: - static bool const value = type::value; - }; - template - using has_on_start = - std::integral_constant::value>; - - void - call_on_start(std::true_type) - { - impl().on_start(); - } - - void - call_on_start(std::false_type) - { - } - - template - class has_on_field_t - { - template().on_field( - std::declval(), - std::declval()), - std::true_type{})> - static R check(int); - template - static std::false_type check(...); - using type = decltype(check(0)); - public: - static bool const value = type::value; - }; - template - using has_on_field = - std::integral_constant::value>; - - void - call_on_field(std::string const& field, - std::string const& value, std::true_type) - { - impl().on_field(field, value); - } - - void - call_on_field(std::string const&, std::string const&, - std::false_type) - { - } - - template - class has_on_headers_complete_t - { - template().on_headers_complete( - std::declval()), std::true_type{})> - static R check(int); - template - static std::false_type check(...); - using type = decltype(check(0)); - public: - static bool const value = type::value; - }; - template - using has_on_headers_complete = - std::integral_constant::value>; - - void - call_on_headers_complete(error_code& ec, std::true_type) - { - impl().on_headers_complete(ec); - } - - void - call_on_headers_complete(error_code&, std::false_type) - { - } - - template - class has_on_request_t - { - template().on_request( - std::declval(), std::declval(), - std::declval(), std::declval(), - std::declval(), std::declval()), - std::true_type{})> - static R check(int); - template - static std::false_type check(...); - using type = decltype(check(0)); - public: - static bool const value = type::value; - }; - template - using has_on_request = - std::integral_constant::value>; - - void - call_on_request(unsigned method, std::string url, - int major, int minor, bool keep_alive, bool upgrade, - std::true_type) - { - impl().on_request( - method, url, major, minor, keep_alive, upgrade); - } - - void - call_on_request(unsigned, std::string, int, int, bool, bool, - std::false_type) - { - } - - template - class has_on_response_t - { - template().on_response( - std::declval(), std::declval, - std::declval(), std::declval(), - std::declval(), std::declval()), - std::true_type{})> - static R check(int); - template - static std::false_type check(...); -#if 0 - using type = decltype(check(0)); -#else - // VFALCO Trait seems broken for http::parser - using type = std::true_type; -#endif - public: - static bool const value = type::value; - }; - template - using has_on_response = - std::integral_constant::value>; - - bool - call_on_response(int status, std::string text, - int major, int minor, bool keep_alive, bool upgrade, - std::true_type) - { - return impl().on_response( - status, text, major, minor, keep_alive, upgrade); - } - - bool - call_on_response(int, std::string, int, int, bool, bool, - std::false_type) - { - // VFALCO Certainly incorrect - return true; - } - - template - class has_on_body_t - { - template().on_body( - std::declval(), std::declval(), - std::declval()), std::true_type{})> - static R check(int); - template - static std::false_type check(...); - using type = decltype(check(0)); - public: - static bool const value = type::value; - }; - template - using has_on_body = - std::integral_constant::value>; - - void - call_on_body(void const* data, std::size_t bytes, - error_code& ec, std::true_type) - { - impl().on_body(data, bytes, ec); - } - - void - call_on_body(void const*, std::size_t, - error_code&, std::false_type) - { - } - - template - class has_on_complete_t - { - template().on_complete(), std::true_type{})> - static R check(int); - template - static std::false_type check(...); - using type = decltype(check(0)); - public: - static bool const value = type::value; - }; - template - using has_on_complete = - std::integral_constant::value>; - - void - call_on_complete(std::true_type) - { - impl().on_complete(); - } - - void - call_on_complete(std::false_type) - { - } - - void - check_header(); - static int cb_message_start(http_parser*); static int cb_url(http_parser*, char const*, std::size_t); static int cb_status(http_parser*, char const*, std::size_t); @@ -482,13 +261,13 @@ std::size_t nodejs_basic_parser::write( ConstBufferSequence const& buffers, error_code& ec) { - static_assert(beast::is_ConstBufferSequence< + static_assert(beast::is_const_buffer_sequence< ConstBufferSequence>::value, "ConstBufferSequence requirements not met"); using boost::asio::buffer_cast; using boost::asio::buffer_size; std::size_t bytes_used = 0; - for(auto const& buffer : buffers) + for(boost::asio::const_buffer buffer : buffers) { auto const n = write( buffer_cast(buffer), @@ -525,7 +304,8 @@ nodejs_basic_parser(nodejs_basic_parser&& other) template auto -nodejs_basic_parser::operator=(nodejs_basic_parser&& other) -> +nodejs_basic_parser:: +operator=(nodejs_basic_parser&& other) -> nodejs_basic_parser& { state_ = other.state_; @@ -568,7 +348,8 @@ operator=(nodejs_basic_parser const& other) -> } template -nodejs_basic_parser::nodejs_basic_parser(bool request) noexcept +nodejs_basic_parser:: +nodejs_basic_parser(bool request) noexcept { state_.data = this; http_parser_init(&state_, request @@ -578,7 +359,8 @@ nodejs_basic_parser::nodejs_basic_parser(bool request) noexcept template std::size_t -nodejs_basic_parser::write(void const* data, +nodejs_basic_parser:: +write(void const* data, std::size_t size, error_code& ec) { ec_ = &ec; @@ -595,11 +377,11 @@ nodejs_basic_parser::write(void const* data, template void -nodejs_basic_parser::write_eof(error_code& ec) +nodejs_basic_parser:: +write_eof(error_code& ec) { ec_ = &ec; - http_parser_execute( - &state_, hooks(), nullptr, 0); + http_parser_execute(&state_, hooks(), nullptr, 0); if(! ec) ec = detail::make_nodejs_error( static_cast(state_.http_errno)); @@ -607,13 +389,12 @@ nodejs_basic_parser::write_eof(error_code& ec) template void -nodejs_basic_parser::check_header() +nodejs_basic_parser:: +check_header() { if(! value_.empty()) { - //detail::trim(value_); - call_on_field(field_, value_, - has_on_field{}); + impl().on_field(field_, value_); field_.clear(); value_.clear(); } @@ -621,7 +402,8 @@ nodejs_basic_parser::check_header() template int -nodejs_basic_parser::cb_message_start(http_parser* p) +nodejs_basic_parser:: +cb_message_start(http_parser* p) { auto& t = *reinterpret_cast(p->data); t.complete_ = false; @@ -629,13 +411,14 @@ nodejs_basic_parser::cb_message_start(http_parser* p) t.status_.clear(); t.field_.clear(); t.value_.clear(); - t.call_on_start(has_on_start{}); + t.impl().on_start(); return 0; } template int -nodejs_basic_parser::cb_url(http_parser* p, +nodejs_basic_parser:: +cb_url(http_parser* p, char const* in, std::size_t bytes) { auto& t = *reinterpret_cast(p->data); @@ -645,7 +428,8 @@ nodejs_basic_parser::cb_url(http_parser* p, template int -nodejs_basic_parser::cb_status(http_parser* p, +nodejs_basic_parser:: +cb_status(http_parser* p, char const* in, std::size_t bytes) { auto& t = *reinterpret_cast(p->data); @@ -655,7 +439,8 @@ nodejs_basic_parser::cb_status(http_parser* p, template int -nodejs_basic_parser::cb_header_field(http_parser* p, +nodejs_basic_parser:: +cb_header_field(http_parser* p, char const* in, std::size_t bytes) { auto& t = *reinterpret_cast(p->data); @@ -666,7 +451,8 @@ nodejs_basic_parser::cb_header_field(http_parser* p, template int -nodejs_basic_parser::cb_header_value(http_parser* p, +nodejs_basic_parser:: +cb_header_value(http_parser* p, char const* in, std::size_t bytes) { auto& t = *reinterpret_cast(p->data); @@ -676,62 +462,68 @@ nodejs_basic_parser::cb_header_value(http_parser* p, template int -nodejs_basic_parser::cb_headers_complete(http_parser* p) +nodejs_basic_parser:: +cb_headers_complete(http_parser* p) { auto& t = *reinterpret_cast(p->data); t.check_header(); - t.call_on_headers_complete(*t.ec_, - has_on_headers_complete{}); + t.impl().on_headers_complete(*t.ec_); if(*t.ec_) return 1; bool const keep_alive = http_should_keep_alive(p) != 0; if(p->type == http_parser_type::HTTP_REQUEST) { - t.call_on_request(p->method, t.url_, + t.impl().on_request(p->method, t.url_, p->http_major, p->http_minor, keep_alive, - p->upgrade, has_on_request{}); + p->upgrade); return 0; } - return t.call_on_response(p->status_code, t.status_, + return t.impl().on_response(p->status_code, t.status_, p->http_major, p->http_minor, keep_alive, - p->upgrade, has_on_response{}) ? 0 : 1; + p->upgrade) ? 0 : 1; } template int -nodejs_basic_parser::cb_body(http_parser* p, +nodejs_basic_parser:: +cb_body(http_parser* p, char const* in, std::size_t bytes) { auto& t = *reinterpret_cast(p->data); - t.call_on_body(in, bytes, *t.ec_, has_on_body{}); + t.impl().on_body(in, bytes, *t.ec_); return *t.ec_ ? 1 : 0; } template int -nodejs_basic_parser::cb_message_complete(http_parser* p) +nodejs_basic_parser:: +cb_message_complete(http_parser* p) { auto& t = *reinterpret_cast(p->data); t.complete_ = true; - t.call_on_complete(has_on_complete{}); + t.impl().on_complete(); return 0; } template int -nodejs_basic_parser::cb_chunk_header(http_parser*) +nodejs_basic_parser:: +cb_chunk_header(http_parser*) { return 0; } template int -nodejs_basic_parser::cb_chunk_complete(http_parser*) +nodejs_basic_parser:: +cb_chunk_complete(http_parser*) { return 0; } +//------------------------------------------------------------------------------ + /** A HTTP parser. The parser may only be used once. @@ -740,11 +532,6 @@ template class nodejs_parser : public nodejs_basic_parser> { - using message_type = - message; - - message_type m_; - typename message_type::body_type::reader r_; bool started_ = false; public: @@ -752,7 +539,6 @@ public: nodejs_parser() : http::nodejs_basic_parser(isRequest) - , r_(m_) { } @@ -763,12 +549,6 @@ public: return started_; } - message_type - release() - { - return std::move(m_); - } - private: friend class http::nodejs_basic_parser; @@ -779,35 +559,30 @@ private: } void - on_field(std::string const& field, std::string const& value) + on_field(std::string const&, std::string const&) { - m_.fields.insert(field, value); } void - on_headers_complete(error_code&) + on_headers_complete(error_code& ec) { // vFALCO TODO Decode the Content-Length and // Transfer-Encoding, see if we can reserve the buffer. // // r_.reserve(content_length) + ec.assign(0, ec.category()); } bool - on_request(unsigned method, std::string const& url, - int major, int minor, bool /*keep_alive*/, bool /*upgrade*/, - std::true_type) + on_request(unsigned, std::string const&, + int, int, bool, bool, std::true_type) { - m_.method = detail::method_to_string(method); - m_.url = url; - m_.version = major * 10 + minor; return true; } bool on_request(unsigned, std::string const&, - int, int, bool, bool, - std::false_type) + int, int, bool, bool, std::false_type) { return true; } @@ -819,19 +594,13 @@ private: return on_request(method, url, major, minor, keep_alive, upgrade, std::integral_constant< - bool, message_type::is_request>{}); + bool, isRequest>{}); } bool - on_response(int status, std::string const& reason, - int major, int minor, bool keep_alive, bool upgrade, - std::true_type) + on_response(int, std::string const&, + int, int, bool, bool, std::true_type) { - beast::detail::ignore_unused(keep_alive, upgrade); - m_.status = status; - m_.reason = reason; - m_.version = major * 10 + minor; - // VFALCO TODO return expect_body_ return true; } @@ -848,14 +617,13 @@ private: { return on_response( status, reason, major, minor, keep_alive, upgrade, - std::integral_constant{}); + std::integral_constant{}); } void - on_body(void const* data, - std::size_t size, error_code& ec) + on_body(void const*, std::size_t, error_code& ec) { - r_.write(data, size, ec); + ec.assign(0, ec.category()); } void diff --git a/src/beast/test/benchmarks/parser.cpp b/src/beast/test/benchmarks/parser.cpp new file mode 100644 index 0000000000..b7c67f42be --- /dev/null +++ b/src/beast/test/benchmarks/parser.cpp @@ -0,0 +1,290 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include "nodejs_parser.hpp" +#include "../http/message_fuzz.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +class parser_test : public beast::unit_test::suite +{ +public: + static std::size_t constexpr N = 2000; + + //using corpus = std::vector; + using corpus = std::vector; + + corpus creq_; + corpus cres_; + std::size_t size_ = 0; + + template + static + std::string + to_string(ConstBufferSequence const& bs) + { + return boost::lexical_cast< + std::string>(buffers(bs)); + } + + corpus + build_corpus(std::size_t n, std::true_type) + { + corpus v; + v.resize(n); + message_fuzz mg; + for(std::size_t i = 0; i < n; ++i) + { + mg.request(v[i]); + size_ += v[i].size(); + BEAST_EXPECT(v[i].size() > 0); + } + return v; + } + + corpus + build_corpus(std::size_t n, std::false_type) + { + corpus v; + v.resize(n); + message_fuzz mg; + for(std::size_t i = 0; i < n; ++i) + { + mg.response(v[i]); + size_ += v[i].size(); + BEAST_EXPECT(v[i].size() > 0); + } + return v; + } + + template + static + std::size_t + feed(ConstBufferSequence const& buffers, + basic_parser& parser, + error_code& ec) + { + using boost::asio::buffer_size; + beast::consuming_buffers< + ConstBufferSequence> cb{buffers}; + std::size_t used = 0; + for(;;) + { + auto const n = + parser.put(cb, ec); + if(ec) + return 0; + if(n == 0) + break; + cb.consume(n); + used += n; + if(parser.is_done()) + break; + if(buffer_size(cb) == 0) + break; + } + return used; + } + + template + void + testParser1(std::size_t repeat, corpus const& v) + { + while(repeat--) + for(auto const& b : v) + { + Parser p; + error_code ec; + p.write(b.data(), ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + log << to_string(b.data()) << std::endl; + } + } + + template + void + testParser2(std::size_t repeat, corpus const& v) + { + while(repeat--) + for(auto const& b : v) + { + Parser p; + p.header_limit((std::numeric_limits::max)()); + error_code ec; + feed(b.data(), p, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + log << to_string(b.data()) << std::endl; + } + } + + template + void + timedTest(std::size_t repeat, std::string const& name, Function&& f) + { + using namespace std::chrono; + using clock_type = std::chrono::high_resolution_clock; + log << name << std::endl; + for(std::size_t trial = 1; trial <= repeat; ++trial) + { + auto const t0 = clock_type::now(); + f(); + auto const elapsed = clock_type::now() - t0; + log << + "Trial " << trial << ": " << + duration_cast(elapsed).count() << " ms" << std::endl; + } + } + + template + struct null_parser : + basic_parser> + { + }; + + template + struct bench_parser : basic_parser< + isRequest, bench_parser> + { + using mutable_buffers_type = + boost::asio::mutable_buffers_1; + + void + on_request(verb, string_view, + string_view, int, error_code& ec) + { + ec.assign(0, ec.category()); + } + + void + on_response(int, + string_view, + int, error_code& ec) + { + ec.assign(0, ec.category()); + } + + void + on_field(field, + string_view, string_view, error_code& ec) + { + ec.assign(0, ec.category()); + } + + void + on_header(error_code& ec) + { + ec.assign(0, ec.category()); + } + + void + on_body(boost::optional const&, + error_code& ec) + { + ec.assign(0, ec.category()); + } + + std::size_t + on_data(string_view s, error_code& ec) + { + ec.assign(0, ec.category()); + return s.size(); + } + + void + on_chunk(std::uint64_t, + string_view, + error_code& ec) + { + ec.assign(0, ec.category()); + } + + void + on_complete(error_code& ec) + { + ec.assign(0, ec.category()); + } + }; + + void + testSpeed() + { + static std::size_t constexpr Trials = 5; + static std::size_t constexpr Repeat = 500; + + creq_ = build_corpus(N/2, std::true_type{}); + cres_ = build_corpus(N/2, std::false_type{}); + + log << "sizeof(request parser) == " << + sizeof(null_parser) << '\n'; + + log << "sizeof(response parser) == " << + sizeof(null_parser)<< '\n'; + + testcase << "Parser speed test, " << + ((Repeat * size_ + 512) / 1024) << "KB in " << + (Repeat * (creq_.size() + cres_.size())) << " messages"; + +#if 0 + timedTest(Trials, "http::parser", + [&] + { + testParser2>(Repeat, creq_); + testParser2>(Repeat, cres_); + }); +#endif +#if 1 + timedTest(Trials, "http::basic_parser", + [&] + { + testParser2 >( + Repeat, creq_); + testParser2>( + Repeat, cres_); + }); +#if 1 + timedTest(Trials, "nodejs_parser", + [&] + { + testParser1>( + Repeat, creq_); + testParser1>( + Repeat, cres_); + }); +#endif +#endif + pass(); + } + + void run() override + { + pass(); + testSpeed(); + } +}; + +BEAST_DEFINE_TESTSUITE(parser,benchmarks,beast); + +} // http +} // beast + diff --git a/src/beast/test/benchmarks/utf8_checker.cpp b/src/beast/test/benchmarks/utf8_checker.cpp new file mode 100644 index 0000000000..bdb8c5da5b --- /dev/null +++ b/src/beast/test/benchmarks/utf8_checker.cpp @@ -0,0 +1,140 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include +#include +#include +#include +#include + +namespace beast { + +class utf8_checker_test : public beast::unit_test::suite +{ + std::mt19937 rng_; + +public: + using size_type = std::uint64_t; + + class timer + { + using clock_type = + std::chrono::system_clock; + + clock_type::time_point when_; + + public: + using duration = + clock_type::duration; + + timer() + : when_(clock_type::now()) + { + } + + duration + elapsed() const + { + return clock_type::now() - when_; + } + }; + + template + UInt + rand(std::size_t n) + { + return static_cast( + std::uniform_int_distribution< + std::size_t>{0, n-1}(rng_)); + } + + static + inline + size_type + throughput(std::chrono::duration< + double> const& elapsed, size_type items) + { + using namespace std::chrono; + return static_cast( + 1 / (elapsed/items).count()); + } + + std::string + corpus(std::size_t n) + { + std::string s; + s.reserve(n); + while(n--) + s.push_back(static_cast( + ' ' + rand(95))); + return s; + } + + void + checkLocale(std::string const& s) + { + using namespace boost::locale; + auto p = s.begin(); + auto const e = s.end(); + while(p != e) + { + auto cp = utf::utf_traits::decode(p, e); + if(cp == utf::illegal) + break; + } + } + + void + checkBeast(std::string const& s) + { + beast::websocket::detail::check_utf8( + s.data(), s.size()); + } + + template + typename timer::clock_type::duration + test(F const& f) + { + timer t; + f(); + return t.elapsed(); + } + + void + run() override + { + auto const s = corpus(32 * 1024 * 1024); + for(int i = 0; i < 5; ++ i) + { + auto const elapsed = test([&]{ + checkBeast(s); + checkBeast(s); + checkBeast(s); + checkBeast(s); + checkBeast(s); + }); + log << "beast: " << throughput(elapsed, s.size()) << " char/s" << std::endl; + } + for(int i = 0; i < 5; ++ i) + { + auto const elapsed = test([&]{ + checkLocale(s); + checkLocale(s); + checkLocale(s); + checkLocale(s); + checkLocale(s); + }); + log << "locale: " << throughput(elapsed, s.size()) << " char/s" << std::endl; + } + pass(); + } +}; + +BEAST_DEFINE_TESTSUITE(utf8_checker,benchmarks,beast); + +} // beast + diff --git a/src/beast/test/common/CMakeLists.txt b/src/beast/test/common/CMakeLists.txt new file mode 100644 index 0000000000..651732cb6b --- /dev/null +++ b/src/beast/test/common/CMakeLists.txt @@ -0,0 +1,27 @@ +# Part of Beast + +GroupSources(example/common common) +GroupSources(extras/beast extras) +GroupSources(include/beast beast) +GroupSources(test/common "/") + +add_executable (common-test + ${BEAST_INCLUDES} + ${COMMON_INCLUDES} + detect_ssl.cpp + mime_types.cpp + rfc7231.cpp + ssl_stream.cpp + write_msg.cpp + main.cpp +) + +target_link_libraries(common-test + Beast + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ) + +if (OPENSSL_FOUND) + target_link_libraries (common-test ${OPENSSL_LIBRARIES}) +endif() diff --git a/src/beast/test/common/Jamfile b/src/beast/test/common/Jamfile new file mode 100644 index 0000000000..282b842be0 --- /dev/null +++ b/src/beast/test/common/Jamfile @@ -0,0 +1,18 @@ +# +# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +# + +exe common-test : + detect_ssl.cpp + mime_types.cpp + rfc7231.cpp + ssl_stream.cpp + write_msg.cpp + main.cpp + : + coverage:no + ubasan:no + ; diff --git a/src/beast/test/core/async_completion.cpp b/src/beast/test/common/detect_ssl.cpp similarity index 85% rename from src/beast/test/core/async_completion.cpp rename to src/beast/test/common/detect_ssl.cpp index 15e0d7dbcc..00fced9f25 100644 --- a/src/beast/test/core/async_completion.cpp +++ b/src/beast/test/common/detect_ssl.cpp @@ -6,4 +6,5 @@ // // Test that header file is self-contained. -#include +#include "../../example/common/detect_ssl.hpp" + diff --git a/src/beast/test/common/main.cpp b/src/beast/test/common/main.cpp new file mode 100644 index 0000000000..6335cbf20a --- /dev/null +++ b/src/beast/test/common/main.cpp @@ -0,0 +1,10 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +int main() +{ +} diff --git a/src/beast/test/common/mime_types.cpp b/src/beast/test/common/mime_types.cpp new file mode 100644 index 0000000000..a681a902c8 --- /dev/null +++ b/src/beast/test/common/mime_types.cpp @@ -0,0 +1,10 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include "../../example/common/mime_types.hpp" + diff --git a/src/beast/test/http/basic_dynabuf_body.cpp b/src/beast/test/common/rfc7231.cpp similarity index 86% rename from src/beast/test/http/basic_dynabuf_body.cpp rename to src/beast/test/common/rfc7231.cpp index a8a449c217..8c5c017ca7 100644 --- a/src/beast/test/http/basic_dynabuf_body.cpp +++ b/src/beast/test/common/rfc7231.cpp @@ -6,4 +6,5 @@ // // Test that header file is self-contained. -#include +#include "../../example/common/rfc7231.hpp" + diff --git a/src/beast/test/common/ssl_stream.cpp b/src/beast/test/common/ssl_stream.cpp new file mode 100644 index 0000000000..6a54bdbdb6 --- /dev/null +++ b/src/beast/test/common/ssl_stream.cpp @@ -0,0 +1,13 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#if BEAST_USE_OPENSSL + +// Test that header file is self-contained. +#include "../../example/common/ssl_stream.hpp" + +#endif diff --git a/src/beast/test/core/prepare_buffer.cpp b/src/beast/test/common/write_msg.cpp similarity index 85% rename from src/beast/test/core/prepare_buffer.cpp rename to src/beast/test/common/write_msg.cpp index 989ce0c4d3..80d62d68de 100644 --- a/src/beast/test/core/prepare_buffer.cpp +++ b/src/beast/test/common/write_msg.cpp @@ -6,4 +6,5 @@ // // Test that header file is self-contained. -#include +#include "../../example/common/write_msg.hpp" + diff --git a/src/beast/test/core/CMakeLists.txt b/src/beast/test/core/CMakeLists.txt index 766627e3ec..39749c57e0 100644 --- a/src/beast/test/core/CMakeLists.txt +++ b/src/beast/test/core/CMakeLists.txt @@ -1,44 +1,56 @@ # Part of Beast +GroupSources(example example) GroupSources(extras/beast extras) GroupSources(include/beast beast) + GroupSources(test/core "/") add_executable (core-tests ${BEAST_INCLUDES} + ${EXAMPLE_INCLUDES} ${EXTRAS_INCLUDES} ../../extras/beast/unit_test/main.cpp buffer_test.hpp - async_completion.cpp + file_test.hpp + async_result.cpp bind_handler.cpp buffer_cat.cpp - buffer_concepts.cpp + buffer_prefix.cpp + buffered_read_stream.cpp buffers_adapter.cpp clamp.cpp consuming_buffers.cpp - dynabuf_readstream.cpp + doc_examples.cpp + doc_snippets.cpp + drain_buffer.cpp error.cpp + file.cpp + file_posix.cpp + file_stdio.cpp + file_win32.cpp + flat_buffer.cpp handler_alloc.cpp - handler_concepts.cpp handler_ptr.cpp - placeholders.cpp - prepare_buffer.cpp - prepare_buffers.cpp - static_streambuf.cpp + multi_buffer.cpp + ostream.cpp + read_size.cpp + span.cpp + static_buffer.cpp static_string.cpp - stream_concepts.cpp - streambuf.cpp - to_string.cpp - write_dynabuf.cpp + string.cpp + string_param.cpp + type_traits.cpp base64.cpp empty_base_optimization.cpp - get_lowest_layer.cpp - is_call_possible.cpp sha1.cpp ) -if (NOT WIN32) - target_link_libraries(core-tests ${Boost_LIBRARIES} Threads::Threads) -else() - target_link_libraries(core-tests ${Boost_LIBRARIES}) -endif() +target_link_libraries(core-tests + Beast + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_COROUTINE_LIBRARY} + ${Boost_THREAD_LIBRARY} + ${Boost_CONTEXT_LIBRARY} + ) diff --git a/src/beast/test/core/Jamfile b/src/beast/test/core/Jamfile new file mode 100644 index 0000000000..159b7ae568 --- /dev/null +++ b/src/beast/test/core/Jamfile @@ -0,0 +1,41 @@ +# +# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +# + +unit-test core-tests : + ../../extras/beast/unit_test/main.cpp + async_result.cpp + bind_handler.cpp + buffer_cat.cpp + buffer_prefix.cpp + buffered_read_stream.cpp + buffers_adapter.cpp + clamp.cpp + consuming_buffers.cpp + doc_examples.cpp + doc_snippets.cpp + drain_buffer.cpp + error.cpp + file.cpp + file_posix.cpp + file_stdio.cpp + file_win32.cpp + flat_buffer.cpp + handler_alloc.cpp + handler_ptr.cpp + multi_buffer.cpp + ostream.cpp + read_size.cpp + span.cpp + static_buffer.cpp + static_string.cpp + string.cpp + string_param.cpp + type_traits.cpp + base64.cpp + empty_base_optimization.cpp + sha1.cpp + ; diff --git a/src/beast/test/core/async_result.cpp b/src/beast/test/core/async_result.cpp new file mode 100644 index 0000000000..1aca1cbed8 --- /dev/null +++ b/src/beast/test/core/async_result.cpp @@ -0,0 +1,38 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include +#include +#include + +namespace beast { +namespace { + +struct handler +{ + void operator()(beast::error_code, std::size_t) const; +}; + +static_assert(detail::is_invocable< + typename async_result::completion_handler_type, + void(error_code, std::size_t)>::value, ""); + +static_assert(std::is_same::return_type>::value, ""); + +static_assert(std::is_constructible< + async_result, + typename async_result::completion_handler_type&>::value, ""); + +} // (anon-ns) +} // beast diff --git a/src/beast/test/core/base64.cpp b/src/beast/test/core/base64.cpp index 606f8b13ea..e33f65d67d 100644 --- a/src/beast/test/core/base64.cpp +++ b/src/beast/test/core/base64.cpp @@ -34,6 +34,19 @@ public: check ("foob", "Zm9vYg=="); check ("fooba", "Zm9vYmE="); check ("foobar", "Zm9vYmFy"); + + check( + "Man is distinguished, not only by his reason, but by this singular passion from " + "other animals, which is a lust of the mind, that by a perseverance of delight " + "in the continued and indefatigable generation of knowledge, exceeds the short " + "vehemence of any carnal pleasure." + , + "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz" + "IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg" + "dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu" + "dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo" + "ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=" + ); } }; diff --git a/src/beast/test/core/bind_handler.cpp b/src/beast/test/core/bind_handler.cpp index 9299cb9e95..85486325a1 100644 --- a/src/beast/test/core/bind_handler.cpp +++ b/src/beast/test/core/bind_handler.cpp @@ -8,6 +8,7 @@ // Test that header file is self-contained. #include +#include #include #include @@ -16,6 +17,21 @@ namespace beast { class bind_handler_test : public unit_test::suite { public: + struct handler + { + void + operator()() const; + }; + +#if 0 + // This function should fail to compile + void + failStdBind() + { + std::bind(bind_handler(handler{})); + } +#endif + void callback(int v) { diff --git a/src/beast/test/core/buffer_cat.cpp b/src/beast/test/core/buffer_cat.cpp index 688b315c87..88005dc558 100644 --- a/src/beast/test/core/buffer_cat.cpp +++ b/src/beast/test/core/buffer_cat.cpp @@ -165,6 +165,12 @@ public: } // decrement iterator + /* VFALCO + This causes a mysterious "uninitialized variable" + warning related to this function (see comment) + https://code.woboq.org/qt5/include/c++/6.3.1/bits/stl_iterator.h.html#159 + */ +#if 0 { auto const rbegin = make_reverse_iterator(bs.end()); @@ -175,6 +181,7 @@ public: n += buffer_size(*it); BEAST_EXPECT(n == 9); } +#endif try { @@ -215,51 +222,39 @@ public: { }; - // Check is_all_ConstBufferSequence - static_assert( - detail::is_all_ConstBufferSequence< - const_buffers_1 - >::value, ""); - static_assert( - detail::is_all_ConstBufferSequence< - const_buffers_1, const_buffers_1 - >::value, ""); - static_assert( - detail::is_all_ConstBufferSequence< - mutable_buffers_1 - >::value, ""); - static_assert( - detail::is_all_ConstBufferSequence< - mutable_buffers_1, mutable_buffers_1 - >::value, ""); - static_assert( - detail::is_all_ConstBufferSequence< - const_buffers_1, mutable_buffers_1 - >::value, ""); - static_assert( - ! detail::is_all_ConstBufferSequence< - const_buffers_1, mutable_buffers_1, int - >::value, ""); + // Check is_all_const_buffer_sequence + BOOST_STATIC_ASSERT( + detail::is_all_const_buffer_sequence::value); + BOOST_STATIC_ASSERT( + detail::is_all_const_buffer_sequence::value); + BOOST_STATIC_ASSERT( + detail::is_all_const_buffer_sequence::value); + BOOST_STATIC_ASSERT( + detail::is_all_const_buffer_sequence::value); + BOOST_STATIC_ASSERT( + detail::is_all_const_buffer_sequence::value); + BOOST_STATIC_ASSERT( + ! detail::is_all_const_buffer_sequence::value); // Ensure that concatenating mutable buffer // sequences results in a mutable buffer sequence - static_assert(std::is_same< + BOOST_STATIC_ASSERT(std::is_same< mutable_buffer, decltype(buffer_cat( std::declval(), std::declval(), std::declval() - ))::value_type>::value, ""); + ))::value_type>::value); // Ensure that concatenating mixed buffer // sequences results in a const buffer sequence. - static_assert(std::is_same< + BOOST_STATIC_ASSERT(std::is_same< const_buffer, decltype(buffer_cat( std::declval(), std::declval(), std::declval() - ))::value_type>::value, ""); + ))::value_type>::value); testBufferCat(); testIterators(); diff --git a/src/beast/test/core/buffer_concepts.cpp b/src/beast/test/core/buffer_concepts.cpp deleted file mode 100644 index 6a3476f670..0000000000 --- a/src/beast/test/core/buffer_concepts.cpp +++ /dev/null @@ -1,25 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -// Test that header file is self-contained. -#include - -namespace beast { - -namespace { -struct T -{ -}; -} - -static_assert(is_ConstBufferSequence::value, ""); -static_assert(! is_ConstBufferSequence::value, ""); - -static_assert(is_MutableBufferSequence::value, ""); -static_assert(! is_MutableBufferSequence::value, ""); - -} // beast diff --git a/src/beast/test/core/buffer_prefix.cpp b/src/beast/test/core/buffer_prefix.cpp new file mode 100644 index 0000000000..4da8687b1b --- /dev/null +++ b/src/beast/test/core/buffer_prefix.cpp @@ -0,0 +1,197 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include +#include +#include +#include +#include + +namespace beast { + +BOOST_STATIC_ASSERT( + std::is_same()))>::value); + +BOOST_STATIC_ASSERT( + is_const_buffer_sequence()))>::value); + +BOOST_STATIC_ASSERT( + std::is_same()))>::value); +BOOST_STATIC_ASSERT( + is_mutable_buffer_sequence()))>::value); + +class buffer_prefix_test : public beast::unit_test::suite +{ +public: + template + static + std::size_t + bsize1(ConstBufferSequence const& bs) + { + using boost::asio::buffer_size; + std::size_t n = 0; + for(auto it = bs.begin(); it != bs.end(); ++it) + n += buffer_size(*it); + return n; + } + + template + static + std::size_t + bsize2(ConstBufferSequence const& bs) + { + using boost::asio::buffer_size; + std::size_t n = 0; + for(auto it = bs.begin(); it != bs.end(); it++) + n += buffer_size(*it); + return n; + } + + template + static + std::size_t + bsize3(ConstBufferSequence const& bs) + { + using boost::asio::buffer_size; + std::size_t n = 0; + for(auto it = bs.end(); it != bs.begin();) + n += buffer_size(*--it); + return n; + } + + template + static + std::size_t + bsize4(ConstBufferSequence const& bs) + { + using boost::asio::buffer_size; + std::size_t n = 0; + for(auto it = bs.end(); it != bs.begin();) + { + it--; + n += buffer_size(*it); + } + return n; + } + + template + static + std::string + to_string(ConstBufferSequence const& bs) + { + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + std::string s; + s.reserve(buffer_size(bs)); + for(boost::asio::const_buffer b : bs) + s.append(buffer_cast(b), + buffer_size(b)); + return s; + } + + template + void testMatrix() + { + using boost::asio::buffer_size; + std::string s = "Hello, world"; + BEAST_EXPECT(s.size() == 12); + for(std::size_t x = 1; x < 4; ++x) { + for(std::size_t y = 1; y < 4; ++y) { + std::size_t z = s.size() - (x + y); + { + std::array bs{{ + BufferType{&s[0], x}, + BufferType{&s[x], y}, + BufferType{&s[x+y], z}}}; + for(std::size_t i = 0; i <= s.size() + 1; ++i) + { + auto pb = buffer_prefix(i, bs); + BEAST_EXPECT(to_string(pb) == s.substr(0, i)); + auto pb2 = pb; + BEAST_EXPECT(to_string(pb2) == to_string(pb)); + pb = buffer_prefix(0, bs); + pb2 = pb; + BEAST_EXPECT(buffer_size(pb2) == 0); + pb2 = buffer_prefix(i, bs); + BEAST_EXPECT(to_string(pb2) == s.substr(0, i)); + } + } + }} + } + + void testNullBuffers() + { + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + using boost::asio::null_buffers; + auto pb0 = buffer_prefix(0, null_buffers{}); + BEAST_EXPECT(buffer_size(pb0) == 0); + auto pb1 = buffer_prefix(1, null_buffers{}); + BEAST_EXPECT(buffer_size(pb1) == 0); + BEAST_EXPECT(buffer_copy(pb0, pb1) == 0); + + using pb_type = decltype(pb0); + consuming_buffers cb(pb0); + BEAST_EXPECT(buffer_size(cb) == 0); + BEAST_EXPECT(buffer_copy(cb, pb1) == 0); + cb.consume(1); + BEAST_EXPECT(buffer_size(cb) == 0); + BEAST_EXPECT(buffer_copy(cb, pb1) == 0); + + auto pbc = buffer_prefix(2, cb); + BEAST_EXPECT(buffer_size(pbc) == 0); + BEAST_EXPECT(buffer_copy(pbc, cb) == 0); + } + + void testIterator() + { + using boost::asio::buffer_size; + using boost::asio::const_buffer; + char b[3]; + std::array bs{{ + const_buffer{&b[0], 1}, + const_buffer{&b[1], 1}, + const_buffer{&b[2], 1}}}; + auto pb = buffer_prefix(2, bs); + BEAST_EXPECT(bsize1(pb) == 2); + BEAST_EXPECT(bsize2(pb) == 2); + BEAST_EXPECT(bsize3(pb) == 2); + BEAST_EXPECT(bsize4(pb) == 2); + std::size_t n = 0; + for(auto it = pb.end(); it != pb.begin(); --it) + { + decltype(pb)::const_iterator it2(std::move(it)); + BEAST_EXPECT(buffer_size(*it2) == 1); + it = std::move(it2); + ++n; + } + BEAST_EXPECT(n == 2); + } + + void run() override + { + testMatrix(); + testMatrix(); + testNullBuffers(); + testIterator(); + } +}; + +BEAST_DEFINE_TESTSUITE(buffer_prefix,core,beast); + +} // beast diff --git a/src/beast/test/core/buffer_test.hpp b/src/beast/test/core/buffer_test.hpp index d6160cdcd0..3a05b45996 100644 --- a/src/beast/test/core/buffer_test.hpp +++ b/src/beast/test/core/buffer_test.hpp @@ -8,9 +8,13 @@ #ifndef BEAST_TEST_BUFFER_TEST_HPP #define BEAST_TEST_BUFFER_TEST_HPP -#include +#include +#include +#include +#include #include #include +#include #include namespace beast { @@ -18,7 +22,32 @@ namespace test { template typename std::enable_if< - is_ConstBufferSequence::value, + is_const_buffer_sequence::value, +std::string>::type +to_string(ConstBufferSequence const& bs) +{ + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + std::string s; + s.reserve(buffer_size(bs)); + for(boost::asio::const_buffer b : bs) + s.append(buffer_cast(b), + buffer_size(b)); + return s; +} + +template +void +write_buffer(DynamicBuffer& b, string_view s) +{ + b.commit(boost::asio::buffer_copy( + b.prepare(s.size()), boost::asio::buffer( + s.data(), s.size()))); +} + +template +typename std::enable_if< + is_const_buffer_sequence::value, std::size_t>::type buffer_count(ConstBufferSequence const& buffers) { @@ -27,7 +56,7 @@ buffer_count(ConstBufferSequence const& buffers) template typename std::enable_if< - is_ConstBufferSequence::value, + is_const_buffer_sequence::value, std::size_t>::type size_pre(ConstBufferSequence const& buffers) { @@ -46,7 +75,7 @@ size_pre(ConstBufferSequence const& buffers) template typename std::enable_if< - is_ConstBufferSequence::value, + is_const_buffer_sequence::value, std::size_t>::type size_post(ConstBufferSequence const& buffers) { @@ -58,7 +87,7 @@ size_post(ConstBufferSequence const& buffers) template typename std::enable_if< - is_ConstBufferSequence::value, + is_const_buffer_sequence::value, std::size_t>::type size_rev_pre(ConstBufferSequence const& buffers) { @@ -70,7 +99,7 @@ size_rev_pre(ConstBufferSequence const& buffers) template typename std::enable_if< - is_ConstBufferSequence::value, + is_const_buffer_sequence::value, std::size_t>::type size_rev_post(ConstBufferSequence const& buffers) { diff --git a/src/beast/test/core/dynabuf_readstream.cpp b/src/beast/test/core/buffered_read_stream.cpp similarity index 73% rename from src/beast/test/core/dynabuf_readstream.cpp rename to src/beast/test/core/buffered_read_stream.cpp index 1293e52369..81536e1ea1 100644 --- a/src/beast/test/core/dynabuf_readstream.cpp +++ b/src/beast/test/core/buffered_read_stream.cpp @@ -6,9 +6,9 @@ // // Test that header file is self-contained. -#include +#include -#include +#include #include #include #include @@ -17,11 +17,11 @@ namespace beast { -class dynabuf_readstream_test +class buffered_read_stream_test : public unit_test::suite , public test::enable_yield_to { - using self = dynabuf_readstream_test; + using self = buffered_read_stream_test; public: void testSpecialMembers() @@ -29,16 +29,16 @@ public: using socket_type = boost::asio::ip::tcp::socket; boost::asio::io_service ios; { - dynabuf_readstream srs(ios); - dynabuf_readstream srs2(std::move(srs)); + buffered_read_stream srs(ios); + buffered_read_stream srs2(std::move(srs)); srs = std::move(srs2); BEAST_EXPECT(&srs.get_io_service() == &ios); BEAST_EXPECT(&srs.get_io_service() == &srs2.get_io_service()); } { socket_type sock(ios); - dynabuf_readstream srs(sock); - dynabuf_readstream srs2(std::move(srs)); + buffered_read_stream srs(sock); + buffered_read_stream srs2(std::move(srs)); } } @@ -55,11 +55,11 @@ public: { test::fail_stream< test::string_istream> fs(n, ios_, ", world!"); - dynabuf_readstream< - decltype(fs)&, streambuf> srs(fs); + buffered_read_stream< + decltype(fs)&, multi_buffer> srs(fs); srs.buffer().commit(buffer_copy( srs.buffer().prepare(5), buffer("Hello", 5))); - error_code ec; + error_code ec = test::error::fail_error; boost::asio::read(srs, buffer(&s[0], s.size()), ec); if(! ec) { @@ -73,12 +73,12 @@ public: { test::fail_stream< test::string_istream> fs(n, ios_, ", world!"); - dynabuf_readstream< - decltype(fs)&, streambuf> srs(fs); + buffered_read_stream< + decltype(fs)&, multi_buffer> srs(fs); srs.capacity(3); srs.buffer().commit(buffer_copy( srs.buffer().prepare(5), buffer("Hello", 5))); - error_code ec; + error_code ec = test::error::fail_error; boost::asio::read(srs, buffer(&s[0], s.size()), ec); if(! ec) { @@ -92,11 +92,11 @@ public: { test::fail_stream< test::string_istream> fs(n, ios_, ", world!"); - dynabuf_readstream< - decltype(fs)&, streambuf> srs(fs); + buffered_read_stream< + decltype(fs)&, multi_buffer> srs(fs); srs.buffer().commit(buffer_copy( srs.buffer().prepare(5), buffer("Hello", 5))); - error_code ec; + error_code ec = test::error::fail_error; boost::asio::async_read( srs, buffer(&s[0], s.size()), do_yield[ec]); if(! ec) @@ -111,12 +111,12 @@ public: { test::fail_stream< test::string_istream> fs(n, ios_, ", world!"); - dynabuf_readstream< - decltype(fs)&, streambuf> srs(fs); + buffered_read_stream< + decltype(fs)&, multi_buffer> srs(fs); srs.capacity(3); srs.buffer().commit(buffer_copy( srs.buffer().prepare(5), buffer("Hello", 5))); - error_code ec; + error_code ec = test::error::fail_error; boost::asio::async_read( srs, buffer(&s[0], s.size()), do_yield[ec]); if(! ec) @@ -132,11 +132,12 @@ public: { testSpecialMembers(); - yield_to(&self::testRead, this); + yield_to([&](yield_context yield){ + testRead(yield);}); } }; -BEAST_DEFINE_TESTSUITE(dynabuf_readstream,core,beast); +BEAST_DEFINE_TESTSUITE(buffered_read_stream,core,beast); } // beast diff --git a/src/beast/test/core/buffers_adapter.cpp b/src/beast/test/core/buffers_adapter.cpp index 9f8054c6f4..eff1f2238b 100644 --- a/src/beast/test/core/buffers_adapter.cpp +++ b/src/beast/test/core/buffers_adapter.cpp @@ -8,10 +8,13 @@ // Test that header file is self-contained. #include -#include +#include "buffer_test.hpp" +#include +#include #include #include #include +#include #include namespace beast { @@ -24,14 +27,8 @@ public: std::string to_string(ConstBufferSequence const& bs) { - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - std::string s; - s.reserve(buffer_size(bs)); - for(auto const& b : bs) - s.append(buffer_cast(b), - buffer_size(b)); - return s; + return boost::lexical_cast< + std::string>(buffers(bs)); } void testBuffersAdapter() @@ -155,19 +152,19 @@ public: using boost::asio::buffer_size; { using sb_type = boost::asio::streambuf; - sb_type sb; + sb_type b; buffers_adapter< - sb_type::mutable_buffers_type> ba(sb.prepare(3)); + sb_type::mutable_buffers_type> ba(b.prepare(3)); BEAST_EXPECT(buffer_size(ba.prepare(3)) == 3); ba.commit(2); BEAST_EXPECT(buffer_size(ba.data()) == 2); } { - using sb_type = beast::streambuf; - sb_type sb(2); - sb.prepare(3); + using sb_type = beast::multi_buffer; + sb_type b; + b.prepare(3); buffers_adapter< - sb_type::mutable_buffers_type> ba(sb.prepare(8)); + sb_type::mutable_buffers_type> ba(b.prepare(8)); BEAST_EXPECT(buffer_size(ba.prepare(8)) == 8); ba.commit(2); BEAST_EXPECT(buffer_size(ba.data()) == 2); @@ -178,10 +175,22 @@ public: ba.consume(5); } } + + void + testIssue386() + { + using type = boost::asio::streambuf; + type buffer; + buffers_adapter< + type::mutable_buffers_type> ba{buffer.prepare(512)}; + read_size(ba, 1024); + } + void run() override { testBuffersAdapter(); testCommit(); + testIssue386(); } }; diff --git a/src/beast/test/core/consuming_buffers.cpp b/src/beast/test/core/consuming_buffers.cpp index d4a1dc4756..64a96770b4 100644 --- a/src/beast/test/core/consuming_buffers.cpp +++ b/src/beast/test/core/consuming_buffers.cpp @@ -9,7 +9,9 @@ #include #include "buffer_test.hpp" -#include + +#include +#include #include #include #include @@ -34,6 +36,7 @@ public: bool eq(Buffers1 const& lhs, Buffers2 const& rhs) { + using namespace test; return to_string(lhs) == to_string(rhs); } @@ -47,8 +50,24 @@ public: BEAST_EXPECT(test::size_rev_post(buffers) == n); } - void testMatrix() + void + testMembers() { + char buf[12]; + consuming_buffers< + boost::asio::const_buffers_1> cb1{ + boost::in_place_init, buf, sizeof(buf)}; + consuming_buffers< + boost::asio::const_buffers_1> cb2{ + boost::in_place_init, nullptr, 0}; + cb2 = cb1; + cb1 = std::move(cb2); + } + + void + testMatrix() + { + using namespace test; using boost::asio::buffer; using boost::asio::const_buffer; char buf[12]; @@ -89,8 +108,39 @@ public: } }}}} } + + void + testDefaultCtor() + { + using namespace test; + class test_buffer : public boost::asio::const_buffers_1 + { + public: + test_buffer() + : boost::asio::const_buffers_1("\r\n", 2) + { + } + }; - void testNullBuffers() + consuming_buffers cb; + BEAST_EXPECT(to_string(cb) == "\r\n"); + } + + void + testInPlace() + { + using namespace test; + consuming_buffers> cb( + boost::in_place_init, + boost::asio::const_buffers_1("\r", 1), + boost::asio::const_buffers_1("\n", 1)); + BEAST_EXPECT(to_string(cb) == "\r\n"); + } + + void + testNullBuffers() { using boost::asio::buffer_copy; using boost::asio::buffer_size; @@ -103,7 +153,8 @@ public: BEAST_EXPECT(buffer_copy(cb2, cb) == 0); } - void testIterator() + void + testIterator() { using boost::asio::const_buffer; std::array ba; @@ -116,7 +167,10 @@ public: void run() override { + testMembers(); testMatrix(); + testDefaultCtor(); + testInPlace(); testNullBuffers(); testIterator(); } diff --git a/src/beast/test/core/doc_examples.cpp b/src/beast/test/core/doc_examples.cpp new file mode 100644 index 0000000000..ff4d2e4b57 --- /dev/null +++ b/src/beast/test/core/doc_examples.cpp @@ -0,0 +1,85 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include "example/common/detect_ssl.hpp" + +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +class doc_core_samples_test + : public beast::unit_test::suite + , public beast::test::enable_yield_to +{ +public: + void + testDetect() + { + char buf[4]; + buf[0] = 0x16; + buf[1] = 0; + buf[2] = 0; + buf[3] = 0; + BEAST_EXPECT(boost::indeterminate(is_ssl_handshake( + boost::asio::buffer(buf, 0)))); + BEAST_EXPECT(boost::indeterminate(is_ssl_handshake( + boost::asio::buffer(buf, 1)))); + BEAST_EXPECT(boost::indeterminate(is_ssl_handshake( + boost::asio::buffer(buf, 2)))); + BEAST_EXPECT(boost::indeterminate(is_ssl_handshake( + boost::asio::buffer(buf, 3)))); + BEAST_EXPECT(is_ssl_handshake( + boost::asio::buffer(buf, 4))); + buf[0] = 0; + BEAST_EXPECT(! is_ssl_handshake( + boost::asio::buffer(buf, 1))); + } + + void + testRead() + { + { + test::pipe p{ios_}; + ostream(p.server.buffer) << + "\x16***"; + error_code ec; + flat_buffer b; + auto const result = detect_ssl(p.server, b, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(result); + } + yield_to( + [&](yield_context yield) + { + test::pipe p{ios_}; + ostream(p.server.buffer) << + "\x16***"; + error_code ec; + flat_buffer b; + auto const result = async_detect_ssl(p.server, b, yield[ec]); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(result); + }); + } + + void + run() + { + testDetect(); + testRead(); + } +}; + +BEAST_DEFINE_TESTSUITE(doc_core_samples,core,beast); + +} // http +} // beast diff --git a/src/beast/test/core/doc_snippets.cpp b/src/beast/test/core/doc_snippets.cpp new file mode 100644 index 0000000000..0fa3e97c28 --- /dev/null +++ b/src/beast/test/core/doc_snippets.cpp @@ -0,0 +1,65 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +//[snippet_core_1a + +#include +#include +#include +#include + +//] + +using namespace beast; + +namespace doc_core_snippets { + +void fxx() +{ + +//[snippet_core_1b +// +using namespace beast; + +boost::asio::io_service ios; +boost::asio::io_service::work work{ios}; +std::thread t{[&](){ ios.run(); }}; + +error_code ec; +boost::asio::ip::tcp::socket sock{ios}; + +//] + +{ +//[snippet_core_2 + +char const* const host = "www.example.com"; +boost::asio::ip::tcp::resolver r{ios}; +boost::asio::ip::tcp::socket stream{ios}; +boost::asio::connect(stream, r.resolve({host, "http"})); + +// At this point `stream` is a connected to a remote +// host and may be used to perform stream operations. + +//] +} + +} // fxx() + +//[snippet_core_3 + +template +void write_string(SyncWriteStream& stream, string_view s) +{ + static_assert(is_sync_write_stream::value, + "SyncWriteStream requirements not met"); + boost::asio::write(stream, boost::asio::const_buffers_1(s.data(), s.size())); +} + +//] + +} // doc_core_snippets diff --git a/src/beast/test/core/drain_buffer.cpp b/src/beast/test/core/drain_buffer.cpp new file mode 100644 index 0000000000..5e7e35e710 --- /dev/null +++ b/src/beast/test/core/drain_buffer.cpp @@ -0,0 +1,51 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include +#include + +namespace beast { + +static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + +class drain_buffer_test : public beast::unit_test::suite +{ +public: + void + run() override + { + using boost::asio::buffer_size; + drain_buffer b; + BEAST_EXPECT(buffer_size(b.prepare(0)) == 0); + BEAST_EXPECT(buffer_size(b.prepare(100)) == 100); + try + { + b.prepare(b.max_size() + 1); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + b.prepare(10); + BEAST_EXPECT(b.size() == 0); + b.commit(10); + BEAST_EXPECT(b.size() == 0); + b.consume(10); + BEAST_EXPECT(b.size() == 0); + b.consume(1000); + BEAST_EXPECT(b.size() == 0); + } +}; + +BEAST_DEFINE_TESTSUITE(drain_buffer,core,beast); + +} // beast diff --git a/src/beast/test/http/parse.cpp b/src/beast/test/core/file.cpp similarity index 89% rename from src/beast/test/http/parse.cpp rename to src/beast/test/core/file.cpp index b15e9e4362..f483142bcc 100644 --- a/src/beast/test/http/parse.cpp +++ b/src/beast/test/core/file.cpp @@ -6,4 +6,4 @@ // // Test that header file is self-contained. -#include +#include diff --git a/src/beast/test/core/file_posix.cpp b/src/beast/test/core/file_posix.cpp new file mode 100644 index 0000000000..2a8bd046c8 --- /dev/null +++ b/src/beast/test/core/file_posix.cpp @@ -0,0 +1,35 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#if BEAST_USE_POSIX_FILE + +#include "file_test.hpp" + +#include +#include + +namespace beast { + +class file_posix_test + : public beast::unit_test::suite +{ +public: + void + run() + { + doTestFile(*this); + } +}; + +BEAST_DEFINE_TESTSUITE(file_posix,core,beast); + +} // beast + +#endif diff --git a/src/beast/test/core/file_stdio.cpp b/src/beast/test/core/file_stdio.cpp new file mode 100644 index 0000000000..3bb61e242f --- /dev/null +++ b/src/beast/test/core/file_stdio.cpp @@ -0,0 +1,31 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include "file_test.hpp" + +#include +#include + +namespace beast { + +class file_stdio_test + : public beast::unit_test::suite +{ +public: + void + run() + { + doTestFile(*this); + } +}; + +BEAST_DEFINE_TESTSUITE(file_stdio,core,beast); + +} // beast diff --git a/src/beast/test/core/file_test.hpp b/src/beast/test/core/file_test.hpp new file mode 100644 index 0000000000..2249a4c0b0 --- /dev/null +++ b/src/beast/test/core/file_test.hpp @@ -0,0 +1,122 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_TEST_CORE_FILE_TEST_HPP +#define BEAST_TEST_CORE_FILE_TEST_HPP + +#include +#include +#include +#include +#include + +namespace beast { + +template +void +doTestFile(beast::unit_test::suite& test) +{ + BOOST_STATIC_ASSERT(is_file::value); + + error_code ec; + auto const temp = boost::filesystem::unique_path(); + + { + { + File f1; + test.BEAST_EXPECT(! f1.is_open()); + f1.open(temp.string().c_str(), file_mode::write, ec); + test.BEAST_EXPECT(! ec); + File f2{std::move(f1)}; + test.BEAST_EXPECT(! f1.is_open()); + test.BEAST_EXPECT(f2.is_open()); + File f3; + f3 = std::move(f2); + test.BEAST_EXPECT(! f2.is_open()); + test.BEAST_EXPECT(f3.is_open()); + f1.close(ec); + test.BEAST_EXPECT(! ec); + auto const temp2 = boost::filesystem::unique_path(); + f3.open(temp2.string().c_str(), file_mode::read, ec); + test.BEAST_EXPECT(ec); + ec.assign(0, ec.category()); + } + boost::filesystem::remove(temp, ec); + test.BEAST_EXPECT(! ec); + } + + File f; + + test.BEAST_EXPECT(! f.is_open()); + + f.size(ec); + test.BEAST_EXPECT(ec == errc::invalid_argument); + ec.assign(0, ec.category()); + + f.pos(ec); + test.BEAST_EXPECT(ec == errc::invalid_argument); + ec.assign(0, ec.category()); + + f.seek(0, ec); + test.BEAST_EXPECT(ec == errc::invalid_argument); + ec.assign(0, ec.category()); + + f.read(nullptr, 0, ec); + test.BEAST_EXPECT(ec == errc::invalid_argument); + ec.assign(0, ec.category()); + + f.write(nullptr, 0, ec); + test.BEAST_EXPECT(ec == errc::invalid_argument); + ec.assign(0, ec.category()); + + f.open(temp.string().c_str(), file_mode::write, ec); + test.BEAST_EXPECT(! ec); + + std::string const s = "Hello, world!"; + f.write(s.data(), s.size(), ec); + test.BEAST_EXPECT(! ec); + + auto size = f.size(ec); + test.BEAST_EXPECT(! ec); + test.BEAST_EXPECT(size == s.size()); + + auto pos = f.pos(ec); + test.BEAST_EXPECT(! ec); + test.BEAST_EXPECT(pos == size); + + f.close(ec); + test.BEAST_EXPECT(! ec); + + f.open(temp.string().c_str(), file_mode::read, ec); + test.BEAST_EXPECT(! ec); + + std::string buf; + buf.resize(s.size()); + f.read(&buf[0], buf.size(), ec); + test.BEAST_EXPECT(! ec); + test.BEAST_EXPECT(buf == s); + + f.seek(1, ec); + test.BEAST_EXPECT(! ec); + buf.resize(3); + f.read(&buf[0], buf.size(), ec); + test.BEAST_EXPECT(! ec); + test.BEAST_EXPECT(buf == "ell"); + + pos = f.pos(ec); + test.BEAST_EXPECT(! ec); + test.BEAST_EXPECT(pos == 4); + + f.close(ec); + test.BEAST_EXPECT(! ec); + boost::filesystem::remove(temp, ec); + test.BEAST_EXPECT(! ec); +} + +} // beast + +#endif diff --git a/src/beast/test/core/file_win32.cpp b/src/beast/test/core/file_win32.cpp new file mode 100644 index 0000000000..91530a7084 --- /dev/null +++ b/src/beast/test/core/file_win32.cpp @@ -0,0 +1,35 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#if BEAST_USE_WIN32_FILE + +#include "file_test.hpp" + +#include +#include + +namespace beast { + +class file_win32_test + : public beast::unit_test::suite +{ +public: + void + run() + { + doTestFile(*this); + } +}; + +BEAST_DEFINE_TESTSUITE(file_win32,core,beast); + +} // beast + +#endif diff --git a/src/beast/test/core/flat_buffer.cpp b/src/beast/test/core/flat_buffer.cpp new file mode 100644 index 0000000000..fda65623fe --- /dev/null +++ b/src/beast/test/core/flat_buffer.cpp @@ -0,0 +1,352 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include "buffer_test.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { + +static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + +class flat_buffer_test : public beast::unit_test::suite +{ +public: + void + testBuffer() + { + using namespace test; + + using a_t = test::test_allocator; + + // Equal == false + using a_neq_t = test::test_allocator; + + // construction + { + { + flat_buffer b; + BEAST_EXPECT(b.capacity() == 0); + } + { + flat_buffer b{500}; + BEAST_EXPECT(b.capacity() == 0); + BEAST_EXPECT(b.max_size() == 500); + } + { + a_neq_t a1; + basic_flat_buffer b{a1}; + BEAST_EXPECT(b.get_allocator() == a1); + a_neq_t a2; + BEAST_EXPECT(b.get_allocator() != a2); + } + { + a_neq_t a; + basic_flat_buffer b{500, a}; + BEAST_EXPECT(b.capacity() == 0); + BEAST_EXPECT(b.max_size() == 500); + } + } + + // move construction + { + { + basic_flat_buffer b1{30}; + BEAST_EXPECT(b1.get_allocator()->nmove == 0); + ostream(b1) << "Hello"; + basic_flat_buffer b2{std::move(b1)}; + BEAST_EXPECT(b2.get_allocator()->nmove == 1); + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.capacity() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + BEAST_EXPECT(b1.max_size() == b2.max_size()); + } + { + basic_flat_buffer b1{30}; + ostream(b1) << "Hello"; + a_t a; + basic_flat_buffer b2{std::move(b1), a}; + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.capacity() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + BEAST_EXPECT(b1.max_size() == b2.max_size()); + } + { + basic_flat_buffer b1{30}; + ostream(b1) << "Hello"; + a_neq_t a; + basic_flat_buffer b2{std::move(b1), a}; + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.capacity() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + BEAST_EXPECT(b1.max_size() == b2.max_size()); + } + } + + // copy construction + { + basic_flat_buffer b1; + ostream(b1) << "Hello"; + basic_flat_buffer b2(b1); + BEAST_EXPECT(b1.get_allocator() == b2.get_allocator()); + BEAST_EXPECT(to_string(b1.data()) == "Hello"); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + basic_flat_buffer b1; + ostream(b1) << "Hello"; + a_neq_t a; + basic_flat_buffer b2(b1, a); + BEAST_EXPECT(b1.get_allocator() != b2.get_allocator()); + BEAST_EXPECT(to_string(b1.data()) == "Hello"); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + basic_flat_buffer b1; + ostream(b1) << "Hello"; + basic_flat_buffer b2(b1); + BEAST_EXPECT(to_string(b1.data()) == "Hello"); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + basic_flat_buffer b1; + ostream(b1) << "Hello"; + a_t a; + basic_flat_buffer b2(b1, a); + BEAST_EXPECT(b2.get_allocator() == a); + BEAST_EXPECT(to_string(b1.data()) == "Hello"); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + + // move assignment + { + { + flat_buffer b1; + ostream(b1) << "Hello"; + flat_buffer b2; + b2 = std::move(b1); + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.capacity() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + using na_t = test::test_allocator; + basic_flat_buffer b1; + ostream(b1) << "Hello"; + basic_flat_buffer b2; + b2 = std::move(b1); + BEAST_EXPECT(b1.get_allocator() == b2.get_allocator()); + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.capacity() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + using na_t = test::test_allocator; + basic_flat_buffer b1; + ostream(b1) << "Hello"; + basic_flat_buffer b2; + b2 = std::move(b1); + BEAST_EXPECT(b1.get_allocator() != b2.get_allocator()); + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.capacity() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + // propagate_on_container_move_assignment : true + using pocma_t = test::test_allocator; + basic_flat_buffer b1; + ostream(b1) << "Hello"; + basic_flat_buffer b2; + b2 = std::move(b1); + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + // propagate_on_container_move_assignment : false + using pocma_t = test::test_allocator; + basic_flat_buffer b1; + ostream(b1) << "Hello"; + basic_flat_buffer b2; + b2 = std::move(b1); + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + } + + // copy assignment + { + { + flat_buffer b1; + ostream(b1) << "Hello"; + flat_buffer b2; + b2 = b1; + BEAST_EXPECT(to_string(b1.data()) == "Hello"); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + basic_flat_buffer b3; + b3 = b2; + BEAST_EXPECT(to_string(b3.data()) == "Hello"); + } + { + // propagate_on_container_copy_assignment : true + using pocca_t = test::test_allocator; + basic_flat_buffer b1; + ostream(b1) << "Hello"; + basic_flat_buffer b2; + b2 = b1; + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + // propagate_on_container_copy_assignment : false + using pocca_t = test::test_allocator; + basic_flat_buffer b1; + ostream(b1) << "Hello"; + basic_flat_buffer b2; + b2 = b1; + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + } + + // operations + { + string_view const s = "Hello, world!"; + flat_buffer b1{64}; + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.max_size() == 64); + BEAST_EXPECT(b1.capacity() == 0); + ostream(b1) << s; + BEAST_EXPECT(to_string(b1.data()) == s); + { + flat_buffer b2{b1}; + BEAST_EXPECT(to_string(b2.data()) == s); + b2.consume(7); + BEAST_EXPECT(to_string(b2.data()) == s.substr(7)); + } + { + flat_buffer b2{64}; + b2 = b1; + BEAST_EXPECT(to_string(b2.data()) == s); + b2.consume(7); + BEAST_EXPECT(to_string(b2.data()) == s.substr(7)); + } + } + + // cause memmove + { + flat_buffer b{20}; + ostream(b) << "12345"; + b.consume(3); + ostream(b) << "67890123"; + BEAST_EXPECT(to_string(b.data()) == "4567890123"); + } + + // read_size + { + flat_buffer b{10}; + BEAST_EXPECT(read_size(b, 512) == 10); + b.prepare(4); + b.commit(4); + BEAST_EXPECT(read_size(b, 512) == 6); + b.consume(2); + BEAST_EXPECT(read_size(b, 512) == 8); + b.prepare(8); + b.commit(8); + BEAST_EXPECT(read_size(b, 512) == 0); + } + + // swap + { + { + basic_flat_buffer b1; + ostream(b1) << "Hello"; + basic_flat_buffer b2; + BEAST_EXPECT(b1.get_allocator() != b2.get_allocator()); + swap(b1, b2); + BEAST_EXPECT(b1.get_allocator() != b2.get_allocator()); + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.capacity() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + using na_t = test::test_allocator; + na_t a1; + basic_flat_buffer b1{a1}; + na_t a2; + ostream(b1) << "Hello"; + basic_flat_buffer b2{a2}; + BEAST_EXPECT(b1.get_allocator() == a1); + BEAST_EXPECT(b2.get_allocator() == a2); + swap(b1, b2); + BEAST_EXPECT(b1.get_allocator() == b2.get_allocator()); + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.capacity() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + } + + // prepare + { + flat_buffer b{100}; + b.prepare(10); + b.commit(10); + b.prepare(5); + BEAST_EXPECT(b.capacity() >= 5); + try + { + b.prepare(1000); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + + // shrink to fit + { + flat_buffer b; + BEAST_EXPECT(b.capacity() == 0); + b.prepare(50); + BEAST_EXPECT(b.capacity() == 50); + b.commit(50); + BEAST_EXPECT(b.capacity() == 50); + b.prepare(75); + BEAST_EXPECT(b.capacity() >= 125); + b.shrink_to_fit(); + BEAST_EXPECT(b.capacity() == b.size()); + + } + } + + void + run() override + { + testBuffer(); + } +}; + +BEAST_DEFINE_TESTSUITE(flat_buffer,core,beast); + +} // beast diff --git a/src/beast/test/core/get_lowest_layer.cpp b/src/beast/test/core/get_lowest_layer.cpp deleted file mode 100644 index 11538a83e4..0000000000 --- a/src/beast/test/core/get_lowest_layer.cpp +++ /dev/null @@ -1,88 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -// Test that header file is self-contained. -#include - -#include -#include - -namespace beast { -namespace detail { - -class get_lowest_layer_test - : public beast::unit_test::suite -{ -public: - struct F1 - { - }; - - struct F2 - { - }; - - template - struct F3 - { - using next_layer_type = - typename std::remove_reference::type; - - using lowest_layer_type = typename - get_lowest_layer::type; - }; - - template - struct F4 - { - using next_layer_type = - typename std::remove_reference::type; - - using lowest_layer_type = typename - get_lowest_layer::type; - }; - - void - run() - { - static_assert(! has_lowest_layer::value, ""); - static_assert(! has_lowest_layer::value, ""); - static_assert(has_lowest_layer>::value, ""); - static_assert(has_lowest_layer>>::value, ""); - - static_assert(std::is_same< - get_lowest_layer::type, F1>::value, ""); - - static_assert(std::is_same< - get_lowest_layer::type, F2>::value, ""); - - static_assert(std::is_same< - get_lowest_layer>::type, F1>::value, ""); - - static_assert(std::is_same< - get_lowest_layer>::type, F2>::value, ""); - - static_assert(std::is_same< - get_lowest_layer>::type, F1>::value, ""); - - static_assert(std::is_same< - get_lowest_layer>::type, F2>::value, ""); - - static_assert(std::is_same< - get_lowest_layer>>::type, F1>::value, ""); - - static_assert(std::is_same< - get_lowest_layer>>::type, F2>::value, ""); - - pass(); - } -}; - -BEAST_DEFINE_TESTSUITE(get_lowest_layer,core,beast); - -} // detail -} // beast diff --git a/src/beast/test/core/handler_alloc.cpp b/src/beast/test/core/handler_alloc.cpp index c657d5fb31..64acc0749c 100644 --- a/src/beast/test/core/handler_alloc.cpp +++ b/src/beast/test/core/handler_alloc.cpp @@ -9,6 +9,7 @@ #include #include +#include #include namespace beast { @@ -24,9 +25,23 @@ public: } }; + // https://github.com/vinniefalco/Beast/issues/432 + void + testRegression432() + { + handler h; + handler_alloc a{h}; + std::list> v{a}; + v.push_back(1); + v.push_back(2); + v.push_back(3); + } + void run() override { + testRegression432(); + handler h; handler h2; handler_alloc a1{h}; diff --git a/src/beast/test/core/is_call_possible.cpp b/src/beast/test/core/is_call_possible.cpp deleted file mode 100644 index 5849793ddc..0000000000 --- a/src/beast/test/core/is_call_possible.cpp +++ /dev/null @@ -1,56 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -// Test that header file is self-contained. -#include - -namespace beast { -namespace detail { -namespace { - -struct is_call_possible_udt1 -{ - void operator()(int) const; -}; - -struct is_call_possible_udt2 -{ - int operator()(int) const; -}; - -struct is_call_possible_udt3 -{ - int operator()(int); -}; - -#ifndef __INTELLISENSE__ -// VFALCO Fails to compile with Intellisense -static_assert(is_call_possible< - is_call_possible_udt1, void(int)>::value, ""); - -static_assert(! is_call_possible< - is_call_possible_udt1, void(void)>::value, ""); - -static_assert(is_call_possible< - is_call_possible_udt2, int(int)>::value, ""); - -static_assert(! is_call_possible< - is_call_possible_udt2, int(void)>::value, ""); - -static_assert(! is_call_possible< - is_call_possible_udt2, void(void)>::value, ""); - -static_assert(is_call_possible< - is_call_possible_udt3, int(int)>::value, ""); - -static_assert(! is_call_possible< - is_call_possible_udt3 const, int(int)>::value, ""); -#endif - -} -} // detail -} // beast diff --git a/src/beast/test/core/multi_buffer.cpp b/src/beast/test/core/multi_buffer.cpp new file mode 100644 index 0000000000..f2e3dbf3b3 --- /dev/null +++ b/src/beast/test/core/multi_buffer.cpp @@ -0,0 +1,592 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include "buffer_test.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { + +BOOST_STATIC_ASSERT(is_dynamic_buffer::value); + +class multi_buffer_test : public beast::unit_test::suite +{ +public: + template + static + bool + eq(basic_multi_buffer const& mb1, + basic_multi_buffer const& mb2) + { + return test::to_string(mb1.data()) == + test::to_string(mb2.data()); + } + + template + void + expect_size(std::size_t n, ConstBufferSequence const& buffers) + { + BEAST_EXPECT(test::size_pre(buffers) == n); + BEAST_EXPECT(test::size_post(buffers) == n); + BEAST_EXPECT(test::size_rev_pre(buffers) == n); + BEAST_EXPECT(test::size_rev_post(buffers) == n); + } + + template + static + void + self_assign(U& u, V&& v) + { + u = std::forward(v); + } + + void + testMatrix1() + { + using namespace test; + using boost::asio::buffer; + std::string const s = "Hello, world"; + BEAST_EXPECT(s.size() == 12); + for(std::size_t i = 1; i < 12; ++i) { + for(std::size_t x = 1; x < 4; ++x) { + for(std::size_t y = 1; y < 4; ++y) { + std::size_t z = s.size() - (x + y); + { + multi_buffer b; + b.commit(buffer_copy(b.prepare(x), buffer(s.data(), x))); + b.commit(buffer_copy(b.prepare(y), buffer(s.data()+x, y))); + b.commit(buffer_copy(b.prepare(z), buffer(s.data()+x+y, z))); + BEAST_EXPECT(to_string(b.data()) == s); + { + multi_buffer mb2{b}; + BEAST_EXPECT(eq(b, mb2)); + } + { + multi_buffer mb2; + mb2 = b; + BEAST_EXPECT(eq(b, mb2)); + } + { + multi_buffer mb2{std::move(b)}; + BEAST_EXPECT(to_string(mb2.data()) == s); + expect_size(0, b.data()); + b = std::move(mb2); + BEAST_EXPECT(to_string(b.data()) == s); + expect_size(0, mb2.data()); + } + self_assign(b, b); + BEAST_EXPECT(to_string(b.data()) == s); + self_assign(b, std::move(b)); + BEAST_EXPECT(to_string(b.data()) == s); + } + }}} + } + + void + testMatrix2() + { + using namespace test; + using boost::asio::buffer; + using boost::asio::buffer_size; + std::string const s = "Hello, world"; + BEAST_EXPECT(s.size() == 12); + for(std::size_t i = 1; i < 12; ++i) { + for(std::size_t x = 1; x < 4; ++x) { + for(std::size_t y = 1; y < 4; ++y) { + for(std::size_t t = 1; t < 4; ++ t) { + for(std::size_t u = 1; u < 4; ++ u) { + std::size_t z = s.size() - (x + y); + std::size_t v = s.size() - (t + u); + { + multi_buffer b; + { + auto d = b.prepare(z); + BEAST_EXPECT(buffer_size(d) == z); + } + { + auto d = b.prepare(0); + BEAST_EXPECT(buffer_size(d) == 0); + } + { + auto d = b.prepare(y); + BEAST_EXPECT(buffer_size(d) == y); + } + { + auto d = b.prepare(x); + BEAST_EXPECT(buffer_size(d) == x); + b.commit(buffer_copy(d, buffer(s.data(), x))); + } + BEAST_EXPECT(b.size() == x); + BEAST_EXPECT(buffer_size(b.data()) == b.size()); + { + auto d = b.prepare(x); + BEAST_EXPECT(buffer_size(d) == x); + } + { + auto d = b.prepare(0); + BEAST_EXPECT(buffer_size(d) == 0); + } + { + auto d = b.prepare(z); + BEAST_EXPECT(buffer_size(d) == z); + } + { + auto d = b.prepare(y); + BEAST_EXPECT(buffer_size(d) == y); + b.commit(buffer_copy(d, buffer(s.data()+x, y))); + } + b.commit(1); + BEAST_EXPECT(b.size() == x + y); + BEAST_EXPECT(buffer_size(b.data()) == b.size()); + { + auto d = b.prepare(x); + BEAST_EXPECT(buffer_size(d) == x); + } + { + auto d = b.prepare(y); + BEAST_EXPECT(buffer_size(d) == y); + } + { + auto d = b.prepare(0); + BEAST_EXPECT(buffer_size(d) == 0); + } + { + auto d = b.prepare(z); + BEAST_EXPECT(buffer_size(d) == z); + b.commit(buffer_copy(d, buffer(s.data()+x+y, z))); + } + b.commit(2); + BEAST_EXPECT(b.size() == x + y + z); + BEAST_EXPECT(buffer_size(b.data()) == b.size()); + BEAST_EXPECT(to_string(b.data()) == s); + b.consume(t); + { + auto d = b.prepare(0); + BEAST_EXPECT(buffer_size(d) == 0); + } + BEAST_EXPECT(to_string(b.data()) == s.substr(t, std::string::npos)); + b.consume(u); + BEAST_EXPECT(to_string(b.data()) == s.substr(t + u, std::string::npos)); + b.consume(v); + BEAST_EXPECT(to_string(b.data()) == ""); + b.consume(1); + { + auto d = b.prepare(0); + BEAST_EXPECT(buffer_size(d) == 0); + } + } + }}}}} + } + + void + testIterators() + { + using boost::asio::buffer_size; + multi_buffer b; + b.prepare(1); + b.commit(1); + b.prepare(2); + b.commit(2); + expect_size(3, b.data()); + b.prepare(1); + expect_size(3, b.prepare(3)); + b.commit(2); + } + + void + testMembers() + { + using namespace test; + + // compare equal + using equal_t = test::test_allocator; + + // compare not equal + using unequal_t = test::test_allocator; + + // construction + { + { + multi_buffer b; + BEAST_EXPECT(b.capacity() == 0); + } + { + multi_buffer b{500}; + BEAST_EXPECT(b.capacity() == 0); + BEAST_EXPECT(b.max_size() == 500); + } + { + unequal_t a1; + basic_multi_buffer b{a1}; + BEAST_EXPECT(b.get_allocator() == a1); + BEAST_EXPECT(b.get_allocator() != unequal_t{}); + } + } + + // move construction + { + { + basic_multi_buffer b1{30}; + BEAST_EXPECT(b1.get_allocator()->nmove == 0); + ostream(b1) << "Hello"; + basic_multi_buffer b2{std::move(b1)}; + BEAST_EXPECT(b2.get_allocator()->nmove == 1); + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.capacity() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + BEAST_EXPECT(b1.max_size() == b2.max_size()); + } + // allocators equal + { + basic_multi_buffer b1{30}; + ostream(b1) << "Hello"; + equal_t a; + basic_multi_buffer b2{std::move(b1), a}; + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.capacity() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + BEAST_EXPECT(b1.max_size() == b2.max_size()); + } + { + // allocators unequal + basic_multi_buffer b1{30}; + ostream(b1) << "Hello"; + unequal_t a; + basic_multi_buffer b2{std::move(b1), a}; + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.capacity() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + BEAST_EXPECT(b1.max_size() == b2.max_size()); + } + } + + // copy construction + { + { + basic_multi_buffer b1; + ostream(b1) << "Hello"; + basic_multi_buffer b2{b1}; + BEAST_EXPECT(b1.get_allocator() == b2.get_allocator()); + BEAST_EXPECT(to_string(b1.data()) == "Hello"); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + basic_multi_buffer b1; + ostream(b1) << "Hello"; + unequal_t a; + basic_multi_buffer b2(b1, a); + BEAST_EXPECT(b1.get_allocator() != b2.get_allocator()); + BEAST_EXPECT(to_string(b1.data()) == "Hello"); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + basic_multi_buffer b1; + ostream(b1) << "Hello"; + basic_multi_buffer b2(b1); + BEAST_EXPECT(to_string(b1.data()) == "Hello"); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + basic_multi_buffer b1; + ostream(b1) << "Hello"; + equal_t a; + basic_multi_buffer b2(b1, a); + BEAST_EXPECT(b2.get_allocator() == a); + BEAST_EXPECT(to_string(b1.data()) == "Hello"); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + } + + // move assignment + { + { + multi_buffer b1; + ostream(b1) << "Hello"; + multi_buffer b2; + b2 = std::move(b1); + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.capacity() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + // propagate_on_container_move_assignment : true + using pocma_t = test::test_allocator; + basic_multi_buffer b1; + ostream(b1) << "Hello"; + basic_multi_buffer b2; + b2 = std::move(b1); + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + // propagate_on_container_move_assignment : false + using pocma_t = test::test_allocator; + basic_multi_buffer b1; + ostream(b1) << "Hello"; + basic_multi_buffer b2; + b2 = std::move(b1); + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + } + + // copy assignment + { + { + multi_buffer b1; + ostream(b1) << "Hello"; + multi_buffer b2; + b2 = b1; + BEAST_EXPECT(to_string(b1.data()) == "Hello"); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + basic_multi_buffer b3; + b3 = b2; + BEAST_EXPECT(to_string(b3.data()) == "Hello"); + } + { + // propagate_on_container_copy_assignment : true + using pocca_t = test::test_allocator; + basic_multi_buffer b1; + ostream(b1) << "Hello"; + basic_multi_buffer b2; + b2 = b1; + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + // propagate_on_container_copy_assignment : false + using pocca_t = test::test_allocator; + basic_multi_buffer b1; + ostream(b1) << "Hello"; + basic_multi_buffer b2; + b2 = b1; + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + } + + // prepare + { + { + multi_buffer b{100}; + try + { + b.prepare(b.max_size() + 1); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + string_view const s = "Hello, world!"; + multi_buffer b1{64}; + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.max_size() == 64); + BEAST_EXPECT(b1.capacity() == 0); + ostream(b1) << s; + BEAST_EXPECT(to_string(b1.data()) == s); + { + multi_buffer b2{b1}; + BEAST_EXPECT(to_string(b2.data()) == s); + b2.consume(7); + BEAST_EXPECT(to_string(b2.data()) == s.substr(7)); + } + { + multi_buffer b2{64}; + b2 = b1; + BEAST_EXPECT(to_string(b2.data()) == s); + b2.consume(7); + BEAST_EXPECT(to_string(b2.data()) == s.substr(7)); + } + } + { + multi_buffer b; + b.prepare(1000); + BEAST_EXPECT(b.capacity() >= 1000); + b.commit(1); + BEAST_EXPECT(b.size() == 1); + BEAST_EXPECT(b.capacity() >= 1000); + b.prepare(1000); + BEAST_EXPECT(b.size() == 1); + BEAST_EXPECT(b.capacity() >= 1000); + b.prepare(1500); + BEAST_EXPECT(b.capacity() >= 1000); + } + { + multi_buffer b; + b.prepare(1000); + BEAST_EXPECT(b.capacity() >= 1000); + b.commit(1); + BEAST_EXPECT(b.capacity() >= 1000); + b.prepare(1000); + BEAST_EXPECT(b.capacity() >= 1000); + b.prepare(2000); + BEAST_EXPECT(b.capacity() >= 2000); + b.commit(2); + } + { + multi_buffer b; + b.prepare(1000); + BEAST_EXPECT(b.capacity() >= 1000); + b.prepare(2000); + BEAST_EXPECT(b.capacity() >= 2000); + b.prepare(4000); + BEAST_EXPECT(b.capacity() >= 4000); + b.prepare(50); + BEAST_EXPECT(b.capacity() >= 50); + } + } + + // commit + { + multi_buffer b; + b.prepare(1000); + BEAST_EXPECT(b.capacity() >= 1000); + b.commit(1000); + BEAST_EXPECT(b.size() == 1000); + BEAST_EXPECT(b.capacity() >= 1000); + b.consume(1000); + BEAST_EXPECT(b.size() == 0); + BEAST_EXPECT(b.capacity() == 0); + b.prepare(1000); + b.commit(650); + BEAST_EXPECT(b.size() == 650); + BEAST_EXPECT(b.capacity() >= 1000); + b.prepare(1000); + BEAST_EXPECT(b.capacity() >= 1650); + b.commit(100); + BEAST_EXPECT(b.size() == 750); + BEAST_EXPECT(b.capacity() >= 1000); + b.prepare(1000); + BEAST_EXPECT(b.capacity() >= 2000); + b.commit(500); + } + + // consume + { + multi_buffer b; + b.prepare(1000); + BEAST_EXPECT(b.capacity() >= 1000); + b.commit(1000); + BEAST_EXPECT(b.size() == 1000); + BEAST_EXPECT(b.capacity() >= 1000); + b.prepare(1000); + BEAST_EXPECT(b.capacity() >= 2000); + b.commit(750); + BEAST_EXPECT(b.size() == 1750); + b.consume(500); + BEAST_EXPECT(b.size() == 1250); + b.consume(500); + BEAST_EXPECT(b.size() == 750); + b.prepare(250); + b.consume(750); + BEAST_EXPECT(b.size() == 0); + b.prepare(1000); + b.commit(800); + BEAST_EXPECT(b.size() == 800); + b.prepare(1000); + b.commit(600); + BEAST_EXPECT(b.size() == 1400); + b.consume(1400); + BEAST_EXPECT(b.size() == 0); + } + + // swap + { + { + // propagate_on_container_swap : true + using pocs_t = test::test_allocator; + pocs_t a1, a2; + BEAST_EXPECT(a1 != a2); + basic_multi_buffer b1{a1}; + ostream(b1) << "Hello"; + basic_multi_buffer b2{a2}; + BEAST_EXPECT(b1.get_allocator() == a1); + BEAST_EXPECT(b2.get_allocator() == a2); + swap(b1, b2); + BEAST_EXPECT(b1.get_allocator() == a2); + BEAST_EXPECT(b2.get_allocator() == a1); + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + swap(b1, b2); + BEAST_EXPECT(b1.get_allocator() == a1); + BEAST_EXPECT(b2.get_allocator() == a2); + BEAST_EXPECT(to_string(b1.data()) == "Hello"); + BEAST_EXPECT(b2.size() == 0); + } + { + // propagate_on_container_swap : false + using pocs_t = test::test_allocator; + pocs_t a1, a2; + BEAST_EXPECT(a1 == a2); + BEAST_EXPECT(a1.id() != a2.id()); + basic_multi_buffer b1{a1}; + ostream(b1) << "Hello"; + basic_multi_buffer b2{a2}; + BEAST_EXPECT(b1.get_allocator() == a1); + BEAST_EXPECT(b2.get_allocator() == a2); + swap(b1, b2); + BEAST_EXPECT(b1.get_allocator().id() == a1.id()); + BEAST_EXPECT(b2.get_allocator().id() == a2.id()); + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + swap(b1, b2); + BEAST_EXPECT(b1.get_allocator().id() == a1.id()); + BEAST_EXPECT(b2.get_allocator().id() == a2.id()); + BEAST_EXPECT(to_string(b1.data()) == "Hello"); + BEAST_EXPECT(b2.size() == 0); + } + } + + // read_size + { + multi_buffer b{10}; + BEAST_EXPECT(read_size(b, 512) == 10); + b.prepare(4); + b.commit(4); + BEAST_EXPECT(read_size(b, 512) == 6); + b.consume(2); + BEAST_EXPECT(read_size(b, 512) == 8); + b.prepare(8); + b.commit(8); + BEAST_EXPECT(read_size(b, 512) == 0); + } + } + + void + run() override + { + testMatrix1(); + testMatrix2(); + testIterators(); + testMembers(); + } +}; + +BEAST_DEFINE_TESTSUITE(multi_buffer,core,beast); + +} // beast diff --git a/src/beast/test/core/ostream.cpp b/src/beast/test/core/ostream.cpp new file mode 100644 index 0000000000..25b805ec31 --- /dev/null +++ b/src/beast/test/core/ostream.cpp @@ -0,0 +1,53 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include +#include +#include +#include + +namespace beast { + +class ostream_test : public beast::unit_test::suite +{ +public: + void + run() override + { + { + multi_buffer b; + auto os = ostream(b); + os << "Hello, world!\n"; + os.flush(); + BEAST_EXPECT(boost::lexical_cast( + buffers(b.data())) == "Hello, world!\n"); + auto os2 = std::move(os); + } + { + auto const s = + "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" + "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" + "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" + "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" + "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" + "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" + "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" + "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" "0123456789abcdef"; + multi_buffer b; + ostream(b) << s; + BEAST_EXPECT(boost::lexical_cast( + buffers(b.data())) == s); + } + } +}; + +BEAST_DEFINE_TESTSUITE(ostream,core,beast); + +} // beast diff --git a/src/beast/test/core/prepare_buffers.cpp b/src/beast/test/core/prepare_buffers.cpp index b9d481e3dd..f7db38403b 100644 --- a/src/beast/test/core/prepare_buffers.cpp +++ b/src/beast/test/core/prepare_buffers.cpp @@ -8,170 +8,3 @@ // Test that header file is self-contained. #include -#include -#include -#include -#include - -namespace beast { - -class prepare_buffers_test : public beast::unit_test::suite -{ -public: - template - static - std::size_t - bsize1(ConstBufferSequence const& bs) - { - using boost::asio::buffer_size; - std::size_t n = 0; - for(auto it = bs.begin(); it != bs.end(); ++it) - n += buffer_size(*it); - return n; - } - - template - static - std::size_t - bsize2(ConstBufferSequence const& bs) - { - using boost::asio::buffer_size; - std::size_t n = 0; - for(auto it = bs.begin(); it != bs.end(); it++) - n += buffer_size(*it); - return n; - } - - template - static - std::size_t - bsize3(ConstBufferSequence const& bs) - { - using boost::asio::buffer_size; - std::size_t n = 0; - for(auto it = bs.end(); it != bs.begin();) - n += buffer_size(*--it); - return n; - } - - template - static - std::size_t - bsize4(ConstBufferSequence const& bs) - { - using boost::asio::buffer_size; - std::size_t n = 0; - for(auto it = bs.end(); it != bs.begin();) - { - it--; - n += buffer_size(*it); - } - return n; - } - - template - static - std::string - to_string(ConstBufferSequence const& bs) - { - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - std::string s; - s.reserve(buffer_size(bs)); - for(auto const& b : bs) - s.append(buffer_cast(b), - buffer_size(b)); - return s; - } - - template - void testMatrix() - { - using boost::asio::buffer_size; - std::string s = "Hello, world"; - BEAST_EXPECT(s.size() == 12); - for(std::size_t x = 1; x < 4; ++x) { - for(std::size_t y = 1; y < 4; ++y) { - std::size_t z = s.size() - (x + y); - { - std::array bs{{ - BufferType{&s[0], x}, - BufferType{&s[x], y}, - BufferType{&s[x+y], z}}}; - for(std::size_t i = 0; i <= s.size() + 1; ++i) - { - auto pb = prepare_buffers(i, bs); - BEAST_EXPECT(to_string(pb) == s.substr(0, i)); - auto pb2 = pb; - BEAST_EXPECT(to_string(pb2) == to_string(pb)); - pb = prepare_buffers(0, bs); - pb2 = pb; - BEAST_EXPECT(buffer_size(pb2) == 0); - pb2 = prepare_buffers(i, bs); - BEAST_EXPECT(to_string(pb2) == s.substr(0, i)); - } - } - }} - } - - void testNullBuffers() - { - using boost::asio::buffer_copy; - using boost::asio::buffer_size; - using boost::asio::null_buffers; - auto pb0 = prepare_buffers(0, null_buffers{}); - BEAST_EXPECT(buffer_size(pb0) == 0); - auto pb1 = prepare_buffers(1, null_buffers{}); - BEAST_EXPECT(buffer_size(pb1) == 0); - BEAST_EXPECT(buffer_copy(pb0, pb1) == 0); - - using pb_type = decltype(pb0); - consuming_buffers cb(pb0); - BEAST_EXPECT(buffer_size(cb) == 0); - BEAST_EXPECT(buffer_copy(cb, pb1) == 0); - cb.consume(1); - BEAST_EXPECT(buffer_size(cb) == 0); - BEAST_EXPECT(buffer_copy(cb, pb1) == 0); - - auto pbc = prepare_buffers(2, cb); - BEAST_EXPECT(buffer_size(pbc) == 0); - BEAST_EXPECT(buffer_copy(pbc, cb) == 0); - } - - void testIterator() - { - using boost::asio::buffer_size; - using boost::asio::const_buffer; - char b[3]; - std::array bs{{ - const_buffer{&b[0], 1}, - const_buffer{&b[1], 1}, - const_buffer{&b[2], 1}}}; - auto pb = prepare_buffers(2, bs); - BEAST_EXPECT(bsize1(pb) == 2); - BEAST_EXPECT(bsize2(pb) == 2); - BEAST_EXPECT(bsize3(pb) == 2); - BEAST_EXPECT(bsize4(pb) == 2); - std::size_t n = 0; - for(auto it = pb.end(); it != pb.begin(); --it) - { - decltype(pb)::const_iterator it2(std::move(it)); - BEAST_EXPECT(buffer_size(*it2) == 1); - it = std::move(it2); - ++n; - } - BEAST_EXPECT(n == 2); - } - - void run() override - { - testMatrix(); - testMatrix(); - testNullBuffers(); - testIterator(); - } -}; - -BEAST_DEFINE_TESTSUITE(prepare_buffers,core,beast); - -} // beast diff --git a/src/beast/test/core/read_size.cpp b/src/beast/test/core/read_size.cpp new file mode 100644 index 0000000000..40a01c0c4e --- /dev/null +++ b/src/beast/test/core/read_size.cpp @@ -0,0 +1,44 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include +#include +#include +#include + +#include + +namespace beast { + +class read_size_test : public beast::unit_test::suite +{ +public: + template + void + check() + { + DynamicBuffer buffer; + read_size(buffer, 65536); + pass(); + } + + void + run() override + { + check(); + check(); + check(); + check(); + } +}; + +BEAST_DEFINE_TESTSUITE(read_size,core,beast); + +} // beast diff --git a/src/beast/test/core/span.cpp b/src/beast/test/core/span.cpp new file mode 100644 index 0000000000..ec8c7d3e04 --- /dev/null +++ b/src/beast/test/core/span.cpp @@ -0,0 +1,56 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include +#include + +namespace beast { + +class span_test : public beast::unit_test::suite +{ +public: + BOOST_STATIC_ASSERT( + detail::is_contiguous_container< + string_view, char const>::value); + + struct base {}; + struct derived : base {}; + + BOOST_STATIC_ASSERT(detail::is_contiguous_container< + std::vector, char>::value); + + BOOST_STATIC_ASSERT(detail::is_contiguous_container< + std::vector, char const>::value); + + BOOST_STATIC_ASSERT(! detail::is_contiguous_container< + std::vector, base>::value); + + BOOST_STATIC_ASSERT(! detail::is_contiguous_container< + std::vector, base const>::value); + + void + testSpan() + { + span sp{"hello", 5}; + BEAST_EXPECT(sp.size() == 5); + std::string s("world"); + sp = s; + } + + void + run() override + { + testSpan(); + } +}; + +BEAST_DEFINE_TESTSUITE(span,core,beast); + +} // beast diff --git a/src/beast/test/core/static_streambuf.cpp b/src/beast/test/core/static_buffer.cpp similarity index 56% rename from src/beast/test/core/static_streambuf.cpp rename to src/beast/test/core/static_buffer.cpp index 0add8c4dcf..fb0f4f628e 100644 --- a/src/beast/test/core/static_streambuf.cpp +++ b/src/beast/test/core/static_buffer.cpp @@ -6,34 +6,28 @@ // // Test that header file is self-contained. -#include +#include +#include "buffer_test.hpp" + +#include +#include #include -#include #include namespace beast { -class static_streambuf_test : public beast::unit_test::suite +static_assert( + is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + +class static_buffer_test : public beast::unit_test::suite { public: - template - static - std::string - to_string(ConstBufferSequence const& bs) - { - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - std::string s; - s.reserve(buffer_size(bs)); - for(auto const& b : bs) - s.append(buffer_cast(b), - buffer_size(b)); - return s; - } - - void testStaticStreambuf() + void + testStaticBuffer() { + using namespace test; using boost::asio::buffer; using boost::asio::buffer_cast; using boost::asio::buffer_size; @@ -50,7 +44,7 @@ public: std::size_t v = sizeof(buf) - (t + u); { std::memset(buf, 0, sizeof(buf)); - static_streambuf_n ba; + static_buffer_n ba; { auto d = ba.prepare(z); BEAST_EXPECT(buffer_size(d) == z); @@ -128,7 +122,7 @@ public: } try { - ba.prepare(1); + ba.prepare(ba.capacity() - ba.size() + 1); fail(); } catch(...) @@ -139,68 +133,101 @@ public: }}}}}} } - void testIterators() + void + testBuffer() { - static_streambuf_n<2> ba; + using namespace test; + string_view const s = "Hello, world!"; + + // static_buffer { - auto mb = ba.prepare(2); - std::size_t n; - n = 0; - for(auto it = mb.begin(); - it != mb.end(); it++) - ++n; - BEAST_EXPECT(n == 1); - mb = ba.prepare(2); - n = 0; - for(auto it = mb.begin(); - it != mb.end(); ++it) - ++n; - BEAST_EXPECT(n == 1); - mb = ba.prepare(2); - n = 0; - for(auto it = mb.end(); - it != mb.begin(); it--) - ++n; - BEAST_EXPECT(n == 1); - mb = ba.prepare(2); - n = 0; - for(auto it = mb.end(); - it != mb.begin(); --it) - ++n; - BEAST_EXPECT(n == 1); + char buf[64]; + static_buffer b{buf, sizeof(buf)}; + ostream(b) << s; + BEAST_EXPECT(to_string(b.data()) == s); + b.consume(b.size()); + BEAST_EXPECT(to_string(b.data()) == ""); + } + + // static_buffer_n + { + static_buffer_n<64> b1; + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.max_size() == 64); + BEAST_EXPECT(b1.capacity() == 64); + ostream(b1) << s; + BEAST_EXPECT(to_string(b1.data()) == s); + { + static_buffer_n<64> b2{b1}; + BEAST_EXPECT(to_string(b2.data()) == s); + b2.consume(7); + BEAST_EXPECT(to_string(b2.data()) == s.substr(7)); + } + { + static_buffer_n<64> b2; + b2 = b1; + BEAST_EXPECT(to_string(b2.data()) == s); + b2.consume(7); + BEAST_EXPECT(to_string(b2.data()) == s.substr(7)); + } + } + + // cause memmove + { + static_buffer_n<10> b; + write_buffer(b, "12345"); + b.consume(3); + write_buffer(b, "67890123"); + BEAST_EXPECT(to_string(b.data()) == "4567890123"); + try + { + b.prepare(1); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + + // read_size + { + static_buffer_n<10> b; + BEAST_EXPECT(read_size(b, 512) == 10); + b.prepare(4); + b.commit(4); + BEAST_EXPECT(read_size(b, 512) == 6); + b.consume(2); + BEAST_EXPECT(read_size(b, 512) == 8); + b.prepare(8); + b.commit(8); + BEAST_EXPECT(read_size(b, 512) == 0); + } + + // base + { + static_buffer_n<10> b; + [&](static_buffer& base) + { + BEAST_EXPECT(base.max_size() == b.capacity()); + } + (b.base()); + + [&](static_buffer const& base) + { + BEAST_EXPECT(base.max_size() == b.capacity()); + } + (b.base()); } - ba.prepare(2); - ba.commit(1); - std::size_t n; - n = 0; - for(auto it = ba.data().begin(); - it != ba.data().end(); it++) - ++n; - BEAST_EXPECT(n == 1); - n = 0; - for(auto it = ba.data().begin(); - it != ba.data().end(); ++it) - ++n; - BEAST_EXPECT(n == 1); - n = 0; - for(auto it = ba.data().end(); - it != ba.data().begin(); it--) - ++n; - BEAST_EXPECT(n == 1); - n = 0; - for(auto it = ba.data().end(); - it != ba.data().begin(); --it) - ++n; - BEAST_EXPECT(n == 1); } void run() override { - testStaticStreambuf(); - testIterators(); + testBuffer(); + //testStaticBuffer(); } }; -BEAST_DEFINE_TESTSUITE(static_streambuf,core,beast); +BEAST_DEFINE_TESTSUITE(static_buffer,core,beast); } // beastp diff --git a/src/beast/test/core/static_string.cpp b/src/beast/test/core/static_string.cpp index cfac29bf82..227b2e721f 100644 --- a/src/beast/test/core/static_string.cpp +++ b/src/beast/test/core/static_string.cpp @@ -15,150 +15,1133 @@ namespace beast { class static_string_test : public beast::unit_test::suite { public: - void testMembers() + void + testConstruct() { - using str1 = static_string<1>; - using str2 = static_string<2>; { - str1 s1; - BEAST_EXPECT(s1 == ""); - BEAST_EXPECT(s1.empty()); - BEAST_EXPECT(s1.size() == 0); - BEAST_EXPECT(s1.max_size() == 1); - BEAST_EXPECT(s1.capacity() == 1); - BEAST_EXPECT(s1.begin() == s1.end()); - BEAST_EXPECT(s1.cbegin() == s1.cend()); - BEAST_EXPECT(s1.rbegin() == s1.rend()); - BEAST_EXPECT(s1.crbegin() == s1.crend()); - try - { - BEAST_EXPECT(s1.at(0) == 0); - fail(); - } - catch(std::exception const&) - { - pass(); - } - BEAST_EXPECT(s1.data()[0] == 0); - BEAST_EXPECT(*s1.c_str() == 0); - BEAST_EXPECT(std::distance(s1.begin(), s1.end()) == 0); - BEAST_EXPECT(std::distance(s1.cbegin(), s1.cend()) == 0); - BEAST_EXPECT(std::distance(s1.rbegin(), s1.rend()) == 0); - BEAST_EXPECT(std::distance(s1.crbegin(), s1.crend()) == 0); - BEAST_EXPECT(s1.compare(s1) == 0); - BEAST_EXPECT(s1.to_string() == std::string{}); + static_string<1> s; + BEAST_EXPECT(s.empty()); + BEAST_EXPECT(s.size() == 0); + BEAST_EXPECT(s == ""); + BEAST_EXPECT(*s.end() == 0); } { - str1 const s1; - BEAST_EXPECT(s1 == ""); - BEAST_EXPECT(s1.empty()); - BEAST_EXPECT(s1.size() == 0); - BEAST_EXPECT(s1.max_size() == 1); - BEAST_EXPECT(s1.capacity() == 1); - BEAST_EXPECT(s1.begin() == s1.end()); - BEAST_EXPECT(s1.cbegin() == s1.cend()); - BEAST_EXPECT(s1.rbegin() == s1.rend()); - BEAST_EXPECT(s1.crbegin() == s1.crend()); + static_string<4> s1(3, 'x'); + BEAST_EXPECT(! s1.empty()); + BEAST_EXPECT(s1.size() == 3); + BEAST_EXPECT(s1 == "xxx"); + BEAST_EXPECT(*s1.end() == 0); try { - BEAST_EXPECT(s1.at(0) == 0); - fail(); - } - catch(std::exception const&) - { - pass(); - } - BEAST_EXPECT(s1.data()[0] == 0); - BEAST_EXPECT(*s1.c_str() == 0); - BEAST_EXPECT(std::distance(s1.begin(), s1.end()) == 0); - BEAST_EXPECT(std::distance(s1.cbegin(), s1.cend()) == 0); - BEAST_EXPECT(std::distance(s1.rbegin(), s1.rend()) == 0); - BEAST_EXPECT(std::distance(s1.crbegin(), s1.crend()) == 0); - BEAST_EXPECT(s1.compare(s1) == 0); - BEAST_EXPECT(s1.to_string() == std::string{}); - } - { - str1 s1; - str1 s2("x"); - BEAST_EXPECT(s2 == "x"); - BEAST_EXPECT(s2[0] == 'x'); - BEAST_EXPECT(s2.at(0) == 'x'); - BEAST_EXPECT(s2.front() == 'x'); - BEAST_EXPECT(s2.back() == 'x'); - str1 const s3(s2); - BEAST_EXPECT(s3 == "x"); - BEAST_EXPECT(s3[0] == 'x'); - BEAST_EXPECT(s3.at(0) == 'x'); - BEAST_EXPECT(s3.front() == 'x'); - BEAST_EXPECT(s3.back() == 'x'); - s2 = "y"; - BEAST_EXPECT(s2 == "y"); - BEAST_EXPECT(s3 == "x"); - s1 = s2; - BEAST_EXPECT(s1 == "y"); - s1.clear(); - BEAST_EXPECT(s1.empty()); - BEAST_EXPECT(s1.size() == 0); - } - { - str2 s1("x"); - str1 s2(s1); - BEAST_EXPECT(s2 == "x"); - str1 s3; - s3 = s2; - BEAST_EXPECT(s3 == "x"); - s1 = "xy"; - BEAST_EXPECT(s1.size() == 2); - BEAST_EXPECT(s1[0] == 'x'); - BEAST_EXPECT(s1[1] == 'y'); - BEAST_EXPECT(s1.at(0) == 'x'); - BEAST_EXPECT(s1.at(1) == 'y'); - BEAST_EXPECT(s1.front() == 'x'); - BEAST_EXPECT(s1.back() == 'y'); - auto const s4 = s1; - BEAST_EXPECT(s4[0] == 'x'); - BEAST_EXPECT(s4[1] == 'y'); - BEAST_EXPECT(s4.at(0) == 'x'); - BEAST_EXPECT(s4.at(1) == 'y'); - BEAST_EXPECT(s4.front() == 'x'); - BEAST_EXPECT(s4.back() == 'y'); - try - { - s3 = s1; - fail(); - } - catch(std::exception const&) - { - pass(); - } - try - { - str1 s5(s1); - fail(); - } - catch(std::exception const&) - { - pass(); - } - } - { - str1 s1("x"); - str2 s2; - s2 = s1; - try - { - s1.resize(2); - fail(); + static_string<2> s2(3, 'x'); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<5> s1("12345"); + BEAST_EXPECT(*s1.end() == 0); + static_string<3> s2(s1, 2); + BEAST_EXPECT(s2 == "345"); + BEAST_EXPECT(*s2.end() == 0); + static_string<0> s3(s1, 5); + BEAST_EXPECT(s3.empty()); + BEAST_EXPECT(s3.front() == 0); + BEAST_EXPECT(*s3.end() == 0); + } + { + static_string<5> s1("12345"); + static_string<2> s2(s1, 1, 2); + BEAST_EXPECT(s2 == "23"); + BEAST_EXPECT(*s2.end() == 0); + static_string<0> s3(s1, 5, 1); + BEAST_EXPECT(s3.empty()); + BEAST_EXPECT(s3.front() == 0); + BEAST_EXPECT(*s3.end() == 0); + try + { + static_string<5> s4(s1, 6); + fail("", __FILE__, __LINE__); + } + catch(std::out_of_range const&) + { + pass(); + } + } + { + static_string<5> s1("UVXYZ", 3); + BEAST_EXPECT(s1 == "UVX"); + BEAST_EXPECT(*s1.end() == 0); + static_string<5> s2("X\0""Y\0""Z", 3); + BEAST_EXPECT(std::memcmp( + s2.data(), "X\0""Y", 3) == 0); + BEAST_EXPECT(*s2.end() == 0); + } + { + static_string<5> s1("12345"); + static_string<3> s2( + s1.begin() + 1, s1.begin() + 3); + BEAST_EXPECT(s2 == "23"); + BEAST_EXPECT(*s2.end() == 0); + } + { + static_string<5> s1("12345"); + static_string<5> s2(s1); + BEAST_EXPECT(s2 == "12345"); + BEAST_EXPECT(*s2.end() == 0); + static_string<6> s3(s1); + BEAST_EXPECT(s3 == "12345"); + BEAST_EXPECT(*s3.end() == 0); + try + { + static_string<4> s4(s1); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<3> s1({'1', '2', '3'}); + BEAST_EXPECT(s1 == "123"); + BEAST_EXPECT(*s1.end() == 0); + BEAST_EXPECT( + static_string<0>({}) == static_string<0>()); + try + { + static_string<2> s2({'1', '2', '3'}); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<3> s1( + string_view("123")); + BEAST_EXPECT(s1 == "123"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<2> s2( + string_view("123")); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<5> s1( + std::string("12345"), 2, 2); + BEAST_EXPECT(s1 == "34"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<2> s2( + std::string("12345"), 1, 3); + fail("", __FILE__, __LINE__); } catch(std::length_error const&) { pass(); } } - pass(); } - void testCompare() + void + testAssign() + { + { + static_string<3> s1("123"); + static_string<3> s2; + s2 = s1; + BEAST_EXPECT(s2 == "123"); + BEAST_EXPECT(*s2.end() == 0); + } + { + static_string<3> s1("123"); + static_string<5> s2; + s2 = s1; + BEAST_EXPECT(s2 == "123"); + BEAST_EXPECT(*s2.end() == 0); + try + { + static_string<1> s3; + s3 = s1; + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<3> s1; + s1 = "123"; + BEAST_EXPECT(s1 == "123"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<1> s2; + s2 = "123"; + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<1> s1; + s1 = 'x'; + BEAST_EXPECT(s1 == "x"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<0> s2; + s2 = 'x'; + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<3> s1; + s1 = {'1', '2', '3'}; + BEAST_EXPECT(s1 == "123"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<1> s2; + s2 = {'1', '2', '3'}; + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<3> s1; + s1 = string_view("123"); + BEAST_EXPECT(s1 == "123"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<1> s2; + s2 = string_view("123"); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + + { + static_string<4> s1; + s1.assign(3, 'x'); + BEAST_EXPECT(s1 == "xxx"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<2> s2; + s2.assign(3, 'x'); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<5> s1("12345"); + BEAST_EXPECT(*s1.end() == 0); + static_string<5> s2; + s2.assign(s1); + BEAST_EXPECT(s2 == "12345"); + BEAST_EXPECT(*s2.end() == 0); + } + { + static_string<5> s1("12345"); + BEAST_EXPECT(*s1.end() == 0); + static_string<7> s2; + s2.assign(s1); + BEAST_EXPECT(s2 == "12345"); + BEAST_EXPECT(*s2.end() == 0); + try + { + static_string<3> s3; + s3.assign(s1); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<5> s1("12345"); + static_string<5> s2; + s2.assign(s1, 1); + BEAST_EXPECT(s2 == "2345"); + BEAST_EXPECT(*s2.end() == 0); + s2.assign(s1, 1, 2); + BEAST_EXPECT(s2 == "23"); + BEAST_EXPECT(*s2.end() == 0); + s2.assign(s1, 1, 100); + BEAST_EXPECT(s2 == "2345"); + BEAST_EXPECT(*s2.end() == 0); + try + { + s2.assign(s1, 6); + fail("", __FILE__, __LINE__); + } + catch(std::out_of_range const&) + { + pass(); + } + try + { + static_string<3> s3; + s3.assign(s1, 1); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<5> s1; + s1.assign("12"); + BEAST_EXPECT(s1 == "12"); + BEAST_EXPECT(*s1.end() == 0); + s1.assign("12345"); + BEAST_EXPECT(s1 == "12345"); + BEAST_EXPECT(*s1.end() == 0); + } + { + static_string<5> s1; + s1.assign("12345", 3); + BEAST_EXPECT(s1 == "123"); + BEAST_EXPECT(*s1.end() == 0); + } + { + static_string<5> s1("12345"); + static_string<3> s2; + s2.assign(s1.begin(), s1.begin() + 2); + BEAST_EXPECT(s2 == "12"); + BEAST_EXPECT(*s2.end() == 0); + try + { + s2.assign(s1.begin(), s1.end()); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<5> s1; + s1.assign({'1', '2', '3'}); + BEAST_EXPECT(s1 == "123"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<1> s2; + s2.assign({'1', '2', '3'}); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<5> s1; + s1.assign(string_view("123")); + BEAST_EXPECT(s1 == "123"); + BEAST_EXPECT(*s1.end() == 0); + s1.assign(string_view("12345")); + BEAST_EXPECT(s1 == "12345"); + BEAST_EXPECT(*s1.end() == 0); + try + { + s1.assign(string_view("1234567")); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<5> s1; + s1.assign(std::string("12345"), 2, 2); + BEAST_EXPECT(s1 == "34"); + BEAST_EXPECT(*s1.end() == 0); + s1.assign(std::string("12345"), 3); + BEAST_EXPECT(s1 == "45"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<2> s2; + s2.assign( + std::string("12345"), 1, 3); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + } + + void + testAccess() + { + { + static_string<5> s("12345"); + BEAST_EXPECT(s.at(1) == '2'); + BEAST_EXPECT(s.at(4) == '5'); + try + { + BEAST_EXPECT(s.at(5) == 0); + fail("", __FILE__, __LINE__); + } + catch(std::out_of_range const&) + { + pass(); + } + } + { + static_string<5> const s("12345"); + BEAST_EXPECT(s.at(1) == '2'); + BEAST_EXPECT(s.at(4) == '5'); + try + { + BEAST_EXPECT(s.at(5) == 0); + fail("", __FILE__, __LINE__); + } + catch(std::out_of_range const&) + { + pass(); + } + } + { + static_string<5> s("12345"); + BEAST_EXPECT(s[1] == '2'); + BEAST_EXPECT(s[4] == '5'); + s[1] = '_'; + BEAST_EXPECT(s == "1_345"); + } + { + static_string<5> const s("12345"); + BEAST_EXPECT(s[1] == '2'); + BEAST_EXPECT(s[4] == '5'); + BEAST_EXPECT(s[5] == 0); + } + { + static_string<3> s("123"); + BEAST_EXPECT(s.front() == '1'); + BEAST_EXPECT(s.back() == '3'); + s.front() = '_'; + BEAST_EXPECT(s == "_23"); + s.back() = '_'; + BEAST_EXPECT(s == "_2_"); + } + { + static_string<3> const s("123"); + BEAST_EXPECT(s.front() == '1'); + BEAST_EXPECT(s.back() == '3'); + } + { + static_string<3> s("123"); + BEAST_EXPECT(std::memcmp( + s.data(), "123", 3) == 0); + } + { + static_string<3> const s("123"); + BEAST_EXPECT(std::memcmp( + s.data(), "123", 3) == 0); + } + { + static_string<3> s("123"); + BEAST_EXPECT(std::memcmp( + s.c_str(), "123\0", 4) == 0); + } + { + static_string<3> s("123"); + string_view sv = s; + BEAST_EXPECT(static_string<5>(sv) == "123"); + } + } + + void + testIterators() + { + { + static_string<3> s; + BEAST_EXPECT(std::distance( + s.begin(), s.end()) == 0); + BEAST_EXPECT(std::distance( + s.rbegin(), s.rend()) == 0); + s = "123"; + BEAST_EXPECT(std::distance( + s.begin(), s.end()) == 3); + BEAST_EXPECT(std::distance( + s.rbegin(), s.rend()) == 3); + } + { + static_string<3> const s("123"); + BEAST_EXPECT(std::distance( + s.begin(), s.end()) == 3); + BEAST_EXPECT(std::distance( + s.cbegin(), s.cend()) == 3); + BEAST_EXPECT(std::distance( + s.rbegin(), s.rend()) == 3); + BEAST_EXPECT(std::distance( + s.crbegin(), s.crend()) == 3); + } + } + + void + testCapacity() + { + static_string<3> s; + BEAST_EXPECT(s.empty()); + BEAST_EXPECT(s.size() == 0); + BEAST_EXPECT(s.length() == 0); + BEAST_EXPECT(s.max_size() == 3); + BEAST_EXPECT(s.capacity() == 3); + s = "123"; + BEAST_EXPECT(! s.empty()); + BEAST_EXPECT(s.size() == 3); + BEAST_EXPECT(s.length() == 3); + s.reserve(0); + s.reserve(3); + try + { + s.reserve(4); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + s.shrink_to_fit(); + BEAST_EXPECT(! s.empty()); + BEAST_EXPECT(s.size() == 3); + BEAST_EXPECT(s.length() == 3); + BEAST_EXPECT(*s.end() == 0); + } + + void + testOperations() + { + // + // clear + // + + { + static_string<3> s("123"); + s.clear(); + BEAST_EXPECT(s.empty()); + BEAST_EXPECT(*s.end() == 0); + } + + // + // insert + // + + { + static_string<7> s1("12345"); + s1.insert(2, 2, '_'); + BEAST_EXPECT(s1 == "12__345"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<6> s2("12345"); + s2.insert(2, 2, '_'); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + try + { + static_string<6> s2("12345"); + s2.insert(6, 2, '_'); + fail("", __FILE__, __LINE__); + } + catch(std::out_of_range const&) + { + pass(); + } + } + { + static_string<7> s1("12345"); + s1.insert(2, "__"); + BEAST_EXPECT(s1 == "12__345"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<6> s2("12345"); + s2.insert(2, "__"); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + try + { + static_string<6> s2("12345"); + s2.insert(6, "__"); + fail("", __FILE__, __LINE__); + } + catch(std::out_of_range const&) + { + pass(); + } + } + { + static_string<7> s1("12345"); + s1.insert(2, "TUV", 2); + BEAST_EXPECT(s1 == "12TU345"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<6> s2("12345"); + s2.insert(2, "TUV", 2); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + try + { + static_string<6> s2("12345"); + s2.insert(6, "TUV", 2); + fail("", __FILE__, __LINE__); + } + catch(std::out_of_range const&) + { + pass(); + } + } + { + static_string<7> s1("12345"); + s1.insert(2, static_string<3>("TU")); + BEAST_EXPECT(s1 == "12TU345"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<6> s2("12345"); + s2.insert(2, static_string<3>("TUV")); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + try + { + static_string<6> s2("12345"); + s2.insert(6, static_string<3>("TUV")); + fail("", __FILE__, __LINE__); + } + catch(std::out_of_range const&) + { + pass(); + } + } + { + static_string<7> s1("12345"); + s1.insert(2, static_string<3>("TUV"), 1); + BEAST_EXPECT(s1 == "12UV345"); + BEAST_EXPECT(*s1.end() == 0); + s1 = "12345"; + s1.insert(2, static_string<3>("TUV"), 1, 1); + BEAST_EXPECT(s1 == "12U345"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<6> s2("12345"); + s2.insert(2, static_string<3>("TUV"), 1, 2); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + try + { + static_string<6> s2("12345"); + s2.insert(6, static_string<3>("TUV"), 1, 2); + fail("", __FILE__, __LINE__); + } + catch(std::out_of_range const&) + { + pass(); + } + } + { + static_string<4> s1("123"); + s1.insert(s1.begin() + 1, '_'); + BEAST_EXPECT(s1 == "1_23"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<3> s2("123"); + s2.insert(s2.begin() + 1, '_'); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<4> s1("12"); + s1.insert(s1.begin() + 1, 2, '_'); + BEAST_EXPECT(s1 == "1__2"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<4> s2("123"); + s2.insert(s2.begin() + 1, 2, ' '); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<3> s1("123"); + static_string<5> s2("UV"); + s2.insert(s2.begin() + 1, s1.begin(), s1.end()); + BEAST_EXPECT(s2 == "U123V"); + BEAST_EXPECT(*s2.end() == 0); + try + { + static_string<4> s3("UV"); + s3.insert(s3.begin() + 1, s1.begin(), s1.end()); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<5> s1("123"); + s1.insert(1, string_view("UV")); + BEAST_EXPECT(s1 == "1UV23"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<4> s2("123"); + s2.insert(1, string_view("UV")); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + try + { + static_string<5> s2("123"); + s2.insert(5, string_view("UV")); + fail("", __FILE__, __LINE__); + } + catch(std::out_of_range const&) + { + pass(); + } + } + { + static_string<5> s1("123"); + s1.insert(1, std::string("UV")); + BEAST_EXPECT(s1 == "1UV23"); + BEAST_EXPECT(*s1.end() == 0); + try + { + s1.insert(1, std::string("UV")); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<6> s1("123"); + s1.insert(1, std::string("UVX"), 1); + BEAST_EXPECT(s1 == "1VX23"); + BEAST_EXPECT(*s1.end() == 0); + s1.insert(4, std::string("PQR"), 1, 1); + BEAST_EXPECT(s1 == "1VX2Q3"); + BEAST_EXPECT(*s1.end() == 0); + try + { + s1.insert(4, std::string("PQR"), 1, 1); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + + // + // erase + // + + { + static_string<9> s1("123456789"); + BEAST_EXPECT(s1.erase(1, 1) == "13456789"); + BEAST_EXPECT(s1 == "13456789"); + BEAST_EXPECT(*s1.end() == 0); + BEAST_EXPECT(s1.erase(5) == "13456"); + BEAST_EXPECT(s1 == "13456"); + BEAST_EXPECT(*s1.end() == 0); + try + { + s1.erase(7); + fail("", __FILE__, __LINE__); + } + catch(std::out_of_range const&) + { + pass(); + } + } + { + static_string<9> s1("123456789"); + BEAST_EXPECT(*s1.erase(s1.begin() + 5) == '7'); + BEAST_EXPECT(s1 == "12345789"); + BEAST_EXPECT(*s1.end() == 0); + } + { + static_string<9> s1("123456789"); + BEAST_EXPECT(*s1.erase( + s1.begin() + 5, s1.begin() + 7) == '8'); + BEAST_EXPECT(s1 == "1234589"); + BEAST_EXPECT(*s1.end() == 0); + } + + // + // push_back + // + + { + static_string<3> s1("12"); + s1.push_back('3'); + BEAST_EXPECT(s1 == "123"); + try + { + s1.push_back('4'); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + static_string<0> s2; + try + { + s2.push_back('_'); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + + // + // pop_back + // + + { + static_string<3> s1("123"); + s1.pop_back(); + BEAST_EXPECT(s1 == "12"); + BEAST_EXPECT(*s1.end() == 0); + s1.pop_back(); + BEAST_EXPECT(s1 == "1"); + BEAST_EXPECT(*s1.end() == 0); + s1.pop_back(); + BEAST_EXPECT(s1.empty()); + BEAST_EXPECT(*s1.end() == 0); + } + + // + // append + // + + { + static_string<3> s1("1"); + s1.append(2, '_'); + BEAST_EXPECT(s1 == "1__"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<2> s2("1"); + s2.append(2, '_'); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<2> s1("__"); + static_string<3> s2("1"); + s2.append(s1); + BEAST_EXPECT(s2 == "1__"); + BEAST_EXPECT(*s2.end() == 0); + try + { + static_string<2> s3("1"); + s3.append(s1); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<3> s1("XYZ"); + static_string<4> s2("12"); + s2.append(s1, 1); + BEAST_EXPECT(s2 == "12YZ"); + BEAST_EXPECT(*s2.end() == 0); + static_string<3> s3("12"); + s3.append(s1, 1, 1); + BEAST_EXPECT(s3 == "12Y"); + BEAST_EXPECT(*s3.end() == 0); + try + { + static_string<3> s4("12"); + s4.append(s1, 3); + fail("", __FILE__, __LINE__); + } + catch(std::out_of_range const&) + { + pass(); + } + try + { + static_string<3> s4("12"); + s4.append(s1, 1); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<4> s1("12"); + s1.append("XYZ", 2); + BEAST_EXPECT(s1 == "12XY"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<3> s3("12"); + s3.append("XYZ", 2); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<5> s1("12"); + s1.append("XYZ"); + BEAST_EXPECT(s1 == "12XYZ"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<4> s2("12"); + s2.append("XYZ"); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<3> s1("XYZ"); + static_string<5> s2("12"); + s2.append(s1.begin(), s1.end()); + BEAST_EXPECT(s2 == "12XYZ"); + BEAST_EXPECT(*s2.end() == 0); + try + { + static_string<4> s3("12"); + s3.append(s1.begin(), s1.end()); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<5> s1("123"); + s1.append({'X', 'Y'}); + BEAST_EXPECT(s1 == "123XY"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<4> s2("123"); + s2.append({'X', 'Y'}); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + string_view s1("XYZ"); + static_string<5> s2("12"); + s2.append(s1); + BEAST_EXPECT(s2 == "12XYZ"); + BEAST_EXPECT(*s2.end() == 0); + try + { + static_string<4> s3("12"); + s3.append(s1); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<6> s1("123"); + s1.append(std::string("UVX"), 1); + BEAST_EXPECT(s1 == "123VX"); + BEAST_EXPECT(*s1.end() == 0); + s1.append(std::string("PQR"), 1, 1); + BEAST_EXPECT(s1 == "123VXQ"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<3> s2("123"); + s2.append(std::string("PQR"), 1, 1); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + + // + // operator+= + // + + { + static_string<2> s1("__"); + static_string<3> s2("1"); + s2 += s1; + BEAST_EXPECT(s2 == "1__"); + BEAST_EXPECT(*s2.end() == 0); + try + { + static_string<2> s3("1"); + s3 += s1; + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<3> s1("12"); + s1 += '3'; + BEAST_EXPECT(s1 == "123"); + try + { + s1 += '4'; + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<4> s1("12"); + s1 += "34"; + BEAST_EXPECT(s1 == "1234"); + try + { + s1 += "5"; + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<4> s1("12"); + s1 += {'3', '4'}; + BEAST_EXPECT(s1 == "1234"); + try + { + s1 += {'5'}; + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + string_view s1("34"); + static_string<4> s2("12"); + s2 += s1; + BEAST_EXPECT(s2 == "1234"); + try + { + s2 += s1; + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + } + + void + testCompare() { using str1 = static_string<1>; using str2 = static_string<2>; @@ -251,10 +1234,240 @@ public: } } - void run() override + void + testSwap() { - testMembers(); + { + static_string<3> s1("123"); + static_string<3> s2("XYZ"); + swap(s1, s2); + BEAST_EXPECT(s1 == "XYZ"); + BEAST_EXPECT(*s1.end() == 0); + BEAST_EXPECT(s2 == "123"); + BEAST_EXPECT(*s2.end() == 0); + static_string<3> s3("UV"); + swap(s2, s3); + BEAST_EXPECT(s2 == "UV"); + BEAST_EXPECT(*s2.end() == 0); + BEAST_EXPECT(s3 == "123"); + BEAST_EXPECT(*s3.end() == 0); + } + { + static_string<5> s1("123"); + static_string<7> s2("XYZ"); + swap(s1, s2); + BEAST_EXPECT(s1 == "XYZ"); + BEAST_EXPECT(*s1.end() == 0); + BEAST_EXPECT(s2 == "123"); + BEAST_EXPECT(*s2.end() == 0); + static_string<3> s3("UV"); + swap(s2, s3); + BEAST_EXPECT(s2 == "UV"); + BEAST_EXPECT(*s2.end() == 0); + BEAST_EXPECT(s3 == "123"); + BEAST_EXPECT(*s3.end() == 0); + try + { + static_string<5> s4("12345"); + static_string<3> s5("XYZ"); + swap(s4, s5); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + try + { + static_string<3> s4("XYZ"); + static_string<5> s5("12345"); + swap(s4, s5); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + } + + void + testGeneral() + { + using str1 = static_string<1>; + using str2 = static_string<2>; + { + str1 s1; + BEAST_EXPECT(s1 == ""); + BEAST_EXPECT(s1.empty()); + BEAST_EXPECT(s1.size() == 0); + BEAST_EXPECT(s1.max_size() == 1); + BEAST_EXPECT(s1.capacity() == 1); + BEAST_EXPECT(s1.begin() == s1.end()); + BEAST_EXPECT(s1.cbegin() == s1.cend()); + BEAST_EXPECT(s1.rbegin() == s1.rend()); + BEAST_EXPECT(s1.crbegin() == s1.crend()); + try + { + BEAST_EXPECT(s1.at(0) == 0); + fail(); + } + catch(std::exception const&) + { + pass(); + } + BEAST_EXPECT(s1.data()[0] == 0); + BEAST_EXPECT(*s1.c_str() == 0); + BEAST_EXPECT(std::distance(s1.begin(), s1.end()) == 0); + BEAST_EXPECT(std::distance(s1.cbegin(), s1.cend()) == 0); + BEAST_EXPECT(std::distance(s1.rbegin(), s1.rend()) == 0); + BEAST_EXPECT(std::distance(s1.crbegin(), s1.crend()) == 0); + BEAST_EXPECT(s1.compare(s1) == 0); + } + { + str1 const s1; + BEAST_EXPECT(s1 == ""); + BEAST_EXPECT(s1.empty()); + BEAST_EXPECT(s1.size() == 0); + BEAST_EXPECT(s1.max_size() == 1); + BEAST_EXPECT(s1.capacity() == 1); + BEAST_EXPECT(s1.begin() == s1.end()); + BEAST_EXPECT(s1.cbegin() == s1.cend()); + BEAST_EXPECT(s1.rbegin() == s1.rend()); + BEAST_EXPECT(s1.crbegin() == s1.crend()); + try + { + BEAST_EXPECT(s1.at(0) == 0); + fail(); + } + catch(std::exception const&) + { + pass(); + } + BEAST_EXPECT(s1.data()[0] == 0); + BEAST_EXPECT(*s1.c_str() == 0); + BEAST_EXPECT(std::distance(s1.begin(), s1.end()) == 0); + BEAST_EXPECT(std::distance(s1.cbegin(), s1.cend()) == 0); + BEAST_EXPECT(std::distance(s1.rbegin(), s1.rend()) == 0); + BEAST_EXPECT(std::distance(s1.crbegin(), s1.crend()) == 0); + BEAST_EXPECT(s1.compare(s1) == 0); + } + { + str1 s1; + str1 s2("x"); + BEAST_EXPECT(s2 == "x"); + BEAST_EXPECT(s2[0] == 'x'); + BEAST_EXPECT(s2.at(0) == 'x'); + BEAST_EXPECT(s2.front() == 'x'); + BEAST_EXPECT(s2.back() == 'x'); + str1 const s3(s2); + BEAST_EXPECT(s3 == "x"); + BEAST_EXPECT(s3[0] == 'x'); + BEAST_EXPECT(s3.at(0) == 'x'); + BEAST_EXPECT(s3.front() == 'x'); + BEAST_EXPECT(s3.back() == 'x'); + s2 = "y"; + BEAST_EXPECT(s2 == "y"); + BEAST_EXPECT(s3 == "x"); + s1 = s2; + BEAST_EXPECT(s1 == "y"); + s1.clear(); + BEAST_EXPECT(s1.empty()); + BEAST_EXPECT(s1.size() == 0); + } + { + str2 s1("x"); + str1 s2(s1); + BEAST_EXPECT(s2 == "x"); + str1 s3; + s3 = s2; + BEAST_EXPECT(s3 == "x"); + s1 = "xy"; + BEAST_EXPECT(s1.size() == 2); + BEAST_EXPECT(s1[0] == 'x'); + BEAST_EXPECT(s1[1] == 'y'); + BEAST_EXPECT(s1.at(0) == 'x'); + BEAST_EXPECT(s1.at(1) == 'y'); + BEAST_EXPECT(s1.front() == 'x'); + BEAST_EXPECT(s1.back() == 'y'); + auto const s4 = s1; + BEAST_EXPECT(s4[0] == 'x'); + BEAST_EXPECT(s4[1] == 'y'); + BEAST_EXPECT(s4.at(0) == 'x'); + BEAST_EXPECT(s4.at(1) == 'y'); + BEAST_EXPECT(s4.front() == 'x'); + BEAST_EXPECT(s4.back() == 'y'); + try + { + s3 = s1; + fail(); + } + catch(std::exception const&) + { + pass(); + } + try + { + str1 s5(s1); + fail(); + } + catch(std::exception const&) + { + pass(); + } + } + { + str1 s1("x"); + str2 s2; + s2 = s1; + try + { + s1.resize(2); + fail(); + } + catch(std::length_error const&) + { + pass(); + } + } + pass(); + } + + void + testToStaticString() + { + BEAST_EXPECT(to_static_string(0) == "0"); + BEAST_EXPECT(to_static_string(1) == "1"); + BEAST_EXPECT(to_static_string(0xffff) == "65535"); + BEAST_EXPECT(to_static_string(0x10000) == "65536"); + BEAST_EXPECT(to_static_string(0xffffffff) == "4294967295"); + + BEAST_EXPECT(to_static_string(-1) == "-1"); + BEAST_EXPECT(to_static_string(-65535) == "-65535"); + BEAST_EXPECT(to_static_string(-65536) == "-65536"); + BEAST_EXPECT(to_static_string(-4294967295ll) == "-4294967295"); + + BEAST_EXPECT(to_static_string(0) == "0"); + BEAST_EXPECT(to_static_string(1) == "1"); + BEAST_EXPECT(to_static_string(0xffff) == "65535"); + BEAST_EXPECT(to_static_string(0x10000) == "65536"); + BEAST_EXPECT(to_static_string(0xffffffff) == "4294967295"); + } + + void + run() override + { + testConstruct(); + testAssign(); + testAccess(); + testIterators(); + testCapacity(); + testOperations(); testCompare(); + testSwap(); + + testGeneral(); + testToStaticString(); } }; diff --git a/src/beast/test/core/stream_concepts.cpp b/src/beast/test/core/stream_concepts.cpp deleted file mode 100644 index f9d5904096..0000000000 --- a/src/beast/test/core/stream_concepts.cpp +++ /dev/null @@ -1,30 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -// Test that header file is self-contained. -#include -#include - -namespace beast { - -using stream_type = boost::asio::ip::tcp::socket; - -static_assert(has_get_io_service::value, ""); -static_assert(is_AsyncReadStream::value, ""); -static_assert(is_AsyncWriteStream::value, ""); -static_assert(is_AsyncStream::value, ""); -static_assert(is_SyncReadStream::value, ""); -static_assert(is_SyncWriteStream::value, ""); -static_assert(is_SyncStream::value, ""); - -static_assert(! has_get_io_service::value, ""); -static_assert(! is_AsyncReadStream::value, ""); -static_assert(! is_AsyncWriteStream::value, ""); -static_assert(! is_SyncReadStream::value, ""); -static_assert(! is_SyncWriteStream::value, ""); - -} // beast diff --git a/src/beast/test/core/streambuf.cpp b/src/beast/test/core/streambuf.cpp deleted file mode 100644 index eb295161f6..0000000000 --- a/src/beast/test/core/streambuf.cpp +++ /dev/null @@ -1,482 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -// Test that header file is self-contained. -#include - -#include "buffer_test.hpp" -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { - -static_assert(is_DynamicBuffer::value, ""); - -struct test_allocator_info -{ - std::size_t ncopy = 0; - std::size_t nmove = 0; - std::size_t nselect = 0; -}; - -template -class test_allocator; - -template -struct test_allocator_base -{ -}; - -template -struct test_allocator_base -{ - static - test_allocator - select_on_container_copy_construction( - test_allocator const& a) - { - return test_allocator{}; - } -}; - -template -class test_allocator : public test_allocator_base< - T, Assign, Move, Swap, Select> -{ - std::size_t id_; - std::shared_ptr info_; - - template - friend class test_allocator; - -public: - using value_type = T; - using propagate_on_container_copy_assignment = - std::integral_constant; - using propagate_on_container_move_assignment = - std::integral_constant; - using propagate_on_container_swap = - std::integral_constant; - - template - struct rebind - { - using other = test_allocator< - U, Assign, Move, Swap, Select>; - }; - - test_allocator() - : id_([] - { - static std::atomic< - std::size_t> sid(0); - return ++sid; - }()) - , info_(std::make_shared()) - { - } - - test_allocator(test_allocator const& u) noexcept - : id_(u.id_) - , info_(u.info_) - { - ++info_->ncopy; - } - - template - test_allocator(test_allocator< - U, Assign, Move, Swap, Select> const& u) noexcept - : id_(u.id_) - , info_(u.info_) - { - ++info_->ncopy; - } - - test_allocator(test_allocator&& t) - : id_(t.id_) - , info_(t.info_) - { - ++info_->nmove; - } - - value_type* - allocate(std::size_t n) - { - return static_cast( - ::operator new (n*sizeof(value_type))); - } - - void - deallocate(value_type* p, std::size_t) noexcept - { - ::operator delete(p); - } - - std::size_t - id() const - { - return id_; - } - - test_allocator_info const* - operator->() const - { - return info_.get(); - } -}; - -class basic_streambuf_test : public beast::unit_test::suite -{ -public: - template - static - bool - eq(basic_streambuf const& sb1, - basic_streambuf const& sb2) - { - return to_string(sb1.data()) == to_string(sb2.data()); - } - - template - void - expect_size(std::size_t n, ConstBufferSequence const& buffers) - { - BEAST_EXPECT(test::size_pre(buffers) == n); - BEAST_EXPECT(test::size_post(buffers) == n); - BEAST_EXPECT(test::size_rev_pre(buffers) == n); - BEAST_EXPECT(test::size_rev_post(buffers) == n); - } - - template - static - void - self_assign(U& u, V&& v) - { - u = std::forward(v); - } - - void testSpecialMembers() - { - using boost::asio::buffer; - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - std::string const s = "Hello, world"; - BEAST_EXPECT(s.size() == 12); - for(std::size_t i = 1; i < 12; ++i) { - for(std::size_t x = 1; x < 4; ++x) { - for(std::size_t y = 1; y < 4; ++y) { - std::size_t z = s.size() - (x + y); - { - streambuf sb(i); - sb.commit(buffer_copy(sb.prepare(x), buffer(s.data(), x))); - sb.commit(buffer_copy(sb.prepare(y), buffer(s.data()+x, y))); - sb.commit(buffer_copy(sb.prepare(z), buffer(s.data()+x+y, z))); - BEAST_EXPECT(to_string(sb.data()) == s); - { - streambuf sb2(sb); - BEAST_EXPECT(eq(sb, sb2)); - } - { - streambuf sb2; - sb2 = sb; - BEAST_EXPECT(eq(sb, sb2)); - } - { - streambuf sb2(std::move(sb)); - BEAST_EXPECT(to_string(sb2.data()) == s); - expect_size(0, sb.data()); - sb = std::move(sb2); - BEAST_EXPECT(to_string(sb.data()) == s); - expect_size(0, sb2.data()); - } - self_assign(sb, sb); - BEAST_EXPECT(to_string(sb.data()) == s); - self_assign(sb, std::move(sb)); - BEAST_EXPECT(to_string(sb.data()) == s); - } - }}} - try - { - streambuf sb0(0); - fail(); - } - catch(std::exception const&) - { - pass(); - } - } - - void testAllocator() - { - // VFALCO This needs work - { - using alloc_type = - test_allocator; - using sb_type = basic_streambuf; - sb_type sb; - BEAST_EXPECT(sb.get_allocator().id() == 1); - } - { - using alloc_type = - test_allocator; - using sb_type = basic_streambuf; - sb_type sb; - BEAST_EXPECT(sb.get_allocator().id() == 2); - sb_type sb2(sb); - BEAST_EXPECT(sb2.get_allocator().id() == 2); - sb_type sb3(sb, alloc_type{}); - } - } - - void - testPrepare() - { - using boost::asio::buffer_size; - { - streambuf sb(2); - BEAST_EXPECT(buffer_size(sb.prepare(5)) == 5); - BEAST_EXPECT(buffer_size(sb.prepare(8)) == 8); - BEAST_EXPECT(buffer_size(sb.prepare(7)) == 7); - } - { - streambuf sb(2); - sb.prepare(2); - BEAST_EXPECT(test::buffer_count(sb.prepare(5)) == 2); - BEAST_EXPECT(test::buffer_count(sb.prepare(8)) == 3); - BEAST_EXPECT(test::buffer_count(sb.prepare(4)) == 2); - } - } - - void testCommit() - { - using boost::asio::buffer_size; - streambuf sb(2); - sb.prepare(2); - sb.prepare(5); - sb.commit(1); - expect_size(1, sb.data()); - } - - void testConsume() - { - using boost::asio::buffer_size; - streambuf sb(1); - expect_size(5, sb.prepare(5)); - sb.commit(3); - expect_size(3, sb.data()); - sb.consume(1); - expect_size(2, sb.data()); - } - - void testMatrix() - { - using boost::asio::buffer; - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - std::string const s = "Hello, world"; - BEAST_EXPECT(s.size() == 12); - for(std::size_t i = 1; i < 12; ++i) { - for(std::size_t x = 1; x < 4; ++x) { - for(std::size_t y = 1; y < 4; ++y) { - for(std::size_t t = 1; t < 4; ++ t) { - for(std::size_t u = 1; u < 4; ++ u) { - std::size_t z = s.size() - (x + y); - std::size_t v = s.size() - (t + u); - { - streambuf sb(i); - { - auto d = sb.prepare(z); - BEAST_EXPECT(buffer_size(d) == z); - } - { - auto d = sb.prepare(0); - BEAST_EXPECT(buffer_size(d) == 0); - } - { - auto d = sb.prepare(y); - BEAST_EXPECT(buffer_size(d) == y); - } - { - auto d = sb.prepare(x); - BEAST_EXPECT(buffer_size(d) == x); - sb.commit(buffer_copy(d, buffer(s.data(), x))); - } - BEAST_EXPECT(sb.size() == x); - BEAST_EXPECT(buffer_size(sb.data()) == sb.size()); - { - auto d = sb.prepare(x); - BEAST_EXPECT(buffer_size(d) == x); - } - { - auto d = sb.prepare(0); - BEAST_EXPECT(buffer_size(d) == 0); - } - { - auto d = sb.prepare(z); - BEAST_EXPECT(buffer_size(d) == z); - } - { - auto d = sb.prepare(y); - BEAST_EXPECT(buffer_size(d) == y); - sb.commit(buffer_copy(d, buffer(s.data()+x, y))); - } - sb.commit(1); - BEAST_EXPECT(sb.size() == x + y); - BEAST_EXPECT(buffer_size(sb.data()) == sb.size()); - { - auto d = sb.prepare(x); - BEAST_EXPECT(buffer_size(d) == x); - } - { - auto d = sb.prepare(y); - BEAST_EXPECT(buffer_size(d) == y); - } - { - auto d = sb.prepare(0); - BEAST_EXPECT(buffer_size(d) == 0); - } - { - auto d = sb.prepare(z); - BEAST_EXPECT(buffer_size(d) == z); - sb.commit(buffer_copy(d, buffer(s.data()+x+y, z))); - } - sb.commit(2); - BEAST_EXPECT(sb.size() == x + y + z); - BEAST_EXPECT(buffer_size(sb.data()) == sb.size()); - BEAST_EXPECT(to_string(sb.data()) == s); - sb.consume(t); - { - auto d = sb.prepare(0); - BEAST_EXPECT(buffer_size(d) == 0); - } - BEAST_EXPECT(to_string(sb.data()) == s.substr(t, std::string::npos)); - sb.consume(u); - BEAST_EXPECT(to_string(sb.data()) == s.substr(t + u, std::string::npos)); - sb.consume(v); - BEAST_EXPECT(to_string(sb.data()) == ""); - sb.consume(1); - { - auto d = sb.prepare(0); - BEAST_EXPECT(buffer_size(d) == 0); - } - } - }}}}} - } - - void testIterators() - { - using boost::asio::buffer_size; - streambuf sb(1); - sb.prepare(1); - sb.commit(1); - sb.prepare(2); - sb.commit(2); - expect_size(3, sb.data()); - sb.prepare(1); - expect_size(3, sb.prepare(3)); - sb.commit(2); - BEAST_EXPECT(test::buffer_count(sb.data()) == 4); - } - - void testOutputStream() - { - streambuf sb; - sb << "x"; - BEAST_EXPECT(to_string(sb.data()) == "x"); - } - - void testCapacity() - { - using boost::asio::buffer_size; - { - streambuf sb{10}; - BEAST_EXPECT(sb.alloc_size() == 10); - BEAST_EXPECT(read_size_helper(sb, 1) == 1); - BEAST_EXPECT(read_size_helper(sb, 10) == 10); - BEAST_EXPECT(read_size_helper(sb, 20) == 20); - BEAST_EXPECT(read_size_helper(sb, 1000) == 512); - sb.prepare(3); - sb.commit(3); - BEAST_EXPECT(read_size_helper(sb, 10) == 7); - BEAST_EXPECT(read_size_helper(sb, 1000) == 7); - } - { - streambuf sb(1000); - BEAST_EXPECT(sb.alloc_size() == 1000); - BEAST_EXPECT(read_size_helper(sb, 1) == 1); - BEAST_EXPECT(read_size_helper(sb, 1000) == 1000); - BEAST_EXPECT(read_size_helper(sb, 2000) == 1000); - sb.prepare(3); - BEAST_EXPECT(read_size_helper(sb, 1) == 1); - BEAST_EXPECT(read_size_helper(sb, 1000) == 1000); - BEAST_EXPECT(read_size_helper(sb, 2000) == 1000); - sb.commit(3); - BEAST_EXPECT(read_size_helper(sb, 1) == 1); - BEAST_EXPECT(read_size_helper(sb, 1000) == 997); - BEAST_EXPECT(read_size_helper(sb, 2000) == 997); - sb.consume(2); - BEAST_EXPECT(read_size_helper(sb, 1) == 1); - BEAST_EXPECT(read_size_helper(sb, 1000) == 997); - BEAST_EXPECT(read_size_helper(sb, 2000) == 997); - } - { - streambuf sb{2}; - BEAST_EXPECT(sb.alloc_size() == 2); - BEAST_EXPECT(test::buffer_count(sb.prepare(2)) == 1); - BEAST_EXPECT(test::buffer_count(sb.prepare(3)) == 2); - BEAST_EXPECT(buffer_size(sb.prepare(5)) == 5); - BEAST_EXPECT(read_size_helper(sb, 10) == 6); - } - { - auto avail = - [](streambuf const& sb) - { - return sb.capacity() - sb.size(); - }; - streambuf sb{100}; - BEAST_EXPECT(sb.alloc_size() == 100); - BEAST_EXPECT(avail(sb) == 0); - sb.prepare(100); - BEAST_EXPECT(avail(sb) == 100); - sb.commit(100); - BEAST_EXPECT(avail(sb) == 0); - sb.consume(100); - BEAST_EXPECT(avail(sb) == 0); - sb.alloc_size(200); - BEAST_EXPECT(sb.alloc_size() == 200); - sb.prepare(1); - BEAST_EXPECT(avail(sb) == 200); - } - } - - void run() override - { - testSpecialMembers(); - testAllocator(); - testPrepare(); - testCommit(); - testConsume(); - testMatrix(); - testIterators(); - testOutputStream(); - testCapacity(); - } -}; - -BEAST_DEFINE_TESTSUITE(basic_streambuf,core,beast); - -} // beast diff --git a/src/beast/test/http/concepts.cpp b/src/beast/test/core/string.cpp similarity index 88% rename from src/beast/test/http/concepts.cpp rename to src/beast/test/core/string.cpp index 9bc87107c0..c8afa23c86 100644 --- a/src/beast/test/http/concepts.cpp +++ b/src/beast/test/core/string.cpp @@ -6,4 +6,4 @@ // // Test that header file is self-contained. -#include +#include diff --git a/src/beast/test/core/string_param.cpp b/src/beast/test/core/string_param.cpp new file mode 100644 index 0000000000..4ccfc9a037 --- /dev/null +++ b/src/beast/test/core/string_param.cpp @@ -0,0 +1,79 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include +#include + +namespace beast { + +class string_param_test : public unit_test::suite +{ +public: + struct nop {}; + + void + check(string_param const& v, string_view s) + { + BEAST_EXPECT(static_cast(v) == s); + } + + class repeater + { + std::size_t n_; + + public: + explicit + repeater(std::size_t n) + : n_(n) + { + } + + friend + std::ostream& + operator<<(std::ostream& os, repeater const& v) + { + return os << std::string(v.n_, '*'); + } + }; + + void + testConversion() + { + // Make sure things convert correctly + check(std::string("hello"), "hello"); + check("xyz", "xyz"); + check(1, "1"); + check(12, "12"); + check(123, "123"); + check(1234, "1234"); + check(12345, "12345"); + check({"a", "b"}, "ab"); + check({1, 2, 3}, "123"); + } + + void + testStaticOstream() + { + // exercise static_ostream for coverage + std::string s(500, '*'); + check(repeater{500}, s); + } + + void + run() override + { + testConversion(); + testStaticOstream(); + } +}; + +BEAST_DEFINE_TESTSUITE(string_param,core,beast); + +} // beast diff --git a/src/beast/test/core/to_string.cpp b/src/beast/test/core/to_string.cpp deleted file mode 100644 index e449dd33aa..0000000000 --- a/src/beast/test/core/to_string.cpp +++ /dev/null @@ -1,28 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -// Test that header file is self-contained. -#include - -#include -#include - -namespace beast { - -class to_string_test : public beast::unit_test::suite -{ -public: - void run() - { - BEAST_EXPECT(to_string(boost::asio::const_buffers_1("x", 1)) == "x"); - } -}; - -BEAST_DEFINE_TESTSUITE(to_string,core,beast); - -} // beast - diff --git a/src/beast/test/core/type_traits.cpp b/src/beast/test/core/type_traits.cpp new file mode 100644 index 0000000000..4706155bd3 --- /dev/null +++ b/src/beast/test/core/type_traits.cpp @@ -0,0 +1,155 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include +#include +#include + +namespace beast { + +namespace detail { + +namespace { + +// +// is_invocable +// + +struct is_invocable_udt1 +{ + void operator()(int) const; +}; + +struct is_invocable_udt2 +{ + int operator()(int) const; +}; + +struct is_invocable_udt3 +{ + int operator()(int); +}; + +#ifndef __INTELLISENSE__ +// VFALCO Fails to compile with Intellisense +BOOST_STATIC_ASSERT(is_invocable::value); +BOOST_STATIC_ASSERT(is_invocable::value); +BOOST_STATIC_ASSERT(is_invocable::value); +BOOST_STATIC_ASSERT(! is_invocable::value); +BOOST_STATIC_ASSERT(! is_invocable::value); +BOOST_STATIC_ASSERT(! is_invocable::value); +BOOST_STATIC_ASSERT(! is_invocable::value); +#endif + +// +// get_lowest_layer +// + +struct F1 {}; +struct F2 {}; + +template +struct F3 +{ + using next_layer_type = + typename std::remove_reference::type; + + using lowest_layer_type = typename + get_lowest_layer::type; +}; + +template +struct F4 +{ + using next_layer_type = + typename std::remove_reference::type; + + using lowest_layer_type = typename + get_lowest_layer::type; +}; + +BOOST_STATIC_ASSERT(std::is_same::type, F1>::value); +BOOST_STATIC_ASSERT(std::is_same::type, F2>::value); +BOOST_STATIC_ASSERT(std::is_same>::type, F1>::value); +BOOST_STATIC_ASSERT(std::is_same>::type, F2>::value); +BOOST_STATIC_ASSERT(std::is_same>::type, F1>::value); +BOOST_STATIC_ASSERT(std::is_same>::type, F2>::value); +BOOST_STATIC_ASSERT(std::is_same>>::type, F1>::value); +BOOST_STATIC_ASSERT(std::is_same>>::type, F2>::value); + +} // (anonymous) + +} // detail + +// +// buffer concepts +// + +namespace { + +struct T {}; + +BOOST_STATIC_ASSERT(is_const_buffer_sequence::value); +BOOST_STATIC_ASSERT(! is_const_buffer_sequence::value); + +BOOST_STATIC_ASSERT(is_mutable_buffer_sequence::value); +BOOST_STATIC_ASSERT(! is_mutable_buffer_sequence::value); + +BOOST_STATIC_ASSERT(is_dynamic_buffer::value); + +} // (anonymous) + +// +// handler concepts +// + +namespace { + +struct H +{ + void operator()(int); +}; + +} // anonymous + +BOOST_STATIC_ASSERT(is_completion_handler::value); +BOOST_STATIC_ASSERT(! is_completion_handler::value); + +// +// stream concepts +// + +namespace { + +using stream_type = boost::asio::ip::tcp::socket; + +struct not_a_stream +{ + void + get_io_service(); +}; + +BOOST_STATIC_ASSERT(has_get_io_service::value); +BOOST_STATIC_ASSERT(is_async_read_stream::value); +BOOST_STATIC_ASSERT(is_async_write_stream::value); +BOOST_STATIC_ASSERT(is_async_stream::value); +BOOST_STATIC_ASSERT(is_sync_read_stream::value); +BOOST_STATIC_ASSERT(is_sync_write_stream::value); +BOOST_STATIC_ASSERT(is_sync_stream::value); + +BOOST_STATIC_ASSERT(! has_get_io_service::value); +BOOST_STATIC_ASSERT(! is_async_read_stream::value); +BOOST_STATIC_ASSERT(! is_async_write_stream::value); +BOOST_STATIC_ASSERT(! is_sync_read_stream::value); +BOOST_STATIC_ASSERT(! is_sync_write_stream::value); + +} // (anonymous) + +} // beast diff --git a/src/beast/test/core/write_dynabuf.cpp b/src/beast/test/core/write_dynabuf.cpp deleted file mode 100644 index 36a65a6c69..0000000000 --- a/src/beast/test/core/write_dynabuf.cpp +++ /dev/null @@ -1,36 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -// Test that header file is self-contained. -#include - -#include -#include - -namespace beast { - -class write_dynabuf_test : public beast::unit_test::suite -{ -public: - void run() override - { - streambuf sb; - std::string s; - write(sb, boost::asio::const_buffer{"", 0}); - write(sb, boost::asio::mutable_buffer{nullptr, 0}); - write(sb, boost::asio::null_buffers{}); - write(sb, boost::asio::const_buffers_1{"", 0}); - write(sb, boost::asio::mutable_buffers_1{nullptr, 0}); - write(sb, s); - write(sb, 23); - pass(); - } -}; - -BEAST_DEFINE_TESTSUITE(write_dynabuf,core,beast); - -} // beast diff --git a/src/beast/test/exemplars.cpp b/src/beast/test/exemplars.cpp new file mode 100644 index 0000000000..9a8256a470 --- /dev/null +++ b/src/beast/test/exemplars.cpp @@ -0,0 +1,345 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +class BodyReader; +class BodyWriter; + +//[concept_Body + +struct Body +{ + // The type of message::body when used + struct value_type; + + /// The algorithm used for extracting buffers + using reader = BodyReader; + + /// The algorithm used for inserting buffers + using writer = BodyWriter; + + /// Returns the body's payload size + static + std::uint64_t + size(value_type const& v); +}; + +static_assert(is_body::value, ""); + +//] + +struct Body_BodyReader { + struct value_type{}; +//[concept_BodyReader + +struct BodyReader +{ +public: + /// The type of buffer returned by `get`. + using const_buffers_type = boost::asio::const_buffers_1; + + /** Construct the reader. + + @param msg The message whose body is to be retrieved. + + @param ec Set to the error, if any occurred. + */ + template + explicit + BodyReader(message const& msg); + + /** Initialize the reader + + This is called after construction and before the first + call to `get`. The message is valid and complete upon + entry. + + @param ec Set to the error, if any occurred. + */ + void + init(error_code& ec) + { + // The specification requires this to indicate "no error" + ec.assign(0, ec.category()); + } + + /** Returns the next buffer in the body. + + @li If the return value is `boost::none` (unseated optional) and + `ec` does not contain an error, this indicates the end of the + body, no more buffers are present. + + @li If the optional contains a value, the first element of the + pair represents a @b ConstBufferSequence containing one or + more octets of the body data. The second element indicates + whether or not there are additional octets of body data. + A value of `true` means there is more data, and that the + implementation will perform a subsequent call to `get`. + A value of `false` means there is no more body data. + + @li If `ec` contains an error code, the return value is ignored. + + @param ec Set to the error, if any occurred. + */ + boost::optional> + get(error_code& ec) + { + // The specification requires this to indicate "no error" + ec.assign(0, ec.category()); + + return boost::none; // for exposition only + } +}; + +//] + using reader = BodyReader; +}; + +static_assert(is_body_reader::value, ""); + +struct Body_BodyWriter { + struct value_type{}; +//[concept_BodyWriter + +struct BodyWriter +{ + /** Construct the writer. + + @param msg The message whose body is to be stored. + */ + template + explicit + BodyWriter(message& msg); + + /** Initialize the writer + + This is called after construction and before the first + call to `put`. The message is valid and complete upon + entry. + + @param ec Set to the error, if any occurred. + */ + void + init( + boost::optional const& content_length, + error_code& ec) + { + boost::ignore_unused(content_length); + + // The specification requires this to indicate "no error" + ec.assign(0, ec.category()); + } + + /** Store buffers. + + This is called zero or more times with parsed body octets. + + @param buffers The constant buffer sequence to store. + + @param ec Set to the error, if any occurred. + + @return The number of bytes transferred from the input buffers. + */ + template + std::size_t + put(ConstBufferSequence const& buffers, error_code& ec) + { + // The specification requires this to indicate "no error" + ec = {}; + + return boost::asio::buffer_size(buffers); + } + + /** Called when the body is complete. + + @param ec Set to the error, if any occurred. + */ + void + finish(error_code& ec) + { + // The specification requires this to indicate "no error" + ec = {}; + } +}; + +//] + using writer = BodyWriter; +}; + +static_assert(is_body_writer::value, ""); + +//[concept_Fields + +class Fields +{ +public: + struct reader; + +protected: + /** Returns the request-method string. + + @note Only called for requests. + */ + string_view + get_method_impl() const; + + /** Returns the request-target string. + + @note Only called for requests. + */ + string_view + get_target_impl() const; + + /** Returns the response reason-phrase string. + + @note Only called for responses. + */ + string_view + get_reason_impl() const; + + /** Returns the chunked Transfer-Encoding setting + */ + bool + get_chunked_impl() const; + + /** Returns the keep-alive setting + */ + bool + get_keep_alive_impl(unsigned version) const; + + /** Set or clear the method string. + + @note Only called for requests. + */ + void + set_method_impl(string_view s); + + /** Set or clear the target string. + + @note Only called for requests. + */ + void + set_target_impl(string_view s); + + /** Set or clear the reason string. + + @note Only called for responses. + */ + void + set_reason_impl(string_view s); + + /** Sets or clears the chunked Transfer-Encoding value + */ + void + set_chunked_impl(bool value); + + /** Sets or clears the Content-Length field + */ + void + set_content_length_impl(boost::optional); + + /** Adjusts the Connection field + */ + void + set_keep_alive_impl(unsigned version, bool keep_alive); +}; + +static_assert(is_fields::value, + "Fields requirements not met"); + +//] + +struct Fields_FieldsReader { + using F = Fields_FieldsReader; +//[concept_FieldsReader + +struct FieldsReader +{ + // The type of buffers returned by `get` + struct const_buffers_type; + + // Constructor for requests + FieldsReader(F const& f, unsigned version, verb method); + + // Constructor for responses + FieldsReader(F const& f, unsigned version, unsigned status); + + // Returns `true` if keep-alive is indicated + bool + keep_alive(); + + // Returns the serialized header buffers + const_buffers_type + get(); +}; + +//] +}; + +//[concept_File + +struct File +{ + /** Default constructor + + There is no open file initially. + */ + File(); + + /** Destructor + + If the file is open it is first closed. + */ + ~File(); + + /// Returns `true` if the file is open + bool + is_open() const; + + /// Close the file if open + void + close(error_code& ec); + + /// Open a file at the given path with the specified mode + void + open(char const* path, file_mode mode, error_code& ec); + + /// Return the size of the open file + std::uint64_t + size(error_code& ec) const; + + /// Return the current position in the open file + std::uint64_t + pos(error_code& ec) const; + + /// Adjust the current position in the open file + void + seek(std::uint64_t offset, error_code& ec); + + /// Read from the open file + std::size_t + read(void* buffer, std::size_t n, error_code& ec) const; + + /// Write to the open file + std::size_t + write(void const* buffer, std::size_t n, error_code& ec); +}; + +//] + +} // http +} // beast diff --git a/src/beast/test/http/CMakeLists.txt b/src/beast/test/http/CMakeLists.txt index 5505e37dc1..a609f6b156 100644 --- a/src/beast/test/http/CMakeLists.txt +++ b/src/beast/test/http/CMakeLists.txt @@ -1,52 +1,47 @@ # Part of Beast +GroupSources(example example) GroupSources(extras/beast extras) GroupSources(include/beast beast) + GroupSources(test/http "/") add_executable (http-tests ${BEAST_INCLUDES} + ${EXAMPLE_INCLUDES} ${EXTRAS_INCLUDES} message_fuzz.hpp - fail_parser.hpp + test_parser.hpp ../../extras/beast/unit_test/main.cpp - basic_dynabuf_body.cpp - basic_fields.cpp - basic_parser_v1.cpp - concepts.cpp + basic_parser.cpp + buffer_body.cpp + doc_examples.cpp + doc_snippets.cpp + dynamic_body.cpp empty_body.cpp + error.cpp + field.cpp fields.cpp - header_parser_v1.cpp + file_body.cpp message.cpp - parse.cpp - parse_error.cpp - parser_v1.cpp + parser.cpp read.cpp - reason.cpp rfc7230.cpp - streambuf_body.cpp + serializer.cpp + span_body.cpp + status.cpp string_body.cpp + type_traits.cpp + vector_body.cpp + verb.cpp write.cpp - chunk_encode.cpp ) -if (NOT WIN32) - target_link_libraries(http-tests ${Boost_LIBRARIES} Threads::Threads) -else() - target_link_libraries(http-tests ${Boost_LIBRARIES}) -endif() - -add_executable (bench-tests - ${BEAST_INCLUDES} - ${EXTRAS_INCLUDES} - nodejs_parser.hpp - ../../extras/beast/unit_test/main.cpp - nodejs_parser.cpp - parser_bench.cpp -) - -if (NOT WIN32) - target_link_libraries(bench-tests ${Boost_LIBRARIES} Threads::Threads) -else() - target_link_libraries(bench-tests ${Boost_LIBRARIES}) -endif() +target_link_libraries(http-tests + Beast + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_COROUTINE_LIBRARY} + ${Boost_THREAD_LIBRARY} + ${Boost_CONTEXT_LIBRARY} + ) diff --git a/src/beast/test/http/Jamfile b/src/beast/test/http/Jamfile new file mode 100644 index 0000000000..aca163002c --- /dev/null +++ b/src/beast/test/http/Jamfile @@ -0,0 +1,31 @@ +# +# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +# + +unit-test http-tests : + ../../extras/beast/unit_test/main.cpp + basic_parser.cpp + buffer_body.cpp + doc_examples.cpp + doc_snippets.cpp + dynamic_body.cpp + error.cpp + field.cpp + fields.cpp + file_body.cpp + message.cpp + parser.cpp + read.cpp + rfc7230.cpp + serializer.cpp + span_body.cpp + status.cpp + string_body.cpp + type_traits.cpp + vector_body.cpp + verb.cpp + write.cpp + ; diff --git a/src/beast/test/http/basic_fields.cpp b/src/beast/test/http/basic_fields.cpp deleted file mode 100644 index 9948bd63a5..0000000000 --- a/src/beast/test/http/basic_fields.cpp +++ /dev/null @@ -1,96 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -// Test that header file is self-contained. -#include - -#include -#include - -namespace beast { -namespace http { - -class basic_fields_test : public beast::unit_test::suite -{ -public: - template - using bha = basic_fields; - - using bh = basic_fields>; - - template - static - void - fill(std::size_t n, basic_fields& h) - { - for(std::size_t i = 1; i<= n; ++i) - h.insert(boost::lexical_cast(i), i); - } - - template - static - void - self_assign(U& u, V&& v) - { - u = std::forward(v); - } - - void testHeaders() - { - bh h1; - BEAST_EXPECT(h1.empty()); - fill(1, h1); - BEAST_EXPECT(h1.size() == 1); - bh h2; - h2 = h1; - BEAST_EXPECT(h2.size() == 1); - h2.insert("2", "2"); - BEAST_EXPECT(std::distance(h2.begin(), h2.end()) == 2); - h1 = std::move(h2); - BEAST_EXPECT(h1.size() == 2); - BEAST_EXPECT(h2.size() == 0); - bh h3(std::move(h1)); - BEAST_EXPECT(h3.size() == 2); - BEAST_EXPECT(h1.size() == 0); - self_assign(h3, std::move(h3)); - BEAST_EXPECT(h3.size() == 2); - BEAST_EXPECT(h2.erase("Not-Present") == 0); - } - - void testRFC2616() - { - bh h; - h.insert("a", "w"); - h.insert("a", "x"); - h.insert("aa", "y"); - h.insert("b", "z"); - BEAST_EXPECT(h.count("a") == 2); - } - - void testErase() - { - bh h; - h.insert("a", "w"); - h.insert("a", "x"); - h.insert("aa", "y"); - h.insert("b", "z"); - BEAST_EXPECT(h.size() == 4); - h.erase("a"); - BEAST_EXPECT(h.size() == 2); - } - - void run() override - { - testHeaders(); - testRFC2616(); - } -}; - -BEAST_DEFINE_TESTSUITE(basic_fields,http,beast); - -} // http -} // beast diff --git a/src/beast/test/http/basic_parser.cpp b/src/beast/test/http/basic_parser.cpp new file mode 100644 index 0000000000..6303fa7f28 --- /dev/null +++ b/src/beast/test/http/basic_parser.cpp @@ -0,0 +1,1164 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include "test_parser.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +class basic_parser_test : public beast::unit_test::suite +{ +public: + enum parse_flag + { + chunked = 1, + connection_keep_alive = 2, + connection_close = 4, + connection_upgrade = 8, + upgrade = 16, + }; + + class expect_version + { + suite& s_; + int version_; + + public: + expect_version(suite& s, int version) + : s_(s) + , version_(version) + { + } + + template + void + operator()(Parser const& p) const + { + s_.BEAST_EXPECT(p.version == version_); + } + }; + + class expect_status + { + suite& s_; + int status_; + + public: + expect_status(suite& s, int status) + : s_(s) + , status_(status) + { + } + + template + void + operator()(Parser const& p) const + { + s_.BEAST_EXPECT(p.status == status_); + } + }; + + class expect_flags + { + suite& s_; + unsigned flags_; + + public: + expect_flags(suite& s, unsigned flags) + : s_(s) + , flags_(flags) + { + } + + template + void + operator()(Parser const& p) const + { + if(flags_ & parse_flag::chunked) + s_.BEAST_EXPECT(p.is_chunked()); + if(flags_ & parse_flag::connection_keep_alive) + s_.BEAST_EXPECT(p.is_keep_alive()); + if(flags_ & parse_flag::connection_close) + s_.BEAST_EXPECT(! p.is_keep_alive()); + if(flags_ & parse_flag::upgrade) + s_.BEAST_EXPECT(! p.is_upgrade()); + } + }; + + class expect_keepalive + { + suite& s_; + bool v_; + + public: + expect_keepalive(suite& s, bool v) + : s_(s) + , v_(v) + { + } + + template + void + operator()(Parser const& p) const + { + s_.BEAST_EXPECT(p.is_keep_alive() == v_); + } + }; + + class expect_body + { + suite& s_; + std::string const& body_; + + public: + expect_body(expect_body&&) = default; + + expect_body(suite& s, std::string const& v) + : s_(s) + , body_(v) + { + } + + template + void + operator()(Parser const& p) const + { + s_.BEAST_EXPECT(p.body == body_); + } + }; + + //-------------------------------------------------------------------------- + + template + typename std::enable_if< + is_const_buffer_sequence::value>::type + parsegrind(ConstBufferSequence const& buffers, + Test const& test, bool skip = false) + { + auto const size = boost::asio::buffer_size(buffers); + for(std::size_t i = 1; i < size - 1; ++i) + { + Parser p; + p.eager(true); + p.skip(skip); + error_code ec; + consuming_buffers cb{buffers}; + auto n = p.put(buffer_prefix(i, cb), ec); + if(! BEAST_EXPECTS(! ec || + ec == error::need_more, ec.message())) + continue; + if(! BEAST_EXPECT(! p.is_done())) + continue; + cb.consume(n); + n = p.put(cb, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + continue; + if(! BEAST_EXPECT(n == boost::asio::buffer_size(cb))) + continue; + if(p.need_eof()) + { + p.put_eof(ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + continue; + } + if(! BEAST_EXPECT(p.is_done())) + continue; + test(p); + } + for(std::size_t i = 1; i < size - 1; ++i) + { + Parser p; + p.eager(true); + error_code ec; + consuming_buffers cb{buffers}; + cb.consume(i); + auto n = p.put(buffer_cat( + buffer_prefix(i, buffers), cb), ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + continue; + if(! BEAST_EXPECT(n == size)) + continue; + if(p.need_eof()) + { + p.put_eof(ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + continue; + } + test(p); + } + } + + template + void + parsegrind(string_view msg, Test const& test, bool skip = false) + { + parsegrind(boost::asio::const_buffers_1{ + msg.data(), msg.size()}, test, skip); + } + + template + typename std::enable_if< + is_const_buffer_sequence::value>::type + parsegrind(ConstBufferSequence const& buffers) + { + parsegrind(buffers, [](Parser const&){}); + } + + template + void + parsegrind(string_view msg) + { + parsegrind(msg, [](Parser const&){}); + } + + template + void + failgrind(string_view msg, error_code const& result) + { + for(std::size_t i = 1; i < msg.size() - 1; ++i) + { + Parser p; + p.eager(true); + error_code ec; + consuming_buffers cb{ + boost::in_place_init, msg.data(), msg.size()}; + auto n = p.put(buffer_prefix(i, cb), ec); + if(ec == result) + { + pass(); + continue; + } + if(! BEAST_EXPECTS( + ec == error::need_more, ec.message())) + continue; + if(! BEAST_EXPECT(! p.is_done())) + continue; + cb.consume(n); + n = p.put(cb, ec); + if(! ec) + p.put_eof(ec); + BEAST_EXPECTS(ec == result, ec.message()); + } + for(std::size_t i = 1; i < msg.size() - 1; ++i) + { + Parser p; + p.eager(true); + error_code ec; + p.put(buffer_cat( + boost::asio::const_buffers_1{msg.data(), i}, + boost::asio::const_buffers_1{ + msg.data() + i, msg.size() - i}), ec); + if(! ec) + p.put_eof(ec); + BEAST_EXPECTS(ec == result, ec.message()); + } + } + + //-------------------------------------------------------------------------- + + void + testFlatten() + { + parsegrind>( + "GET / HTTP/1.1\r\n" + "\r\n" + ); + parsegrind>( + "POST / HTTP/1.1\r\n" + "Content-Length: 5\r\n" + "\r\n" + "*****" + ); + parsegrind>( + "HTTP/1.1 403 Not Found\r\n" + "\r\n" + ); + parsegrind>( + "HTTP/1.1 200 OK\r\n" + "Content-Length: 5\r\n" + "\r\n" + "*****" + ); + parsegrind>( + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "5;x\r\n*****\r\n" + "0\r\nMD5: 0xff30\r\n" + "\r\n" + ); + parsegrind>( + "HTTP/1.1 200 OK\r\n" + "\r\n" + "*****" + ); + } + + void + testObsFold() + { + auto const check = + [&](std::string const& s, string_view value) + { + std::string m = + "GET / HTTP/1.1\r\n" + "f: " + s + "\r\n" + "\r\n"; + parsegrind>(m, + [&](parser const& p) + { + BEAST_EXPECT(p.get()["f"] == value); + }); + }; + check("x", "x"); + check(" x", "x"); + check("\tx", "x"); + check(" \tx", "x"); + check("\t x", "x"); + check("x ", "x"); + check(" x\t", "x"); + check("\tx \t", "x"); + check(" \tx\t ", "x"); + check("\t x \t ", "x"); + check("\r\n x", "x"); + check(" \r\n x", "x"); + check(" \r\n\tx", "x"); + check(" \r\n\t x", "x"); + check(" \r\n \tx", "x"); + check(" \r\n \r\n \r\n x \t", "x"); + check("xy", "xy"); + check("\r\n x", "x"); + check("\r\n x", "x"); + check("\r\n xy", "xy"); + check("\r\n \r\n \r\n x", "x"); + check("\r\n \r\n \r\n xy", "xy"); + check("x\r\n y", "x y"); + check("x\r\n y\r\n z ", "x y z"); + } + + // Check that all callbacks are invoked + void + testCallbacks() + { + parsegrind>( + "GET / HTTP/1.1\r\n" + "User-Agent: test\r\n" + "Content-Length: 1\r\n" + "\r\n" + "*", + [&](test_parser const& p) + { + BEAST_EXPECT(p.got_on_begin == 1); + BEAST_EXPECT(p.got_on_field == 2); + BEAST_EXPECT(p.got_on_header == 1); + BEAST_EXPECT(p.got_on_body == 1); + BEAST_EXPECT(p.got_on_chunk == 0); + BEAST_EXPECT(p.got_on_complete == 1); + }); + parsegrind>( + "HTTP/1.1 200 OK\r\n" + "Server: test\r\n" + "Content-Length: 1\r\n" + "\r\n" + "*", + [&](test_parser const& p) + { + BEAST_EXPECT(p.got_on_begin == 1); + BEAST_EXPECT(p.got_on_field == 2); + BEAST_EXPECT(p.got_on_header == 1); + BEAST_EXPECT(p.got_on_body == 1); + BEAST_EXPECT(p.got_on_chunk == 0); + BEAST_EXPECT(p.got_on_complete == 1); + }); + parsegrind>( + "HTTP/1.1 200 OK\r\n" + "Server: test\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "1\r\n*\r\n" + "0\r\n\r\n", + [&](test_parser const& p) + { + BEAST_EXPECT(p.got_on_begin == 1); + BEAST_EXPECT(p.got_on_field == 2); + BEAST_EXPECT(p.got_on_header == 1); + BEAST_EXPECT(p.got_on_body == 1); + BEAST_EXPECT(p.got_on_chunk == 1); + BEAST_EXPECT(p.got_on_complete == 1); + }); + parsegrind>( + "HTTP/1.1 200 OK\r\n" + "Server: test\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "1;x\r\n*\r\n" + "0\r\n\r\n", + [&](test_parser const& p) + { + BEAST_EXPECT(p.got_on_begin == 1); + BEAST_EXPECT(p.got_on_field == 2); + BEAST_EXPECT(p.got_on_header == 1); + BEAST_EXPECT(p.got_on_body == 1); + BEAST_EXPECT(p.got_on_chunk == 1); + BEAST_EXPECT(p.got_on_complete == 1); + }); + } + + void + testRequestLine() + { + using P = test_parser; + + parsegrind

("GET /x HTTP/1.0\r\n\r\n"); + parsegrind

("!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz / HTTP/1.0\r\n\r\n"); + parsegrind

("GET / HTTP/1.0\r\n\r\n", expect_version{*this, 10}); + parsegrind

("G / HTTP/1.1\r\n\r\n", expect_version{*this, 11}); + // VFALCO TODO various forms of good request-target (uri) + + failgrind

("\tGET / HTTP/1.0\r\n" "\r\n", error::bad_method); + failgrind

("GET\x01 / HTTP/1.0\r\n" "\r\n", error::bad_method); + failgrind

("GET / HTTP/1.0\r\n" "\r\n", error::bad_target); + failgrind

("GET \x01 HTTP/1.0\r\n" "\r\n", error::bad_target); + failgrind

("GET /\x01 HTTP/1.0\r\n" "\r\n", error::bad_target); + // VFALCO TODO various forms of bad request-target (uri) + failgrind

("GET / HTTP/1.0\r\n" "\r\n", error::bad_version); + failgrind

("GET / _TTP/1.0\r\n" "\r\n", error::bad_version); + failgrind

("GET / H_TP/1.0\r\n" "\r\n", error::bad_version); + failgrind

("GET / HT_P/1.0\r\n" "\r\n", error::bad_version); + failgrind

("GET / HTT_/1.0\r\n" "\r\n", error::bad_version); + failgrind

("GET / HTTP_1.0\r\n" "\r\n", error::bad_version); + failgrind

("GET / HTTP/01.2\r\n" "\r\n", error::bad_version); + failgrind

("GET / HTTP/3.45\r\n" "\r\n", error::bad_version); + failgrind

("GET / HTTP/67.89\r\n" "\r\n", error::bad_version); + failgrind

("GET / HTTP/x.0\r\n" "\r\n", error::bad_version); + failgrind

("GET / HTTP/1.x\r\n" "\r\n", error::bad_version); + failgrind

("GET / HTTP/1.0 \r\n" "\r\n", error::bad_version); + failgrind

("GET / HTTP/1_0\r\n" "\r\n", error::bad_version); + failgrind

("GET / HTTP/1.0\n\r\n" "\r\n", error::bad_version); + failgrind

("GET / HTTP/1.0\n\r\r\n" "\r\n", error::bad_version); + failgrind

("GET / HTTP/1.0\r\r\n" "\r\n", error::bad_version); + } + + void + testStatusLine() + { + using P = test_parser; + + parsegrind

("HTTP/1.0 000 OK\r\n" "\r\n", expect_status{*this, 0}); + parsegrind

("HTTP/1.1 012 OK\r\n" "\r\n", expect_status{*this, 12}); + parsegrind

("HTTP/1.0 345 OK\r\n" "\r\n", expect_status{*this, 345}); + parsegrind

("HTTP/1.0 678 OK\r\n" "\r\n", expect_status{*this, 678}); + parsegrind

("HTTP/1.0 999 OK\r\n" "\r\n", expect_status{*this, 999}); + parsegrind

("HTTP/1.0 200 \tX\r\n" "\r\n", expect_version{*this, 10}); + parsegrind

("HTTP/1.1 200 X\r\n" "\r\n", expect_version{*this, 11}); + parsegrind

("HTTP/1.0 200 \r\n" "\r\n"); + parsegrind

("HTTP/1.1 200 X \r\n" "\r\n"); + parsegrind

("HTTP/1.1 200 X\t\r\n" "\r\n"); + parsegrind

("HTTP/1.1 200 \x80\x81...\xfe\xff\r\n\r\n"); + parsegrind

("HTTP/1.1 200 !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\r\n\r\n"); + + failgrind

("\rHTTP/1.0 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("\nHTTP/1.0 200 OK\r\n" "\r\n", error::bad_version); + failgrind

(" HTTP/1.0 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("_TTP/1.0 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("H_TP/1.0 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("HT_P/1.0 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("HTT_/1.0 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("HTTP_1.0 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("HTTP/01.2 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("HTTP/3.45 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("HTTP/67.89 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("HTTP/x.0 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("HTTP/1.x 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("HTTP/1_0 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("HTTP/1.0 200 OK\r\n" "\r\n", error::bad_status); + failgrind

("HTTP/1.0 0 OK\r\n" "\r\n", error::bad_status); + failgrind

("HTTP/1.0 12 OK\r\n" "\r\n", error::bad_status); + failgrind

("HTTP/1.0 3456 OK\r\n" "\r\n", error::bad_status); + failgrind

("HTTP/1.0 200\r\n" "\r\n", error::bad_status); + failgrind

("HTTP/1.0 200 \n\r\n" "\r\n", error::bad_reason); + failgrind

("HTTP/1.0 200 \x01\r\n" "\r\n", error::bad_reason); + failgrind

("HTTP/1.0 200 \x7f\r\n" "\r\n", error::bad_reason); + failgrind

("HTTP/1.0 200 OK\n\r\n" "\r\n", error::bad_reason); + failgrind

("HTTP/1.0 200 OK\r\r\n" "\r\n", error::bad_line_ending); + } + + void + testFields() + { + auto const m = + [](std::string const& s) + { + return "GET / HTTP/1.1\r\n" + s + "\r\n"; + }; + + using P = test_parser; + + parsegrind

(m("f:\r\n")); + parsegrind

(m("f: \r\n")); + parsegrind

(m("f:\t\r\n")); + parsegrind

(m("f: \t\r\n")); + parsegrind

(m("f: v\r\n")); + parsegrind

(m("f:\tv\r\n")); + parsegrind

(m("f:\tv \r\n")); + parsegrind

(m("f:\tv\t\r\n")); + parsegrind

(m("f:\tv\t \r\n")); + parsegrind

(m("f:\r\n \r\n")); + parsegrind

(m("f:v\r\n")); + parsegrind

(m("f: v\r\n u\r\n")); + parsegrind

(m("!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz: v\r\n")); + parsegrind

(m("f: !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x80\x81...\xfe\xff\r\n")); + + failgrind

(m(" f: v\r\n"), error::bad_field); + failgrind

(m("\tf: v\r\n"), error::bad_field); + failgrind

(m("f : v\r\n"), error::bad_field); + failgrind

(m("f\t: v\r\n"), error::bad_field); + failgrind

(m("f: \n\r\n"), error::bad_value); + failgrind

(m("f: v\r \r\n"), error::bad_line_ending); + failgrind

(m("f: \r v\r\n"), error::bad_line_ending); + failgrind

( + "GET / HTTP/1.1\r\n" + "\r \n\r\n" + "\r\n", error::bad_line_ending); + } + + void + testConnectionField() + { + auto const m = [](std::string const& s) + { return "GET / HTTP/1.1\r\n" + s + "\r\n"; }; + auto const cn = [](std::string const& s) + { return "GET / HTTP/1.1\r\nConnection: " + s + "\r\n"; }; + #if 0 + auto const keepalive = [&](bool v) + { //return keepalive_f{*this, v}; return true; }; + #endif + + using P = test_parser; + + parsegrind

(cn("close\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn(",close\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn(" close\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn("\tclose\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn("close,\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn("close\t\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn("close\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn(" ,\t,,close,, ,\t,,\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn("\r\n close\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn("close\r\n \r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn("any,close\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn("close,any\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn("any\r\n ,close\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn("close\r\n ,any\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn("close,close\r\n"), expect_flags{*this, parse_flag::connection_close}); // weird but allowed + + parsegrind

(cn("keep-alive\r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + parsegrind

(cn("keep-alive \r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + parsegrind

(cn("keep-alive\t \r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + parsegrind

(cn("keep-alive\t ,x\r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + parsegrind

(cn("\r\n keep-alive \t\r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + parsegrind

(cn("keep-alive \r\n \t \r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + parsegrind

(cn("keep-alive\r\n \r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + + parsegrind

(cn("upgrade\r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + parsegrind

(cn("upgrade \r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + parsegrind

(cn("upgrade\t \r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + parsegrind

(cn("upgrade\t ,x\r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + parsegrind

(cn("\r\n upgrade \t\r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + parsegrind

(cn("upgrade \r\n \t \r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + parsegrind

(cn("upgrade\r\n \r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + + // VFALCO What's up with these? + //parsegrind

(cn("close,keep-alive\r\n"), expect_flags{*this, parse_flag::connection_close | parse_flag::connection_keep_alive}); + parsegrind

(cn("upgrade,keep-alive\r\n"), expect_flags{*this, parse_flag::connection_upgrade | parse_flag::connection_keep_alive}); + parsegrind

(cn("upgrade,\r\n keep-alive\r\n"), expect_flags{*this, parse_flag::connection_upgrade | parse_flag::connection_keep_alive}); + //parsegrind

(cn("close,keep-alive,upgrade\r\n"), expect_flags{*this, parse_flag::connection_close | parse_flag::connection_keep_alive | parse_flag::connection_upgrade}); + + parsegrind

("GET / HTTP/1.1\r\n\r\n", expect_keepalive(*this, true)); + parsegrind

("GET / HTTP/1.0\r\n\r\n", expect_keepalive(*this, false)); + parsegrind

("GET / HTTP/1.0\r\n" + "Connection: keep-alive\r\n\r\n", expect_keepalive(*this, true)); + parsegrind

("GET / HTTP/1.1\r\n" + "Connection: close\r\n\r\n", expect_keepalive(*this, false)); + + parsegrind

(cn("x\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("x,y\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("x ,y\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("x\t,y\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("keep\r\n"), expect_flags{*this, 0}); + parsegrind

(cn(",keep\r\n"), expect_flags{*this, 0}); + parsegrind

(cn(" keep\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("\tnone\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("keep,\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("keep\t\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("keep\r\n"), expect_flags{*this, 0}); + parsegrind

(cn(" ,\t,,keep,, ,\t,,\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("\r\n keep\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("keep\r\n \r\n"), expect_flags{*this, 0}); + parsegrind

(cn("closet\r\n"), expect_flags{*this, 0}); + parsegrind

(cn(",closet\r\n"), expect_flags{*this, 0}); + parsegrind

(cn(" closet\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("\tcloset\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("closet,\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("closet\t\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("closet\r\n"), expect_flags{*this, 0}); + parsegrind

(cn(" ,\t,,closet,, ,\t,,\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("\r\n closet\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("closet\r\n \r\n"), expect_flags{*this, 0}); + parsegrind

(cn("clog\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("key\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("uptown\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("keeper\r\n \r\n"), expect_flags{*this, 0}); + parsegrind

(cn("keep-alively\r\n \r\n"), expect_flags{*this, 0}); + parsegrind

(cn("up\r\n \r\n"), expect_flags{*this, 0}); + parsegrind

(cn("upgrader\r\n \r\n"), expect_flags{*this, 0}); + parsegrind

(cn("none\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("\r\n none\r\n"), expect_flags{*this, 0}); + + parsegrind

(m("ConnectioX: close\r\n"), expect_flags{*this, 0}); + parsegrind

(m("Condor: close\r\n"), expect_flags{*this, 0}); + parsegrind

(m("Connect: close\r\n"), expect_flags{*this, 0}); + parsegrind

(m("Connections: close\r\n"), expect_flags{*this, 0}); + + parsegrind

(m("Proxy-Connection: close\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(m("Proxy-Connection: keep-alive\r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + parsegrind

(m("Proxy-Connection: upgrade\r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + parsegrind

(m("Proxy-ConnectioX: none\r\n"), expect_flags{*this, 0}); + parsegrind

(m("Proxy-Connections: 1\r\n"), expect_flags{*this, 0}); + parsegrind

(m("Proxy-Connotes: see-also\r\n"), expect_flags{*this, 0}); + + failgrind

(cn("[\r\n"), error::bad_value); + failgrind

(cn("close[\r\n"), error::bad_value); + failgrind

(cn("close [\r\n"), error::bad_value); + failgrind

(cn("close, upgrade [\r\n"), error::bad_value); + failgrind

(cn("upgrade[]\r\n"), error::bad_value); + failgrind

(cn("keep\r\n -alive\r\n"), error::bad_value); + failgrind

(cn("keep-alive[\r\n"), error::bad_value); + failgrind

(cn("keep-alive []\r\n"), error::bad_value); + failgrind

(cn("no[ne]\r\n"), error::bad_value); + } + + void + testContentLengthField() + { + using P = test_parser; + auto const c = [](std::string const& s) + { return "GET / HTTP/1.1\r\nContent-Length: " + s + "\r\n"; }; + auto const m = [](std::string const& s) + { return "GET / HTTP/1.1\r\n" + s + "\r\n"; }; + auto const check = + [&](std::string const& s, std::uint64_t v) + { + parsegrind

(c(s), + [&](P const& p) + { + BEAST_EXPECT(p.content_length()); + BEAST_EXPECT(p.content_length() && *p.content_length() == v); + }, true); + }; + + check("0\r\n", 0); + check("00\r\n", 0); + check("1\r\n", 1); + check("01\r\n", 1); + check("9\r\n", 9); + check("42 \r\n", 42); + check("42\t\r\n", 42); + check("42 \t \r\n", 42); + check("42\r\n \t \r\n", 42); + + parsegrind

(m("Content-LengtX: 0\r\n"), expect_flags{*this, 0}); + parsegrind

(m("Content-Lengths: many\r\n"), expect_flags{*this, 0}); + parsegrind

(m("Content: full\r\n"), expect_flags{*this, 0}); + + failgrind

(c("\r\n"), error::bad_content_length); + failgrind

(c("18446744073709551616\r\n"), error::bad_content_length); + failgrind

(c("0 0\r\n"), error::bad_content_length); + failgrind

(c("0 1\r\n"), error::bad_content_length); + failgrind

(c(",\r\n"), error::bad_content_length); + failgrind

(c("0,\r\n"), error::bad_content_length); + failgrind

(m( + "Content-Length: 0\r\nContent-Length: 0\r\n"), error::bad_content_length); + } + + void + testTransferEncodingField() + { + auto const m = [](std::string const& s) + { return "GET / HTTP/1.1\r\n" + s + "\r\n"; }; + auto const ce = [](std::string const& s) + { return "GET / HTTP/1.1\r\nTransfer-Encoding: " + s + "\r\n0\r\n\r\n"; }; + auto const te = [](std::string const& s) + { return "GET / HTTP/1.1\r\nTransfer-Encoding: " + s + "\r\n"; }; + + using P = test_parser; + + parsegrind

(ce("chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("chunked \r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("chunked\t\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("chunked \t\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce(" chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("\tchunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("chunked,\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("chunked ,\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("chunked, \r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce(",chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce(", chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce(" ,chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("chunked\r\n \r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("\r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce(",\r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("\r\n ,chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce(",\r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("gzip, chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("gzip, chunked \r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("gzip, \r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + + // Technically invalid but beyond the parser's scope to detect + // VFALCO Look into this + //parsegrind

(ce("custom;key=\",chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + + parsegrind

(te("gzip\r\n"), expect_flags{*this, 0}); + parsegrind

(te("chunked, gzip\r\n"), expect_flags{*this, 0}); + parsegrind

(te("chunked\r\n , gzip\r\n"), expect_flags{*this, 0}); + parsegrind

(te("chunked,\r\n gzip\r\n"), expect_flags{*this, 0}); + parsegrind

(te("chunked,\r\n ,gzip\r\n"), expect_flags{*this, 0}); + parsegrind

(te("bigchunked\r\n"), expect_flags{*this, 0}); + parsegrind

(te("chunk\r\n ked\r\n"), expect_flags{*this, 0}); + parsegrind

(te("bar\r\n ley chunked\r\n"), expect_flags{*this, 0}); + parsegrind

(te("barley\r\n chunked\r\n"), expect_flags{*this, 0}); + + parsegrind

(m("Transfer-EncodinX: none\r\n"), expect_flags{*this, 0}); + parsegrind

(m("Transfer-Encodings: 2\r\n"), expect_flags{*this, 0}); + parsegrind

(m("Transfer-Encoded: false\r\n"), expect_flags{*this, 0}); + + failgrind>( + "HTTP/1.1 200 OK\r\n" + "Content-Length: 1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n", error::bad_transfer_encoding); + } + + void + testUpgradeField() + { + auto const m = [](std::string const& s) + { return "GET / HTTP/1.1\r\n" + s + "\r\n"; }; + + using P = test_parser; + + parsegrind

(m("Upgrade:\r\n"), expect_flags{*this, parse_flag::upgrade}); + parsegrind

(m("Upgrade: \r\n"), expect_flags{*this, parse_flag::upgrade}); + parsegrind

(m("Upgrade: yes\r\n"), expect_flags{*this, parse_flag::upgrade}); + + parsegrind

(m("Up: yes\r\n"), expect_flags{*this, 0}); + parsegrind

(m("UpgradX: none\r\n"), expect_flags{*this, 0}); + parsegrind

(m("Upgrades: 2\r\n"), expect_flags{*this, 0}); + parsegrind

(m("Upsample: 4x\r\n"), expect_flags{*this, 0}); + + parsegrind

( + "GET / HTTP/1.1\r\n" + "Connection: upgrade\r\n" + "Upgrade: WebSocket\r\n" + "\r\n", + [&](P const& p) + { + BEAST_EXPECT(p.is_upgrade()); + }); + } + + void + testPartial() + { + // Make sure the slow-loris defense works and that + // we don't get duplicate or missing fields on a split. + parsegrind>( + "GET / HTTP/1.1\r\n" + "a: 0\r\n" + "b: 1\r\n" + "c: 2\r\n" + "d: 3\r\n" + "e: 4\r\n" + "f: 5\r\n" + "g: 6\r\n" + "h: 7\r\n" + "i: 8\r\n" + "j: 9\r\n" + "\r\n", + [&](test_parser const& p) + { + BEAST_EXPECT(p.fields.size() == 10); + BEAST_EXPECT(p.fields.at("a") == "0"); + BEAST_EXPECT(p.fields.at("b") == "1"); + BEAST_EXPECT(p.fields.at("c") == "2"); + BEAST_EXPECT(p.fields.at("d") == "3"); + BEAST_EXPECT(p.fields.at("e") == "4"); + BEAST_EXPECT(p.fields.at("f") == "5"); + BEAST_EXPECT(p.fields.at("g") == "6"); + BEAST_EXPECT(p.fields.at("h") == "7"); + BEAST_EXPECT(p.fields.at("i") == "8"); + BEAST_EXPECT(p.fields.at("j") == "9"); + }); + } + + void + testLimits() + { + { + multi_buffer b; + ostream(b) << + "POST / HTTP/1.1\r\n" + "Content-Length: 2\r\n" + "\r\n" + "**"; + error_code ec; + test_parser p; + p.header_limit(10); + p.eager(true); + p.put(b.data(), ec); + BEAST_EXPECTS(ec == error::header_limit, ec.message()); + } + { + multi_buffer b; + ostream(b) << + "POST / HTTP/1.1\r\n" + "Content-Length: 2\r\n" + "\r\n" + "**"; + error_code ec; + test_parser p; + p.body_limit(1); + p.eager(true); + p.put(b.data(), ec); + BEAST_EXPECTS(ec == error::body_limit, ec.message()); + } + { + multi_buffer b; + ostream(b) << + "HTTP/1.1 200 OK\r\n" + "\r\n" + "**"; + error_code ec; + test_parser p; + p.body_limit(1); + p.eager(true); + p.put(b.data(), ec); + BEAST_EXPECTS(ec == error::body_limit, ec.message()); + } + { + multi_buffer b; + ostream(b) << + "POST / HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "2\r\n" + "**\r\n" + "0\r\n\r\n"; + error_code ec; + test_parser p; + p.body_limit(1); + p.eager(true); + p.put(b.data(), ec); + BEAST_EXPECTS(ec == error::body_limit, ec.message()); + } + } + + //-------------------------------------------------------------------------- + + static + boost::asio::const_buffers_1 + buf(string_view s) + { + return {s.data(), s.size()}; + } + + template + std::size_t + feed(ConstBufferSequence const& buffers, + basic_parser& p, error_code& ec) + { + p.eager(true); + return p.put(buffers, ec); + } + + void + testBody() + { + parsegrind>( + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "Content-Type: application/octet-stream\r\n" + "\r\n" + "4\r\nabcd\r\n" + "0\r\n\r\n" + ,[&](test_parser const& p) + { + BEAST_EXPECT(p.body == "abcd"); + }); + parsegrind>( + "HTTP/1.1 200 OK\r\n" + "Server: test\r\n" + "Expect: Expires, MD5-Fingerprint\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "5\r\n" + "*****\r\n" + "2;a;b=1;c=\"2\"\r\n" + "--\r\n" + "0;d;e=3;f=\"4\"\r\n" + "Expires: never\r\n" + "MD5-Fingerprint: -\r\n" + "\r\n" + ,[&](test_parser const& p) + { + BEAST_EXPECT(p.body == "*****--"); + }); + + parsegrind>( + "GET / HTTP/1.1\r\n" + "Content-Length: 1\r\n" + "\r\n" + "1", + expect_body(*this, "1")); + + parsegrind>( + "HTTP/1.0 200 OK\r\n" + "\r\n" + "hello", + expect_body(*this, "hello")); + + parsegrind>(buffer_cat( + buf("GET / HTTP/1.1\r\n" + "Content-Length: 10\r\n" + "\r\n"), + buf("12"), + buf("345"), + buf("67890"))); + + // request without Content-Length or + // Transfer-Encoding: chunked has no body. + { + error_code ec; + test_parser p; + feed(buf( + "GET / HTTP/1.0\r\n" + "\r\n" + ), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_done()); + } + { + error_code ec; + test_parser p; + feed(buf( + "GET / HTTP/1.1\r\n" + "\r\n" + ), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_done()); + } + + // response without Content-Length or + // Transfer-Encoding: chunked requires eof. + { + error_code ec; + test_parser p; + feed(buf( + "HTTP/1.0 200 OK\r\n" + "\r\n" + ), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(! p.is_done()); + BEAST_EXPECT(p.need_eof()); + } + + // 304 "Not Modified" response does not require eof + { + error_code ec; + test_parser p; + feed(buf( + "HTTP/1.0 304 Not Modified\r\n" + "\r\n" + ), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_done()); + } + + // Chunked response does not require eof + { + error_code ec; + test_parser p; + feed(buf( + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + ), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(! p.is_done()); + feed(buf( + "0\r\n\r\n" + ), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_done()); + } + + // restart: 1.0 assumes Connection: close + { + error_code ec; + test_parser p; + feed(buf( + "GET / HTTP/1.0\r\n" + "\r\n" + ), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_done()); + } + + // restart: 1.1 assumes Connection: keep-alive + { + error_code ec; + test_parser p; + feed(buf( + "GET / HTTP/1.1\r\n" + "\r\n" + ), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_done()); + } + + failgrind>( + "GET / HTTP/1.1\r\n" + "Content-Length: 1\r\n" + "\r\n", + error::partial_message); + } + + //-------------------------------------------------------------------------- + + // https://github.com/vinniefalco/Beast/issues/430 + void + testIssue430() + { + parsegrind>( + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "Content-Type: application/octet-stream\r\n" + "\r\n" + "4\r\nabcd\r\n" + "0\r\n\r\n"); + } + + // https://github.com/vinniefalco/Beast/issues/452 + void + testIssue452() + { + error_code ec; + test_parser p; + p.eager(true); + string_view s = + "GET / HTTP/1.1\r\n" + "\r\n" + "die!"; + p.put(boost::asio::buffer( + s.data(), s.size()), ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + BEAST_EXPECT(p.is_done()); + } + + // https://github.com/vinniefalco/Beast/issues/496 + void + testIssue496() + { + // The bug affected hex parsing with leading zeroes + using P = test_parser; + parsegrind

( + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "Content-Type: application/octet-stream\r\n" + "\r\n" + "0004\r\nabcd\r\n" + "0\r\n\r\n" + ,[&](P const& p) + { + BEAST_EXPECT(p.body == "abcd"); + }); + } + + //-------------------------------------------------------------------------- + + void + testFuzz1() + { + // crash_00cda0b02d5166bd1039ddb3b12618cd80da75f3 + unsigned char buf[] ={ + 0x4C,0x4F,0x43,0x4B,0x20,0x2F,0x25,0x65,0x37,0x6C,0x59,0x3B,0x2F,0x3B,0x3B,0x25,0x30,0x62,0x38,0x3D,0x70,0x2F,0x72,0x20, + 0x48,0x54,0x54,0x50,0x2F,0x31,0x2E,0x31,0x0D,0x0A,0x41,0x63,0x63,0x65,0x70,0x74,0x2D,0x45,0x6E,0x63,0x6F,0x64,0x69,0x6E, + 0x67,0x3A,0x0D,0x0A,0x09,0x20,0xEE,0x0D,0x0A,0x4F,0x72,0x69,0x67,0x69,0x6E,0x61,0x6C,0x2D,0x4D,0x65,0x73,0x73,0x61,0x67, + 0x65,0x2D,0x49,0x44,0x3A,0xEB,0x09,0x09,0x09,0x09,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x3A,0x20,0x0D,0x0A,0x09,0x20, + 0xF7,0x44,0x9B,0xA5,0x06,0x9F,0x0D,0x0A,0x52,0x65,0x73,0x65,0x6E,0x74,0x2D,0x44,0x61,0x74,0x65,0x3A,0xF4,0x0D,0x0A,0x41, + 0x6C,0x74,0x2D,0x53,0x76,0x63,0x3A,0x20,0x0D,0x0A,0x54,0x72,0x61,0x69,0x6C,0x65,0x72,0x3A,0x20,0x20,0x09,0x20,0x20,0x20, + 0x0D,0x0A,0x4C,0x69,0x73,0x74,0x2D,0x49,0x44,0x3A,0xA6,0x6B,0x86,0x09,0x09,0x20,0x09,0x0D,0x0A,0x41,0x6C,0x74,0x65,0x72, + 0x6E,0x61,0x74,0x65,0x2D,0x52,0x65,0x63,0x69,0x70,0x69,0x65,0x6E,0x74,0x3A,0xF3,0x13,0xE3,0x22,0x9D,0xEF,0xFB,0x84,0x71, + 0x4A,0xCC,0xBC,0x96,0xF7,0x5B,0x72,0xF1,0xF2,0x0D,0x0A,0x4C,0x6F,0x63,0x61,0x74,0x69,0x6F,0x6E,0x3A,0x20,0x0D,0x0A,0x41, + 0x63,0x63,0x65,0x70,0x74,0x2D,0x41,0x64,0x64,0x69,0x74,0x69,0x6F,0x6E,0x73,0x3A,0x20,0x0D,0x0A,0x4D,0x4D,0x48,0x53,0x2D, + 0x4F,0x72,0x69,0x67,0x69,0x6E,0x61,0x74,0x6F,0x72,0x2D,0x50,0x4C,0x41,0x44,0x3A,0x20,0x0D,0x0A,0x4F,0x72,0x69,0x67,0x69, + 0x6E,0x61,0x6C,0x2D,0x53,0x65,0x6E,0x64,0x65,0x72,0x3A,0x20,0x0D,0x0A,0x4F,0x72,0x69,0x67,0x69,0x6E,0x61,0x6C,0x2D,0x53, + 0x65,0x6E,0x64,0x65,0x72,0x3A,0x0D,0x0A,0x50,0x49,0x43,0x53,0x2D,0x4C,0x61,0x62,0x65,0x6C,0x3A,0x0D,0x0A,0x20,0x09,0x0D, + 0x0A,0x49,0x66,0x3A,0x20,0x40,0xC1,0x50,0x5C,0xD6,0xC3,0x86,0xFC,0x8D,0x5C,0x7C,0x96,0x45,0x0D,0x0A,0x4D,0x4D,0x48,0x53, + 0x2D,0x45,0x78,0x65,0x6D,0x70,0x74,0x65,0x64,0x2D,0x41,0x64,0x64,0x72,0x65,0x73,0x73,0x3A,0x0D,0x0A,0x49,0x6E,0x6A,0x65, + 0x63,0x74,0x69,0x6F,0x6E,0x2D,0x49,0x6E,0x66,0x6F,0x3A,0x20,0x0D,0x0A,0x43,0x6F,0x6E,0x74,0x65,0x74,0x6E,0x2D,0x4C,0x65, + 0x6E,0x67,0x74,0x68,0x3A,0x20,0x30,0x0D,0x0A,0x0D,0x0A + }; + + error_code ec; + test_parser p; + feed(boost::asio::buffer(buf, sizeof(buf)), p, ec); + BEAST_EXPECT(ec); + } + + //-------------------------------------------------------------------------- + + void + run() override + { + testFlatten(); + testObsFold(); + testCallbacks(); + testRequestLine(); + testStatusLine(); + testFields(); + testConnectionField(); + testContentLengthField(); + testTransferEncodingField(); + testUpgradeField(); + testPartial(); + testLimits(); + testBody(); + testIssue430(); + testIssue452(); + testIssue496(); + testFuzz1(); + } +}; + +BEAST_DEFINE_TESTSUITE(basic_parser,http,beast); + +} // http +} // beast diff --git a/src/beast/test/http/basic_parser_v1.cpp b/src/beast/test/http/basic_parser_v1.cpp deleted file mode 100644 index 5c88a98dee..0000000000 --- a/src/beast/test/http/basic_parser_v1.cpp +++ /dev/null @@ -1,1175 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -// Test that header file is self-contained. -#include - -#include "fail_parser.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace http { - -class basic_parser_v1_test : public beast::unit_test::suite -{ -public: - struct cb_req_checker - { - bool method = false; - bool uri = false; - bool request = false; - }; - - struct cb_res_checker - { - bool reason = false; - bool response = false; - }; - - template - struct cb_checker - : public basic_parser_v1> - , std::conditional::type - - { - bool start = false; - bool field = false; - bool value = false; - bool fields = false; - bool _body_what = false; - bool body = false; - bool complete = false; - - private: - friend class basic_parser_v1>; - - void on_start(error_code&) - { - this->start = true; - } - void on_method(boost::string_ref const&, error_code&) - { - this->method = true; - } - void on_uri(boost::string_ref const&, error_code&) - { - this->uri = true; - } - void on_reason(boost::string_ref const&, error_code&) - { - this->reason = true; - } - void on_request(error_code&) - { - this->request = true; - } - void on_response(error_code&) - { - this->response = true; - } - void on_field(boost::string_ref const&, error_code&) - { - field = true; - } - void on_value(boost::string_ref const&, error_code&) - { - value = true; - } - void - on_header(std::uint64_t, error_code&) - { - fields = true; - } - body_what - on_body_what(std::uint64_t, error_code&) - { - _body_what = true; - return body_what::normal; - } - void on_body(boost::string_ref const&, error_code&) - { - body = true; - } - void on_complete(error_code&) - { - complete = true; - } - }; - - // Check that all callbacks are invoked - void - testCallbacks() - { - using boost::asio::buffer; - { - cb_checker p; - error_code ec; - std::string const s = - "GET / HTTP/1.1\r\n" - "User-Agent: test\r\n" - "Content-Length: 1\r\n" - "\r\n" - "*"; - p.write(buffer(s), ec); - if(BEAST_EXPECT(! ec)) - { - BEAST_EXPECT(p.start); - BEAST_EXPECT(p.method); - BEAST_EXPECT(p.uri); - BEAST_EXPECT(p.request); - BEAST_EXPECT(p.field); - BEAST_EXPECT(p.value); - BEAST_EXPECT(p.fields); - BEAST_EXPECT(p._body_what); - BEAST_EXPECT(p.body); - BEAST_EXPECT(p.complete); - } - } - { - cb_checker p; - error_code ec; - std::string const s = - "HTTP/1.1 200 OK\r\n" - "Server: test\r\n" - "Content-Length: 1\r\n" - "\r\n" - "*"; - p.write(buffer(s), ec); - if(BEAST_EXPECT(! ec)) - { - BEAST_EXPECT(p.start); - BEAST_EXPECT(p.reason); - BEAST_EXPECT(p.response); - BEAST_EXPECT(p.field); - BEAST_EXPECT(p.value); - BEAST_EXPECT(p.fields); - BEAST_EXPECT(p.body); - BEAST_EXPECT(p.complete); - } - } - } - - //-------------------------------------------------------------------------- - - template - static - void - for_split(boost::string_ref const& s, F const& f) - { - using boost::asio::buffer; - using boost::asio::buffer_copy; - for(std::size_t i = 0; i < s.size(); ++i) - { - // Use separately allocated buffers so - // address sanitizer has something to chew on. - // - auto const n1 = s.size() - i; - auto const n2 = i; - std::unique_ptr p1(new char[n1]); - std::unique_ptr p2(new char[n2]); - buffer_copy(buffer(p1.get(), n1), buffer(s.data(), n1)); - buffer_copy(buffer(p2.get(), n2), buffer(s.data() + n1, n2)); - f( - boost::string_ref{p1.get(), n1}, - boost::string_ref{p2.get(), n2}); - } - } - - struct none - { - template - void - operator()(Parser const&) const - { - } - }; - - template - void - good(body_what onBodyRv, std::string const& s, F const& f) - { - using boost::asio::buffer; - for_split(s, - [&](boost::string_ref const& s1, boost::string_ref const& s2) - { - static std::size_t constexpr Limit = 200; - std::size_t n; - for(n = 0; n < Limit; ++n) - { - test::fail_counter fc(n); - fail_parser p(fc); - p.on_body_rv(onBodyRv); - error_code ec; - p.write(buffer(s1.data(), s1.size()), ec); - if(ec == test::error::fail_error) - continue; - if(! BEAST_EXPECT(! ec)) - break; - if(! BEAST_EXPECT(s2.empty() || ! p.complete())) - break; - p.write(buffer(s2.data(), s2.size()), ec); - if(ec == test::error::fail_error) - continue; - if(! BEAST_EXPECT(! ec)) - break; - p.write_eof(ec); - if(ec == test::error::fail_error) - continue; - if(! BEAST_EXPECT(! ec)) - break; - BEAST_EXPECT(p.complete()); - f(p); - break; - } - BEAST_EXPECT(n < Limit); - }); - } - - template - void - good(std::string const& s, F const& f = {}) - { - return good(body_what::normal, s, f); - } - - template - void - bad(body_what onBodyRv, std::string const& s, error_code ev) - { - using boost::asio::buffer; - for_split(s, - [&](boost::string_ref const& s1, boost::string_ref const& s2) - { - static std::size_t constexpr Limit = 200; - std::size_t n; - for(n = 0; n < Limit; ++n) - { - test::fail_counter fc(n); - fail_parser p(fc); - p.on_body_rv(onBodyRv); - error_code ec; - p.write(buffer(s1.data(), s1.size()), ec); - if(ec == test::error::fail_error) - continue; - if(ec) - { - BEAST_EXPECT((ec && ! ev) || ec == ev); - break; - } - if(! BEAST_EXPECT(! p.complete())) - break; - if(! s2.empty()) - { - p.write(buffer(s2.data(), s2.size()), ec); - if(ec == test::error::fail_error) - continue; - if(ec) - { - BEAST_EXPECT((ec && ! ev) || ec == ev); - break; - } - if(! BEAST_EXPECT(! p.complete())) - break; - } - p.write_eof(ec); - if(ec == test::error::fail_error) - continue; - BEAST_EXPECT(! p.complete()); - BEAST_EXPECT((ec && ! ev) || ec == ev); - break; - } - BEAST_EXPECT(n < Limit); - }); - } - - template - void - bad(std::string const& s, error_code ev = {}) - { - return bad(body_what::normal, s, ev); - } - - //-------------------------------------------------------------------------- - - class version - { - suite& s_; - unsigned major_; - unsigned minor_; - - public: - version(suite& s, unsigned major, unsigned minor) - : s_(s) - , major_(major) - , minor_(minor) - { - } - - template - void - operator()(Parser const& p) const - { - s_.BEAST_EXPECT(p.http_major() == major_); - s_.BEAST_EXPECT(p.http_minor() == minor_); - } - }; - - class status - { - suite& s_; - unsigned code_; - public: - status(suite& s, int code) - : s_(s) - , code_(code) - { - } - - template - void - operator()(Parser const& p) const - { - s_.BEAST_EXPECT(p.status_code() == code_); - } - }; - - void testRequestLine() - { - /* - request-line = method SP request-target SP HTTP-version CRLF - method = token - request-target = origin-form / absolute-form / authority-form / asterisk-form - HTTP-version = "HTTP/" DIGIT "." DIGIT - */ - good("GET /x HTTP/1.0\r\n\r\n"); - good("!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz / HTTP/1.0\r\n\r\n"); - good("GET / HTTP/1.0\r\n\r\n", version{*this, 1, 0}); - good("G / HTTP/1.1\r\n\r\n", version{*this, 1, 1}); - // VFALCO TODO various forms of good request-target (uri) - good("GET / HTTP/0.1\r\n\r\n", version{*this, 0, 1}); - good("GET / HTTP/2.3\r\n\r\n", version{*this, 2, 3}); - good("GET / HTTP/4.5\r\n\r\n", version{*this, 4, 5}); - good("GET / HTTP/6.7\r\n\r\n", version{*this, 6, 7}); - good("GET / HTTP/8.9\r\n\r\n", version{*this, 8, 9}); - - bad("\tGET / HTTP/1.0\r\n" "\r\n", parse_error::bad_method); - bad("GET\x01 / HTTP/1.0\r\n" "\r\n", parse_error::bad_method); - bad("GET / HTTP/1.0\r\n" "\r\n", parse_error::bad_uri); - bad("GET \x01 HTTP/1.0\r\n" "\r\n", parse_error::bad_uri); - bad("GET /\x01 HTTP/1.0\r\n" "\r\n", parse_error::bad_uri); - // VFALCO TODO various forms of bad request-target (uri) - bad("GET / HTTP/1.0\r\n" "\r\n", parse_error::bad_version); - bad("GET / _TTP/1.0\r\n" "\r\n", parse_error::bad_version); - bad("GET / H_TP/1.0\r\n" "\r\n", parse_error::bad_version); - bad("GET / HT_P/1.0\r\n" "\r\n", parse_error::bad_version); - bad("GET / HTT_/1.0\r\n" "\r\n", parse_error::bad_version); - bad("GET / HTTP_1.0\r\n" "\r\n", parse_error::bad_version); - bad("GET / HTTP/01.2\r\n" "\r\n", parse_error::bad_version); - bad("GET / HTTP/3.45\r\n" "\r\n", parse_error::bad_version); - bad("GET / HTTP/67.89\r\n" "\r\n", parse_error::bad_version); - bad("GET / HTTP/x.0\r\n" "\r\n", parse_error::bad_version); - bad("GET / HTTP/1.x\r\n" "\r\n", parse_error::bad_version); - bad("GET / HTTP/1.0 \r\n" "\r\n", parse_error::bad_version); - bad("GET / HTTP/1_0\r\n" "\r\n", parse_error::bad_version); - bad("GET / HTTP/1.0\n" "\r\n", parse_error::bad_version); - bad("GET / HTTP/1.0\n\r" "\r\n", parse_error::bad_version); - bad("GET / HTTP/1.0\r\r\n" "\r\n", parse_error::bad_crlf); - - // write a bad request line in 2 pieces - { - error_code ec; - test::fail_counter fc(1000); - fail_parser p(fc); - p.write(buffer_cat( - buf("GET / "), buf("_TTP/1.1\r\n"), - buf("\r\n") - ), ec); - BEAST_EXPECT(ec == parse_error::bad_version); - } - } - - void testStatusLine() - { - /* - status-line = HTTP-version SP status-code SP reason-phrase CRLF - HTTP-version = "HTTP/" DIGIT "." DIGIT - status-code = 3DIGIT - reason-phrase = *( HTAB / SP / VCHAR / obs-text ) - */ - good("HTTP/0.1 200 OK\r\n" "\r\n", version{*this, 0, 1}); - good("HTTP/2.3 200 OK\r\n" "\r\n", version{*this, 2, 3}); - good("HTTP/4.5 200 OK\r\n" "\r\n", version{*this, 4, 5}); - good("HTTP/6.7 200 OK\r\n" "\r\n", version{*this, 6, 7}); - good("HTTP/8.9 200 OK\r\n" "\r\n", version{*this, 8, 9}); - good("HTTP/1.0 000 OK\r\n" "\r\n", status{*this, 0}); - good("HTTP/1.1 012 OK\r\n" "\r\n", status{*this, 12}); - good("HTTP/1.0 345 OK\r\n" "\r\n", status{*this, 345}); - good("HTTP/1.0 678 OK\r\n" "\r\n", status{*this, 678}); - good("HTTP/1.0 999 OK\r\n" "\r\n", status{*this, 999}); - good("HTTP/1.0 200 \tX\r\n" "\r\n", version{*this, 1, 0}); - good("HTTP/1.1 200 X\r\n" "\r\n", version{*this, 1, 1}); - good("HTTP/1.0 200 \r\n" "\r\n"); - good("HTTP/1.1 200 X \r\n" "\r\n"); - good("HTTP/1.1 200 X\t\r\n" "\r\n"); - good("HTTP/1.1 200 \x80\x81...\xfe\xff\r\n\r\n"); - good("HTTP/1.1 200 !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\r\n\r\n"); - - bad("\rHTTP/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("\nHTTP/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); - bad(" HTTP/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("_TTP/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("H_TP/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("HT_P/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("HTT_/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("HTTP_1.0 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("HTTP/01.2 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("HTTP/3.45 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("HTTP/67.89 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("HTTP/x.0 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("HTTP/1.x 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("HTTP/1_0 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("HTTP/1.0 200 OK\r\n" "\r\n", parse_error::bad_status); - bad("HTTP/1.0 0 OK\r\n" "\r\n", parse_error::bad_status); - bad("HTTP/1.0 12 OK\r\n" "\r\n", parse_error::bad_status); - bad("HTTP/1.0 3456 OK\r\n" "\r\n", parse_error::bad_status); - bad("HTTP/1.0 200\r\n" "\r\n", parse_error::bad_status); - bad("HTTP/1.0 200 \n" "\r\n", parse_error::bad_reason); - bad("HTTP/1.0 200 \x01\r\n" "\r\n", parse_error::bad_reason); - bad("HTTP/1.0 200 \x7f\r\n" "\r\n", parse_error::bad_reason); - bad("HTTP/1.0 200 OK\n" "\r\n", parse_error::bad_reason); - bad("HTTP/1.0 200 OK\r\r\n" "\r\n", parse_error::bad_crlf); - } - - //-------------------------------------------------------------------------- - - void testHeaders() - { - /* - header-field = field-name ":" OWS field-value OWS - field-name = token - field-value = *( field-content / obs-fold ) - field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] - field-vchar = VCHAR / obs-text - obs-fold = CRLF 1*( SP / HTAB ) - ; obsolete line folding - */ - auto const m = - [](std::string const& s) - { - return "GET / HTTP/1.1\r\n" + s + "\r\n"; - }; - good(m("f:\r\n")); - good(m("f: \r\n")); - good(m("f:\t\r\n")); - good(m("f: \t\r\n")); - good(m("f: v\r\n")); - good(m("f:\tv\r\n")); - good(m("f:\tv \r\n")); - good(m("f:\tv\t\r\n")); - good(m("f:\tv\t \r\n")); - good(m("f:\r\n \r\n")); - good(m("f:v\r\n")); - good(m("f: v\r\n u\r\n")); - good(m("!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz: v\r\n")); - good(m("f: !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x80\x81...\xfe\xff\r\n")); - - bad(m(" f: v\r\n"), parse_error::bad_field); - bad(m("\tf: v\r\n"), parse_error::bad_field); - bad(m("f : v\r\n"), parse_error::bad_field); - bad(m("f\t: v\r\n"), parse_error::bad_field); - bad(m("f: \n\r\n"), parse_error::bad_value); - bad(m("f: v\r \r\n"), parse_error::bad_crlf); - bad(m("f: \r v\r\n"), parse_error::bad_crlf); - bad("GET / HTTP/1.1\r\n\r \n", parse_error::bad_crlf); - } - - //-------------------------------------------------------------------------- - - class flags - { - suite& s_; - std::size_t value_; - - public: - flags(suite& s, std::size_t value) - : s_(s) - , value_(value) - { - } - - template - void - operator()(Parser const& p) const - { - s_.BEAST_EXPECT(p.flags() == value_); - } - }; - - class keepalive_f - { - suite& s_; - bool value_; - - public: - keepalive_f(suite& s, bool value) - : s_(s) - , value_(value) - { - } - - template - void - operator()(Parser const& p) const - { - s_.BEAST_EXPECT(p.keep_alive() == value_); - } - }; - - void testConnectionHeader() - { - auto const m = - [](std::string const& s) - { - return "GET / HTTP/1.1\r\n" + s + "\r\n"; - }; - auto const cn = - [](std::string const& s) - { - return "GET / HTTP/1.1\r\nConnection: " + s + "\r\n"; - }; - auto const keepalive = - [&](bool v) - { - return keepalive_f{*this, v}; - }; - - good(cn("close\r\n"), flags{*this, parse_flag::connection_close}); - good(cn(",close\r\n"), flags{*this, parse_flag::connection_close}); - good(cn(" close\r\n"), flags{*this, parse_flag::connection_close}); - good(cn("\tclose\r\n"), flags{*this, parse_flag::connection_close}); - good(cn("close,\r\n"), flags{*this, parse_flag::connection_close}); - good(cn("close\t\r\n"), flags{*this, parse_flag::connection_close}); - good(cn("close\r\n"), flags{*this, parse_flag::connection_close}); - good(cn(" ,\t,,close,, ,\t,,\r\n"), flags{*this, parse_flag::connection_close}); - good(cn("\r\n close\r\n"), flags{*this, parse_flag::connection_close}); - good(cn("close\r\n \r\n"), flags{*this, parse_flag::connection_close}); - good(cn("any,close\r\n"), flags{*this, parse_flag::connection_close}); - good(cn("close,any\r\n"), flags{*this, parse_flag::connection_close}); - good(cn("any\r\n ,close\r\n"), flags{*this, parse_flag::connection_close}); - good(cn("close\r\n ,any\r\n"), flags{*this, parse_flag::connection_close}); - good(cn("close,close\r\n"), flags{*this, parse_flag::connection_close}); // weird but allowed - - good(cn("keep-alive\r\n"), flags{*this, parse_flag::connection_keep_alive}); - good(cn("keep-alive \r\n"), flags{*this, parse_flag::connection_keep_alive}); - good(cn("keep-alive\t \r\n"), flags{*this, parse_flag::connection_keep_alive}); - good(cn("keep-alive\t ,x\r\n"), flags{*this, parse_flag::connection_keep_alive}); - good(cn("\r\n keep-alive \t\r\n"), flags{*this, parse_flag::connection_keep_alive}); - good(cn("keep-alive \r\n \t \r\n"), flags{*this, parse_flag::connection_keep_alive}); - good(cn("keep-alive\r\n \r\n"), flags{*this, parse_flag::connection_keep_alive}); - - good(cn("upgrade\r\n"), flags{*this, parse_flag::connection_upgrade}); - good(cn("upgrade \r\n"), flags{*this, parse_flag::connection_upgrade}); - good(cn("upgrade\t \r\n"), flags{*this, parse_flag::connection_upgrade}); - good(cn("upgrade\t ,x\r\n"), flags{*this, parse_flag::connection_upgrade}); - good(cn("\r\n upgrade \t\r\n"), flags{*this, parse_flag::connection_upgrade}); - good(cn("upgrade \r\n \t \r\n"), flags{*this, parse_flag::connection_upgrade}); - good(cn("upgrade\r\n \r\n"), flags{*this, parse_flag::connection_upgrade}); - - good(cn("close,keep-alive\r\n"), flags{*this, parse_flag::connection_close | parse_flag::connection_keep_alive}); - good(cn("upgrade,keep-alive\r\n"), flags{*this, parse_flag::connection_upgrade | parse_flag::connection_keep_alive}); - good(cn("upgrade,\r\n keep-alive\r\n"), flags{*this, parse_flag::connection_upgrade | parse_flag::connection_keep_alive}); - good(cn("close,keep-alive,upgrade\r\n"), flags{*this, parse_flag::connection_close | parse_flag::connection_keep_alive | parse_flag::connection_upgrade}); - - good("GET / HTTP/1.1\r\n\r\n", keepalive(true)); - good("GET / HTTP/1.0\r\n\r\n", keepalive(false)); - good("GET / HTTP/1.0\r\n" - "Connection: keep-alive\r\n\r\n", keepalive(true)); - good("GET / HTTP/1.1\r\n" - "Connection: close\r\n\r\n", keepalive(false)); - - good(cn("x\r\n"), flags{*this, 0}); - good(cn("x,y\r\n"), flags{*this, 0}); - good(cn("x ,y\r\n"), flags{*this, 0}); - good(cn("x\t,y\r\n"), flags{*this, 0}); - good(cn("keep\r\n"), flags{*this, 0}); - good(cn(",keep\r\n"), flags{*this, 0}); - good(cn(" keep\r\n"), flags{*this, 0}); - good(cn("\tnone\r\n"), flags{*this, 0}); - good(cn("keep,\r\n"), flags{*this, 0}); - good(cn("keep\t\r\n"), flags{*this, 0}); - good(cn("keep\r\n"), flags{*this, 0}); - good(cn(" ,\t,,keep,, ,\t,,\r\n"), flags{*this, 0}); - good(cn("\r\n keep\r\n"), flags{*this, 0}); - good(cn("keep\r\n \r\n"), flags{*this, 0}); - good(cn("closet\r\n"), flags{*this, 0}); - good(cn(",closet\r\n"), flags{*this, 0}); - good(cn(" closet\r\n"), flags{*this, 0}); - good(cn("\tcloset\r\n"), flags{*this, 0}); - good(cn("closet,\r\n"), flags{*this, 0}); - good(cn("closet\t\r\n"), flags{*this, 0}); - good(cn("closet\r\n"), flags{*this, 0}); - good(cn(" ,\t,,closet,, ,\t,,\r\n"), flags{*this, 0}); - good(cn("\r\n closet\r\n"), flags{*this, 0}); - good(cn("closet\r\n \r\n"), flags{*this, 0}); - good(cn("clog\r\n"), flags{*this, 0}); - good(cn("key\r\n"), flags{*this, 0}); - good(cn("uptown\r\n"), flags{*this, 0}); - good(cn("keeper\r\n \r\n"), flags{*this, 0}); - good(cn("keep-alively\r\n \r\n"), flags{*this, 0}); - good(cn("up\r\n \r\n"), flags{*this, 0}); - good(cn("upgrader\r\n \r\n"), flags{*this, 0}); - good(cn("none\r\n"), flags{*this, 0}); - good(cn("\r\n none\r\n"), flags{*this, 0}); - - good(m("ConnectioX: close\r\n"), flags{*this, 0}); - good(m("Condor: close\r\n"), flags{*this, 0}); - good(m("Connect: close\r\n"), flags{*this, 0}); - good(m("Connections: close\r\n"), flags{*this, 0}); - - good(m("Proxy-Connection: close\r\n"), flags{*this, parse_flag::connection_close}); - good(m("Proxy-Connection: keep-alive\r\n"), flags{*this, parse_flag::connection_keep_alive}); - good(m("Proxy-Connection: upgrade\r\n"), flags{*this, parse_flag::connection_upgrade}); - good(m("Proxy-ConnectioX: none\r\n"), flags{*this, 0}); - good(m("Proxy-Connections: 1\r\n"), flags{*this, 0}); - good(m("Proxy-Connotes: see-also\r\n"), flags{*this, 0}); - - bad(cn("["), parse_error::bad_value); - bad(cn("\"\r\n"), parse_error::bad_value); - bad(cn("close[\r\n"), parse_error::bad_value); - bad(cn("close [\r\n"), parse_error::bad_value); - bad(cn("close, upgrade [\r\n"), parse_error::bad_value); - bad(cn("upgrade[]\r\n"), parse_error::bad_value); - bad(cn("keep\r\n -alive\r\n"), parse_error::bad_value); - bad(cn("keep-alive[\r\n"), parse_error::bad_value); - bad(cn("keep-alive []\r\n"), parse_error::bad_value); - bad(cn("no[ne]\r\n"), parse_error::bad_value); - } - - void testContentLengthHeader() - { - auto const length = - [&](std::string const& s, std::uint64_t v) - { - good(body_what::skip, s, - [&](fail_parser const& p) - { - BEAST_EXPECT(p.content_length() == v); - if(v != no_content_length) - BEAST_EXPECT(p.flags() & parse_flag::contentlength); - }); - }; - auto const c = - [](std::string const& s) - { - return "GET / HTTP/1.1\r\nContent-Length: " + s + "\r\n"; - }; - auto const m = - [](std::string const& s) - { - return "GET / HTTP/1.1\r\n" + s + "\r\n"; - }; - - length(c("0\r\n"), 0); - length(c("00\r\n"), 0); - length(c("1\r\n"), 1); - length(c("01\r\n"), 1); - length(c("9\r\n"), 9); - length(c("123456789\r\n"), 123456789); - length(c("42 \r\n"), 42); - length(c("42\t\r\n"), 42); - length(c("42 \t \r\n"), 42); - length(c("42\r\n \t \r\n"), 42); - - good(m("Content-LengtX: 0\r\n"), flags{*this, 0}); - good(m("Content-Lengths: many\r\n"), flags{*this, 0}); - good(m("Content: full\r\n"), flags{*this, 0}); - - bad(c("\r\n"), parse_error::bad_content_length); - bad(c("18446744073709551616\r\n"), parse_error::bad_content_length); - bad(c("0 0\r\n"), parse_error::bad_content_length); - bad(c("0 1\r\n"), parse_error::bad_content_length); - bad(c(",\r\n"), parse_error::bad_content_length); - bad(c("0,\r\n"), parse_error::bad_content_length); - bad(m( - "Content-Length: 0\r\nContent-Length: 0\r\n"), parse_error::bad_content_length); - } - - void testTransferEncodingHeader() - { - auto const m = - [](std::string const& s) - { - return "GET / HTTP/1.1\r\n" + s + "\r\n"; - }; - auto const ce = - [](std::string const& s) - { - return "GET / HTTP/1.1\r\nTransfer-Encoding: " + s + "\r\n0\r\n\r\n"; - }; - auto const te = - [](std::string const& s) - { - return "GET / HTTP/1.1\r\nTransfer-Encoding: " + s + "\r\n"; - }; - good(ce("chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("chunked \r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("chunked\t\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("chunked \t\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce(" chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("\tchunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("chunked,\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("chunked ,\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("chunked, \r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce(",chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce(", chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce(" ,chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("chunked\r\n \r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("\r\n chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce(",\r\n chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("\r\n ,chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce(",\r\n chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("gzip, chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("gzip, chunked \r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("gzip, \r\n chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - - // Technically invalid but beyond the parser's scope to detect - good(ce("custom;key=\",chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - - good(te("gzip\r\n"), flags{*this, 0}); - good(te("chunked, gzip\r\n"), flags{*this, 0}); - good(te("chunked\r\n , gzip\r\n"), flags{*this, 0}); - good(te("chunked,\r\n gzip\r\n"), flags{*this, 0}); - good(te("chunked,\r\n ,gzip\r\n"), flags{*this, 0}); - good(te("bigchunked\r\n"), flags{*this, 0}); - good(te("chunk\r\n ked\r\n"), flags{*this, 0}); - good(te("bar\r\n ley chunked\r\n"), flags{*this, 0}); - good(te("barley\r\n chunked\r\n"), flags{*this, 0}); - - good(m("Transfer-EncodinX: none\r\n"), flags{*this, 0}); - good(m("Transfer-Encodings: 2\r\n"), flags{*this, 0}); - good(m("Transfer-Encoded: false\r\n"), flags{*this, 0}); - - bad(body_what::skip, - "HTTP/1.1 200 OK\r\n" - "Content-Length: 1\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n", parse_error::illegal_content_length); - } - - void testUpgradeHeader() - { - auto const m = - [](std::string const& s) - { - return "GET / HTTP/1.1\r\n" + s + "\r\n"; - }; - good(m("Upgrade:\r\n"), flags{*this, parse_flag::upgrade}); - good(m("Upgrade: \r\n"), flags{*this, parse_flag::upgrade}); - good(m("Upgrade: yes\r\n"), flags{*this, parse_flag::upgrade}); - - good(m("Up: yes\r\n"), flags{*this, 0}); - good(m("UpgradX: none\r\n"), flags{*this, 0}); - good(m("Upgrades: 2\r\n"), flags{*this, 0}); - good(m("Upsample: 4x\r\n"), flags{*this, 0}); - - good( - "GET / HTTP/1.1\r\n" - "Connection: upgrade\r\n" - "Upgrade: WebSocket\r\n" - "\r\n", - [&](fail_parser const& p) - { - BEAST_EXPECT(p.upgrade()); - }); - } - - //-------------------------------------------------------------------------- - - class body_f - { - suite& s_; - std::string const& body_; - - public: - body_f(body_f&&) = default; - - body_f(suite& s, std::string const& v) - : s_(s) - , body_(v) - { - } - - template - void - operator()(Parser const& p) const - { - s_.BEAST_EXPECT(p.body == body_); - } - }; - - template - static - boost::asio::const_buffers_1 - buf(char const (&s)[N]) - { - return { s, N-1 }; - } - - void testBody() - { - using boost::asio::buffer; - auto const body = - [&](std::string const& s) - { - return body_f{*this, s}; - }; - good( - "GET / HTTP/1.1\r\n" - "Content-Length: 1\r\n" - "\r\n" - "1", - body("1")); - - good( - "HTTP/1.0 200 OK\r\n" - "\r\n" - "hello", - body("hello")); - - // on_body returns 2, meaning upgrade - { - error_code ec; - test::fail_counter fc(1000); - fail_parser p{fc}; - p.on_body_rv(body_what::upgrade); - p.write(buf( - "GET / HTTP/1.1\r\n" - "Content-Length: 1\r\n" - "\r\n" - ), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(p.complete()); - } - - // write the body in 3 pieces - { - error_code ec; - test::fail_counter fc(1000); - fail_parser p(fc); - p.write(buffer_cat( - buf("GET / HTTP/1.1\r\n" - "Content-Length: 10\r\n" - "\r\n"), - buf("12"), - buf("345"), - buf("67890")), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(p.complete()); - BEAST_EXPECT(! p.needs_eof()); - p.write_eof(ec); - BEAST_EXPECT(! ec); - p.write_eof(ec); - BEAST_EXPECT(! ec); - p.write(buf("GET / HTTP/1.1\r\n\r\n"), ec); - BEAST_EXPECT(ec == parse_error::connection_closed); - } - - // request without Content-Length or - // Transfer-Encoding: chunked has no body. - { - error_code ec; - test::fail_counter fc(1000); - fail_parser p(fc); - p.write(buf( - "GET / HTTP/1.0\r\n" - "\r\n" - ), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(! p.needs_eof()); - BEAST_EXPECT(p.complete()); - } - { - error_code ec; - test::fail_counter fc(1000); - fail_parser p(fc); - p.write(buf( - "GET / HTTP/1.1\r\n" - "\r\n" - ), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(! p.needs_eof()); - BEAST_EXPECT(p.complete()); - } - - // response without Content-Length or - // Transfer-Encoding: chunked requires eof. - { - error_code ec; - test::fail_counter fc(1000); - fail_parser p(fc); - p.write(buf( - "HTTP/1.0 200 OK\r\n" - "\r\n" - ), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(! p.complete()); - BEAST_EXPECT(p.needs_eof()); - p.write(buf( - "hello" - ), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(! p.complete()); - BEAST_EXPECT(p.needs_eof()); - p.write_eof(ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(p.complete()); - p.write(buf("GET / HTTP/1.1\r\n\r\n"), ec); - BEAST_EXPECT(ec == parse_error::connection_closed); - } - - // 304 "Not Modified" response does not require eof - { - error_code ec; - test::fail_counter fc(1000); - fail_parser p(fc); - p.write(buf( - "HTTP/1.0 304 Not Modified\r\n" - "\r\n" - ), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(! p.needs_eof()); - BEAST_EXPECT(p.complete()); - } - - // Chunked response does not require eof - { - error_code ec; - test::fail_counter fc(1000); - fail_parser p(fc); - p.write(buf( - "HTTP/1.1 200 OK\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n" - ), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(! p.needs_eof()); - BEAST_EXPECT(! p.complete()); - p.write(buf( - "0\r\n\r\n" - ), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(! p.needs_eof()); - BEAST_EXPECT(p.complete()); - } - - // restart: 1.0 assumes Connection: close - { - error_code ec; - test::fail_counter fc(1000); - fail_parser p(fc); - p.write(buf( - "GET / HTTP/1.0\r\n" - "\r\n" - ), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(p.complete()); - p.write(buf( - "GET / HTTP/1.0\r\n" - "\r\n" - ), ec); - BEAST_EXPECT(ec == parse_error::connection_closed); - } - - // restart: 1.1 assumes Connection: keep-alive - { - error_code ec; - test::fail_counter fc(1000); - fail_parser p(fc); - p.write(buf( - "GET / HTTP/1.1\r\n" - "\r\n" - ), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(p.complete()); - p.write(buf( - "GET / HTTP/1.0\r\n" - "\r\n" - ), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(p.complete()); - } - - bad(body_what::normal, - "GET / HTTP/1.1\r\n" - "Content-Length: 1\r\n" - "\r\n", - parse_error::short_read); - } - - void testChunkedBody() - { - auto const body = - [&](std::string const& s) - { - return body_f{*this, s}; - }; - auto const ce = - [](std::string const& s) - { - return - "GET / HTTP/1.1\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n" + s; - }; - - /* - chunked-body = *chunk - last-chunk - trailer-part - CRLF - chunk = chunk-size [ chunk-ext ] CRLF - chunk-data CRLF - chunk-size = 1*HEXDIG - last-chunk = 1*("0") [ chunk-ext ] CRLF - chunk-data = 1*OCTET ; a sequence of chunk-size octets - chunk-ext = *( ";" chunk-ext-name [ "=" chunk-ext-val ] ) - chunk-ext-name = token - chunk-ext-val = token / quoted-string - trailer-part = *( header-field CRLF ) - */ - good(ce( - "1;xy\r\n*\r\n" "0\r\n\r\n" - ), body("*")); - - good(ce( - "1;x\r\n*\r\n" "0\r\n\r\n" - ), body("*")); - - good(ce( - "1;x;y\r\n*\r\n" "0\r\n\r\n" - ), body("*")); - - good(ce( - "1;i;j=2;k=\"3\"\r\n*\r\n" "0\r\n\r\n" - ), body("*")); - - good(ce( - "1\r\n" "a\r\n" "0\r\n" "\r\n" - ), body("a")); - - good(ce( - "2\r\n" "ab\r\n" "0\r\n" "\r\n" - ), body("ab")); - - good(ce( - "2\r\n" "ab\r\n" "1\r\n" "c\r\n" "0\r\n" "\r\n" - ), body("abc")); - - good(ce( - "10\r\n" "1234567890123456\r\n" "0\r\n" "\r\n" - ), body("1234567890123456")); - - bad(ce("ffffffffffffffff0\r\n0\r\n\r\n"), parse_error::bad_content_length); - bad(ce("g\r\n0\r\n\r\n"), parse_error::invalid_chunk_size); - bad(ce("0g\r\n0\r\n\r\n"), parse_error::invalid_chunk_size); - bad(ce("0\r_\n"), parse_error::bad_crlf); - bad(ce("1\r\n*_\r\n"), parse_error::bad_crlf); - bad(ce("1\r\n*\r_\n"), parse_error::bad_crlf); - bad(ce("1;,x\r\n*\r\n" "0\r\n\r\n"), parse_error::invalid_ext_name); - bad(ce("1;x,\r\n*\r\n" "0\r\n\r\n"), parse_error::invalid_ext_name); - } - - void testLimits() - { - std::size_t n; - static std::size_t constexpr Limit = 100; - { - for(n = 1; n < Limit; ++n) - { - test::fail_counter fc(1000); - fail_parser p(fc); - p.set_option(header_max_size{n}); - error_code ec; - p.write(buf( - "GET / HTTP/1.1\r\n" - "User-Agent: beast\r\n" - "\r\n" - ), ec); - if(! ec) - break; - BEAST_EXPECT(ec == parse_error::header_too_big); - } - BEAST_EXPECT(n < Limit); - } - { - for(n = 1; n < Limit; ++n) - { - test::fail_counter fc(1000); - fail_parser p(fc); - p.set_option(header_max_size{n}); - error_code ec; - p.write(buf( - "HTTP/1.1 200 OK\r\n" - "Server: beast\r\n" - "Content-Length: 4\r\n" - "\r\n" - "****" - ), ec); - if(! ec) - break; - BEAST_EXPECT(ec == parse_error::header_too_big); - } - BEAST_EXPECT(n < Limit); - } - { - test::fail_counter fc(1000); - fail_parser p(fc); - p.set_option(body_max_size{2}); - error_code ec; - p.write(buf( - "HTTP/1.1 200 OK\r\n" - "Server: beast\r\n" - "Content-Length: 4\r\n" - "\r\n" - "****" - ), ec); - BEAST_EXPECT(ec == parse_error::body_too_big); - } - } - - void run() override - { - testCallbacks(); - testRequestLine(); - testStatusLine(); - testHeaders(); - testConnectionHeader(); - testContentLengthHeader(); - testTransferEncodingHeader(); - testUpgradeHeader(); - testBody(); - testChunkedBody(); - testLimits(); - } -}; - -BEAST_DEFINE_TESTSUITE(basic_parser_v1,http,beast); - -} // http -} // beast diff --git a/src/beast/test/core/placeholders.cpp b/src/beast/test/http/buffer_body.cpp similarity index 87% rename from src/beast/test/core/placeholders.cpp rename to src/beast/test/http/buffer_body.cpp index 1f4955d5be..2526a3c03f 100644 --- a/src/beast/test/core/placeholders.cpp +++ b/src/beast/test/http/buffer_body.cpp @@ -6,4 +6,4 @@ // // Test that header file is self-contained. -#include +#include diff --git a/src/beast/test/http/chunk_encode.cpp b/src/beast/test/http/chunk_encode.cpp deleted file mode 100644 index 9bbe68f36d..0000000000 --- a/src/beast/test/http/chunk_encode.cpp +++ /dev/null @@ -1,136 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -// Test that header file is self-contained. -#include - -#include -#include - -namespace beast { -namespace http { - -class chunk_encode_test : public beast::unit_test::suite -{ -public: - struct final_chunk - { - std::string s; - - final_chunk() = default; - - explicit - final_chunk(std::string s_) - : s(std::move(s_)) - { - } - }; - - static - void - encode1(std::string& s, final_chunk const& fc) - { - using boost::asio::buffer; - if(! fc.s.empty()) - s.append(to_string(chunk_encode( - false, buffer(fc.s.data(), fc.s.size())))); - s.append(to_string(chunk_encode_final())); - } - - static - void - encode1(std::string& s, std::string const& piece) - { - using boost::asio::buffer; - s.append(to_string(chunk_encode( - false, buffer(piece.data(), piece.size())))); - } - - static - inline - void - encode(std::string&) - { - } - - template - static - void - encode(std::string& s, Arg const& arg, Args const&... args) - { - encode1(s, arg); - encode(s, args...); - } - - template - void - check(std::string const& answer, Args const&... args) - { - std::string s; - encode(s, args...); - BEAST_EXPECT(s == answer); - } - - void run() override - { - check( - "0\r\n\r\n" - "0\r\n\r\n", - "", final_chunk{}); - - check( - "1\r\n" - "*\r\n" - "0\r\n\r\n", - final_chunk("*")); - - check( - "2\r\n" - "**\r\n" - "0\r\n\r\n", - final_chunk("**")); - - check( - "1\r\n" - "*\r\n" - "1\r\n" - "*\r\n" - "0\r\n\r\n", - "*", final_chunk("*")); - - check( - "5\r\n" - "*****\r\n" - "7\r\n" - "*******\r\n" - "0\r\n\r\n", - "*****", final_chunk("*******")); - - check( - "1\r\n" - "*\r\n" - "1\r\n" - "*\r\n" - "0\r\n\r\n", - "*", "*", final_chunk{}); - - check( - "4\r\n" - "****\r\n" - "0\r\n\r\n", - "****", final_chunk{}); - - BEAST_EXPECT(to_string(chunk_encode(true, - boost::asio::buffer("****", 4))) == - "4\r\n****\r\n0\r\n\r\n"); - } -}; - -BEAST_DEFINE_TESTSUITE(chunk_encode,http,beast); - -} // http -} // beast diff --git a/src/beast/test/http/doc_examples.cpp b/src/beast/test/http/doc_examples.cpp new file mode 100644 index 0000000000..7017e176d9 --- /dev/null +++ b/src/beast/test/http/doc_examples.cpp @@ -0,0 +1,303 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include "example/doc/http_examples.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +class doc_examples_test + : public beast::unit_test::suite + , public beast::test::enable_yield_to +{ +public: + // two threads, for some examples using a pipe + doc_examples_test() + : enable_yield_to(2) + { + } + + template + bool + equal_body(string_view sv, string_view body) + { + test::string_istream si{ + get_io_service(), sv.to_string()}; + message m; + multi_buffer b; + try + { + read(si, b, m); + return m.body == body; + } + catch(std::exception const& e) + { + log << "equal_body: " << e.what() << std::endl; + return false; + } + } + + void + doExpect100Continue() + { + test::pipe p{ios_}; + yield_to( + [&](yield_context) + { + error_code ec; + flat_buffer buffer; + receive_expect_100_continue( + p.server, buffer, ec); + BEAST_EXPECTS(! ec, ec.message()); + }, + [&](yield_context) + { + flat_buffer buffer; + request req; + req.version = 11; + req.method_string("POST"); + req.target("/"); + req.insert(field::user_agent, "test"); + req.body = "Hello, world!"; + req.prepare_payload(); + + error_code ec; + send_expect_100_continue( + p.client, buffer, req, ec); + BEAST_EXPECTS(! ec, ec.message()); + }); + } + + void + doCgiResponse() + { + std::string const s = "Hello, world!"; + test::pipe child{ios_}; + child.server.read_size(3); + ostream(child.server.buffer) << s; + child.client.close(); + test::pipe p{ios_}; + error_code ec; + send_cgi_response(child.server, p.client, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(equal_body(p.server.str(), s)); + } + + void + doRelay() + { + request req; + req.version = 11; + req.method_string("POST"); + req.target("/"); + req.insert(field::user_agent, "test"); + req.body = "Hello, world!"; + req.prepare_payload(); + + test::pipe downstream{ios_}; + downstream.server.read_size(3); + test::pipe upstream{ios_}; + upstream.client.write_size(3); + + error_code ec; + write(downstream.client, req); + BEAST_EXPECTS(! ec, ec.message()); + downstream.client.close(); + + flat_buffer buffer; + relay(upstream.client, downstream.server, buffer, ec, + [&](header& h, error_code& ev) + { + ev = {}; + h.erase("Content-Length"); + h.set("Transfer-Encoding", "chunked"); + }); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(equal_body( + upstream.server.str(), req.body)); + } + + void + doReadStdStream() + { + std::string const s = + "HTTP/1.0 200 OK\r\n" + "User-Agent: test\r\n" + "\r\n" + "Hello, world!"; + std::istringstream is(s); + error_code ec; + flat_buffer buffer; + response res; + read_istream(is, buffer, res, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(boost::lexical_cast< + std::string>(res) == s); + } + + void + doWriteStdStream() + { + std::ostringstream os; + request req; + req.version = 11; + req.method(verb::get); + req.target("/"); + req.insert(field::user_agent, "test"); + error_code ec; + write_ostream(os, req, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(boost::lexical_cast< + std::string>(req) == os.str()); + } + + void + doCustomParser() + { + { + string_view s{ + "POST / HTTP/1.1\r\n" + "User-Agent: test\r\n" + "Content-Length: 13\r\n" + "\r\n" + "Hello, world!" + }; + error_code ec; + custom_parser p; + p.put(boost::asio::buffer( + s.data(), s.size()), ec); + BEAST_EXPECTS(! ec, ec.message()); + } + { + string_view s{ + "HTTP/1.1 200 OK\r\n" + "Server: test\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "d\r\n" + "Hello, world!" + "\r\n" + "0\r\n\r\n" + }; + error_code ec; + custom_parser p; + p.put(boost::asio::buffer( + s.data(), s.size()), ec); + BEAST_EXPECTS(! ec, ec.message()); + } + } + + void + doHEAD() + { + test::pipe p{ios_}; + yield_to( + [&](yield_context) + { + error_code ec; + flat_buffer buffer; + do_server_head(p.server, buffer, ec); + BEAST_EXPECTS(! ec, ec.message()); + }, + [&](yield_context) + { + error_code ec; + flat_buffer buffer; + auto res = do_head_request(p.client, buffer, "/", ec); + BEAST_EXPECTS(! ec, ec.message()); + }); + } + + struct handler + { + std::string body; + + template + void + operator()(request&&) + { + } + + void + operator()(request&& req) + { + body = req.body; + } + }; + + void + doDeferredBody() + { + test::pipe p{ios_}; + ostream(p.server.buffer) << + "POST / HTTP/1.1\r\n" + "User-Agent: test\r\n" + "Content-Type: multipart/form-data\r\n" + "Content-Length: 13\r\n" + "\r\n" + "Hello, world!"; + + handler h; + flat_buffer buffer; + do_form_request(p.server, buffer, h); + BEAST_EXPECT(h.body == "Hello, world!"); + } + + //-------------------------------------------------------------------------- + + void + doIncrementalRead() + { + test::pipe c{ios_}; + std::string s(2048, '*'); + ostream(c.server.buffer) << + "HTTP/1.1 200 OK\r\n" + "Content-Length: 2048\r\n" + "Server: test\r\n" + "\r\n" << + s; + error_code ec; + flat_buffer b; + std::stringstream ss; + read_and_print_body(ss, c.server, b, ec); + if(BEAST_EXPECTS(! ec, ec.message())) + BEAST_EXPECT(ss.str() == s); + } + + //-------------------------------------------------------------------------- + + void + run() + { + doExpect100Continue(); + doCgiResponse(); + doRelay(); + doReadStdStream(); + doWriteStdStream(); + doCustomParser(); + doHEAD(); + doDeferredBody(); + doIncrementalRead(); + } +}; + +BEAST_DEFINE_TESTSUITE(doc_examples,http,beast); + +} // http +} // beast diff --git a/src/beast/test/http/doc_snippets.cpp b/src/beast/test/http/doc_snippets.cpp new file mode 100644 index 0000000000..1fdf10de90 --- /dev/null +++ b/src/beast/test/http/doc_snippets.cpp @@ -0,0 +1,328 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include +#include +#include +#include +#include + +using namespace beast; + +//[http_snippet_1 + +#include +using namespace beast::http; + +//] + +namespace doc_http_snippets { + +void fxx() { + + boost::asio::io_service ios; + boost::asio::io_service::work work{ios}; + std::thread t{[&](){ ios.run(); }}; + boost::asio::ip::tcp::socket sock{ios}; + +{ +//[http_snippet_2 + + request req; + req.version = 11; // HTTP/1.1 + req.method(verb::get); + req.target("/index.htm"); + req.set(field::accept, "text/html"); + req.set(field::user_agent, "Beast"); + +//] +} + +{ +//[http_snippet_3 + + response res; + res.version = 11; // HTTP/1.1 + res.result(status::ok); + res.set(field::server, "Beast"); + res.body = "Hello, world!"; + res.prepare_payload(); + +//] +} + +{ +//[http_snippet_4 + + flat_buffer buffer; // (The parser is optimized for flat buffers) + request req; + read(sock, buffer, req); + +//] +} + +{ +//[http_snippet_5 + + flat_buffer buffer; + response res; + async_read(sock, buffer, res, + [&](error_code ec) + { + std::cerr << ec.message() << std::endl; + }); + +//] +} + +{ +//[http_snippet_6 + + // This buffer's max size is too small for much of anything + flat_buffer buffer{10}; + + // Try to read a request + error_code ec; + request req; + read(sock, buffer, req, ec); + if(ec == error::buffer_overflow) + std::cerr << "Buffer limit exceeded!" << std::endl; + +//] +} + +{ +//[http_snippet_7 + + response res; + res.version = 11; + res.result(status::ok); + res.set(field::server, "Beast"); + res.body = "Hello, world!"; + + error_code ec; + write(sock, res, ec); + if(ec == error::end_of_stream) + sock.close(); +//] + +//[http_snippet_8 + async_write(sock, res, + [&](error_code) + { + if(ec) + std::cerr << ec.message() << std::endl; + }); +//] +} + +{ +//[http_snippet_10 + + response res; + + response_serializer sr{res}; + +//] +} + +} // fxx() + +//[http_snippet_12 + +/** Send a message to a stream synchronously. + + @param stream The stream to write to. This type must support + the @b SyncWriteStream concept. + + @param m The message to send. The Body type must support + the @b BodyReader concept. +*/ +template< + class SyncWriteStream, + bool isRequest, class Body, class Fields> +void +send( + SyncWriteStream& stream, + message const& m) +{ + // Check the template types + static_assert(is_sync_write_stream::value, + "SyncWriteStream requirements not met"); + static_assert(is_body_reader::value, + "BodyReader requirements not met"); + + // Create the instance of serializer for the message + serializer sr{m}; + + // Loop until the serializer is finished + do + { + // This call guarantees it will make some + // forward progress, or otherwise return an error. + write_some(stream, sr); + } + while(! sr.is_done()); +} + +//] + +//[http_snippet_13 + +template +void +print_response(SyncReadStream& stream) +{ + static_assert(is_sync_read_stream::value, + "SyncReadStream requirements not met"); + + // Declare a parser for an HTTP response + response_parser parser; + + // Read the entire message + read(stream, parser); + + // Now print the message + std::cout << parser.get() << std::endl; +} + +//] + +#ifdef BOOST_MSVC +//[http_snippet_14 + +template +void +print_cxx14(message const& m) +{ + error_code ec; + serializer sr{m}; + do + { + sr.next(ec, + [&sr](error_code& ec, auto const& buffer) + { + ec.assign(0, ec.category()); + std::cout << buffers(buffer); + sr.consume(boost::asio::buffer_size(buffer)); + }); + } + while(! ec && ! sr.is_done()); + if(! ec) + std::cout << std::endl; + else + std::cerr << ec.message() << std::endl; +} + +//] +#endif + +//[http_snippet_15 + +template +struct lambda +{ + Serializer& sr; + + lambda(Serializer& sr_) : sr(sr_) {} + + template + void operator()(error_code& ec, ConstBufferSequence const& buffer) const + { + ec.assign(0, ec.category()); + std::cout << buffers(buffer); + sr.consume(boost::asio::buffer_size(buffer)); + } +}; + +template +void +print(message const& m) +{ + error_code ec; + serializer sr{m}; + do + { + sr.next(ec, lambda{sr}); + } + while(! ec && ! sr.is_done()); + if(! ec) + std::cout << std::endl; + else + std::cerr << ec.message() << std::endl; +} + +//] + +#if BOOST_MSVC +//[http_snippet_16 + +template +void +split_print_cxx14(message const& m) +{ + error_code ec; + serializer sr{m}; + sr.split(true); + std::cout << "Header:" << std::endl; + do + { + sr.next(ec, + [&sr](error_code& ec, auto const& buffer) + { + ec.assign(0, ec.category()); + std::cout << buffers(buffer); + sr.consume(boost::asio::buffer_size(buffer)); + }); + } + while(! sr.is_header_done()); + if(! ec && ! sr.is_done()) + { + std::cout << "Body:" << std::endl; + do + { + sr.next(ec, + [&sr](error_code& ec, auto const& buffer) + { + ec.assign(0, ec.category()); + std::cout << buffers(buffer); + sr.consume(boost::asio::buffer_size(buffer)); + }); + } + while(! ec && ! sr.is_done()); + } + if(ec) + std::cerr << ec.message() << std::endl; +} + +//] +#endif + +//[http_snippet_17 + +struct decorator +{ + std::string s; + + template + string_view + operator()(ConstBufferSequence const& buffers) + { + s = ";x=" + std::to_string(boost::asio::buffer_size(buffers)); + return s; + } + + string_view + operator()(boost::asio::null_buffers) + { + return "Result: OK\r\n"; + } +}; + +//] + +} // doc_http_snippets diff --git a/src/beast/test/http/streambuf_body.cpp b/src/beast/test/http/dynamic_body.cpp similarity index 60% rename from src/beast/test/http/streambuf_body.cpp rename to src/beast/test/http/dynamic_body.cpp index 2b5ed5f7a7..37e3fb5d92 100644 --- a/src/beast/test/http/streambuf_body.cpp +++ b/src/beast/test/http/dynamic_body.cpp @@ -6,11 +6,11 @@ // // Test that header file is self-contained. -#include +#include -#include +#include #include -#include +#include #include #include #include @@ -20,12 +20,13 @@ namespace beast { namespace http { -class streambuf_body_test : public beast::unit_test::suite +class dynamic_body_test : public beast::unit_test::suite { boost::asio::io_service ios_; public: - void run() override + void + run() override { std::string const s = "HTTP/1.1 200 OK\r\n" @@ -34,15 +35,17 @@ public: "\r\n" "xyz"; test::string_istream ss(ios_, s); - parser_v1 p; - streambuf sb; - parse(ss, sb, p); - BEAST_EXPECT(to_string(p.get().body.data()) == "xyz"); - BEAST_EXPECT(boost::lexical_cast(p.get()) == s); + response_parser p; + multi_buffer b; + read(ss, b, p); + auto const& m = p.get(); + BEAST_EXPECT(boost::lexical_cast( + buffers(m.body.data())) == "xyz"); + BEAST_EXPECT(boost::lexical_cast(m) == s); } }; -BEAST_DEFINE_TESTSUITE(streambuf_body,http,beast); +BEAST_DEFINE_TESTSUITE(dynamic_body,http,beast); } // http } // beast diff --git a/src/beast/test/http/empty_body.cpp b/src/beast/test/http/empty_body.cpp index fa190ed38e..a1fdfd5ec6 100644 --- a/src/beast/test/http/empty_body.cpp +++ b/src/beast/test/http/empty_body.cpp @@ -7,3 +7,13 @@ // Test that header file is self-contained. #include + +namespace beast { +namespace http { + +BOOST_STATIC_ASSERT(is_body::value); +BOOST_STATIC_ASSERT(is_body_reader::value); +BOOST_STATIC_ASSERT(is_body_writer::value); + +} // http +} // beast diff --git a/src/beast/test/http/error.cpp b/src/beast/test/http/error.cpp new file mode 100644 index 0000000000..52d986bb90 --- /dev/null +++ b/src/beast/test/http/error.cpp @@ -0,0 +1,66 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include +#include + +namespace beast { +namespace http { + +class error_test : public unit_test::suite +{ +public: + void + check(char const* name, error ev) + { + auto const ec = make_error_code(ev); + BEAST_EXPECT(std::string(ec.category().name()) == name); + BEAST_EXPECT(! ec.message().empty()); + BEAST_EXPECT(std::addressof(ec.category()) == + std::addressof(detail::get_http_error_category())); + BEAST_EXPECT(detail::get_http_error_category().equivalent( + static_cast::type>(ev), + ec.category().default_error_condition( + static_cast::type>(ev)))); + BEAST_EXPECT(detail::get_http_error_category().equivalent( + ec, static_cast::type>(ev))); + } + + void + run() override + { + check("beast.http", error::end_of_stream); + check("beast.http", error::partial_message); + check("beast.http", error::need_more); + check("beast.http", error::unexpected_body); + check("beast.http", error::need_buffer); + check("beast.http", error::buffer_overflow); + check("beast.http", error::body_limit); + check("beast.http", error::bad_alloc); + + check("beast.http", error::bad_line_ending); + check("beast.http", error::bad_method); + check("beast.http", error::bad_target); + check("beast.http", error::bad_version); + check("beast.http", error::bad_status); + check("beast.http", error::bad_reason); + check("beast.http", error::bad_field); + check("beast.http", error::bad_value); + check("beast.http", error::bad_content_length); + check("beast.http", error::bad_transfer_encoding); + check("beast.http", error::bad_chunk); + check("beast.http", error::bad_obs_fold); + } +}; + +BEAST_DEFINE_TESTSUITE(error,http,beast); + +} // http +} // beast diff --git a/src/beast/test/http/fail_parser.hpp b/src/beast/test/http/fail_parser.hpp deleted file mode 100644 index 656516332d..0000000000 --- a/src/beast/test/http/fail_parser.hpp +++ /dev/null @@ -1,120 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_HTTP_TEST_FAIL_PARSER_HPP -#define BEAST_HTTP_TEST_FAIL_PARSER_HPP - -#include -#include - -namespace beast { -namespace http { - -template -class fail_parser - : public basic_parser_v1> -{ - test::fail_counter& fc_; - std::uint64_t content_length_ = no_content_length; - body_what body_rv_ = body_what::normal; - -public: - std::string body; - - template - explicit - fail_parser(test::fail_counter& fc, Args&&... args) - : fc_(fc) - { - } - - void - on_body_rv(body_what rv) - { - body_rv_ = rv; - } - - // valid on successful parse - std::uint64_t - content_length() const - { - return content_length_; - } - - void on_start(error_code& ec) - { - fc_.fail(ec); - } - - void on_method(boost::string_ref const&, error_code& ec) - { - fc_.fail(ec); - } - - void on_uri(boost::string_ref const&, error_code& ec) - { - fc_.fail(ec); - } - - void on_reason(boost::string_ref const&, error_code& ec) - { - fc_.fail(ec); - } - - void on_request(error_code& ec) - { - fc_.fail(ec); - } - - void on_response(error_code& ec) - { - fc_.fail(ec); - } - - void on_field(boost::string_ref const&, error_code& ec) - { - fc_.fail(ec); - } - - void on_value(boost::string_ref const&, error_code& ec) - { - fc_.fail(ec); - } - - void - on_header(std::uint64_t content_length, error_code& ec) - { - if(fc_.fail(ec)) - return; - } - - body_what - on_body_what(std::uint64_t content_length, error_code& ec) - { - if(fc_.fail(ec)) - return body_what::normal; - content_length_ = content_length; - return body_rv_; - } - - void on_body(boost::string_ref const& s, error_code& ec) - { - if(fc_.fail(ec)) - return; - body.append(s.data(), s.size()); - } - - void on_complete(error_code& ec) - { - fc_.fail(ec); - } -}; - -} // http -} // beast - -#endif diff --git a/src/beast/test/http/field.cpp b/src/beast/test/http/field.cpp new file mode 100644 index 0000000000..c44f48cc89 --- /dev/null +++ b/src/beast/test/http/field.cpp @@ -0,0 +1,405 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include + +namespace beast { +namespace http { + +class field_test : public beast::unit_test::suite +{ +public: + void + testField() + { + auto const match = + [&](field f, string_view s) + { + BEAST_EXPECT(iequals(to_string(f), s)); + BEAST_EXPECT(string_to_field(s) == f); + }; + + match(field::accept, "accept"); + match(field::accept, "aCcept"); + match(field::accept, "ACCEPT"); + + + match(field::a_im, "A-IM"); + match(field::accept, "Accept"); + match(field::accept_additions, "Accept-Additions"); + match(field::accept_charset, "Accept-Charset"); + match(field::accept_datetime, "Accept-Datetime"); + match(field::accept_encoding, "Accept-Encoding"); + match(field::accept_features, "Accept-Features"); + match(field::accept_language, "Accept-Language"); + match(field::accept_patch, "Accept-Patch"); + match(field::accept_post, "Accept-Post"); + match(field::accept_ranges, "Accept-Ranges"); + match(field::access_control, "Access-Control"); + match(field::access_control_allow_credentials, "Access-Control-Allow-Credentials"); + match(field::access_control_allow_headers, "Access-Control-Allow-Headers"); + match(field::access_control_allow_methods, "Access-Control-Allow-Methods"); + match(field::access_control_allow_origin, "Access-Control-Allow-Origin"); + match(field::access_control_max_age, "Access-Control-Max-Age"); + match(field::access_control_request_headers, "Access-Control-Request-Headers"); + match(field::access_control_request_method, "Access-Control-Request-Method"); + match(field::age, "Age"); + match(field::allow, "Allow"); + match(field::alpn, "ALPN"); + match(field::also_control, "Also-Control"); + match(field::alt_svc, "Alt-Svc"); + match(field::alt_used, "Alt-Used"); + match(field::alternate_recipient, "Alternate-Recipient"); + match(field::alternates, "Alternates"); + match(field::apparently_to, "Apparently-To"); + match(field::apply_to_redirect_ref, "Apply-To-Redirect-Ref"); + match(field::approved, "Approved"); + match(field::archive, "Archive"); + match(field::archived_at, "Archived-At"); + match(field::article_names, "Article-Names"); + match(field::article_updates, "Article-Updates"); + match(field::authentication_control, "Authentication-Control"); + match(field::authentication_info, "Authentication-Info"); + match(field::authentication_results, "Authentication-Results"); + match(field::authorization, "Authorization"); + match(field::auto_submitted, "Auto-Submitted"); + match(field::autoforwarded, "Autoforwarded"); + match(field::autosubmitted, "Autosubmitted"); + match(field::base, "Base"); + match(field::bcc, "Bcc"); + match(field::body, "Body"); + match(field::c_ext, "C-Ext"); + match(field::c_man, "C-Man"); + match(field::c_opt, "C-Opt"); + match(field::c_pep, "C-PEP"); + match(field::c_pep_info, "C-PEP-Info"); + match(field::cache_control, "Cache-Control"); + match(field::caldav_timezones, "CalDAV-Timezones"); + match(field::cancel_key, "Cancel-Key"); + match(field::cancel_lock, "Cancel-Lock"); + match(field::cc, "Cc"); + match(field::close, "Close"); + match(field::comments, "Comments"); + match(field::compliance, "Compliance"); + match(field::connection, "Connection"); + match(field::content_alternative, "Content-Alternative"); + match(field::content_base, "Content-Base"); + match(field::content_description, "Content-Description"); + match(field::content_disposition, "Content-Disposition"); + match(field::content_duration, "Content-Duration"); + match(field::content_encoding, "Content-Encoding"); + match(field::content_features, "Content-features"); + match(field::content_id, "Content-ID"); + match(field::content_identifier, "Content-Identifier"); + match(field::content_language, "Content-Language"); + match(field::content_length, "Content-Length"); + match(field::content_location, "Content-Location"); + match(field::content_md5, "Content-MD5"); + match(field::content_range, "Content-Range"); + match(field::content_return, "Content-Return"); + match(field::content_script_type, "Content-Script-Type"); + match(field::content_style_type, "Content-Style-Type"); + match(field::content_transfer_encoding, "Content-Transfer-Encoding"); + match(field::content_type, "Content-Type"); + match(field::content_version, "Content-Version"); + match(field::control, "Control"); + match(field::conversion, "Conversion"); + match(field::conversion_with_loss, "Conversion-With-Loss"); + match(field::cookie, "Cookie"); + match(field::cookie2, "Cookie2"); + match(field::cost, "Cost"); + match(field::dasl, "DASL"); + match(field::date, "Date"); + match(field::date_received, "Date-Received"); + match(field::dav, "DAV"); + match(field::default_style, "Default-Style"); + match(field::deferred_delivery, "Deferred-Delivery"); + match(field::delivery_date, "Delivery-Date"); + match(field::delta_base, "Delta-Base"); + match(field::depth, "Depth"); + match(field::derived_from, "Derived-From"); + match(field::destination, "Destination"); + match(field::differential_id, "Differential-ID"); + match(field::digest, "Digest"); + match(field::discarded_x400_ipms_extensions, "Discarded-X400-IPMS-Extensions"); + match(field::discarded_x400_mts_extensions, "Discarded-X400-MTS-Extensions"); + match(field::disclose_recipients, "Disclose-Recipients"); + match(field::disposition_notification_options, "Disposition-Notification-Options"); + match(field::disposition_notification_to, "Disposition-Notification-To"); + match(field::distribution, "Distribution"); + match(field::dkim_signature, "DKIM-Signature"); + match(field::dl_expansion_history, "DL-Expansion-History"); + match(field::downgraded_bcc, "Downgraded-Bcc"); + match(field::downgraded_cc, "Downgraded-Cc"); + match(field::downgraded_disposition_notification_to, "Downgraded-Disposition-Notification-To"); + match(field::downgraded_final_recipient, "Downgraded-Final-Recipient"); + match(field::downgraded_from, "Downgraded-From"); + match(field::downgraded_in_reply_to, "Downgraded-In-Reply-To"); + match(field::downgraded_mail_from, "Downgraded-Mail-From"); + match(field::downgraded_message_id, "Downgraded-Message-Id"); + match(field::downgraded_original_recipient, "Downgraded-Original-Recipient"); + match(field::downgraded_rcpt_to, "Downgraded-Rcpt-To"); + match(field::downgraded_references, "Downgraded-References"); + match(field::downgraded_reply_to, "Downgraded-Reply-To"); + match(field::downgraded_resent_bcc, "Downgraded-Resent-Bcc"); + match(field::downgraded_resent_cc, "Downgraded-Resent-Cc"); + match(field::downgraded_resent_from, "Downgraded-Resent-From"); + match(field::downgraded_resent_reply_to, "Downgraded-Resent-Reply-To"); + match(field::downgraded_resent_sender, "Downgraded-Resent-Sender"); + match(field::downgraded_resent_to, "Downgraded-Resent-To"); + match(field::downgraded_return_path, "Downgraded-Return-Path"); + match(field::downgraded_sender, "Downgraded-Sender"); + match(field::downgraded_to, "Downgraded-To"); + match(field::ediint_features, "EDIINT-Features"); + match(field::eesst_version, "Eesst-Version"); + match(field::encoding, "Encoding"); + match(field::encrypted, "Encrypted"); + match(field::errors_to, "Errors-To"); + match(field::etag, "ETag"); + match(field::expect, "Expect"); + match(field::expires, "Expires"); + match(field::expiry_date, "Expiry-Date"); + match(field::ext, "Ext"); + match(field::followup_to, "Followup-To"); + match(field::forwarded, "Forwarded"); + match(field::from, "From"); + match(field::generate_delivery_report, "Generate-Delivery-Report"); + match(field::getprofile, "GetProfile"); + match(field::hobareg, "Hobareg"); + match(field::host, "Host"); + match(field::http2_settings, "HTTP2-Settings"); + match(field::if_, "If"); + match(field::if_match, "If-Match"); + match(field::if_modified_since, "If-Modified-Since"); + match(field::if_none_match, "If-None-Match"); + match(field::if_range, "If-Range"); + match(field::if_schedule_tag_match, "If-Schedule-Tag-Match"); + match(field::if_unmodified_since, "If-Unmodified-Since"); + match(field::im, "IM"); + match(field::importance, "Importance"); + match(field::in_reply_to, "In-Reply-To"); + match(field::incomplete_copy, "Incomplete-Copy"); + match(field::injection_date, "Injection-Date"); + match(field::injection_info, "Injection-Info"); + match(field::jabber_id, "Jabber-ID"); + match(field::keep_alive, "Keep-Alive"); + match(field::keywords, "Keywords"); + match(field::label, "Label"); + match(field::language, "Language"); + match(field::last_modified, "Last-Modified"); + match(field::latest_delivery_time, "Latest-Delivery-Time"); + match(field::lines, "Lines"); + match(field::link, "Link"); + match(field::list_archive, "List-Archive"); + match(field::list_help, "List-Help"); + match(field::list_id, "List-ID"); + match(field::list_owner, "List-Owner"); + match(field::list_post, "List-Post"); + match(field::list_subscribe, "List-Subscribe"); + match(field::list_unsubscribe, "List-Unsubscribe"); + match(field::list_unsubscribe_post, "List-Unsubscribe-Post"); + match(field::location, "Location"); + match(field::lock_token, "Lock-Token"); + match(field::man, "Man"); + match(field::max_forwards, "Max-Forwards"); + match(field::memento_datetime, "Memento-Datetime"); + match(field::message_context, "Message-Context"); + match(field::message_id, "Message-ID"); + match(field::message_type, "Message-Type"); + match(field::meter, "Meter"); + match(field::method_check, "Method-Check"); + match(field::method_check_expires, "Method-Check-Expires"); + match(field::mime_version, "MIME-Version"); + match(field::mmhs_acp127_message_identifier, "MMHS-Acp127-Message-Identifier"); + match(field::mmhs_authorizing_users, "MMHS-Authorizing-Users"); + match(field::mmhs_codress_message_indicator, "MMHS-Codress-Message-Indicator"); + match(field::mmhs_copy_precedence, "MMHS-Copy-Precedence"); + match(field::mmhs_exempted_address, "MMHS-Exempted-Address"); + match(field::mmhs_extended_authorisation_info, "MMHS-Extended-Authorisation-Info"); + match(field::mmhs_handling_instructions, "MMHS-Handling-Instructions"); + match(field::mmhs_message_instructions, "MMHS-Message-Instructions"); + match(field::mmhs_message_type, "MMHS-Message-Type"); + match(field::mmhs_originator_plad, "MMHS-Originator-PLAD"); + match(field::mmhs_originator_reference, "MMHS-Originator-Reference"); + match(field::mmhs_other_recipients_indicator_cc, "MMHS-Other-Recipients-Indicator-CC"); + match(field::mmhs_other_recipients_indicator_to, "MMHS-Other-Recipients-Indicator-To"); + match(field::mmhs_primary_precedence, "MMHS-Primary-Precedence"); + match(field::mmhs_subject_indicator_codes, "MMHS-Subject-Indicator-Codes"); + match(field::mt_priority, "MT-Priority"); + match(field::negotiate, "Negotiate"); + match(field::newsgroups, "Newsgroups"); + match(field::nntp_posting_date, "NNTP-Posting-Date"); + match(field::nntp_posting_host, "NNTP-Posting-Host"); + match(field::non_compliance, "Non-Compliance"); + match(field::obsoletes, "Obsoletes"); + match(field::opt, "Opt"); + match(field::optional, "Optional"); + match(field::optional_www_authenticate, "Optional-WWW-Authenticate"); + match(field::ordering_type, "Ordering-Type"); + match(field::organization, "Organization"); + match(field::origin, "Origin"); + match(field::original_encoded_information_types, "Original-Encoded-Information-Types"); + match(field::original_from, "Original-From"); + match(field::original_message_id, "Original-Message-ID"); + match(field::original_recipient, "Original-Recipient"); + match(field::original_sender, "Original-Sender"); + match(field::original_subject, "Original-Subject"); + match(field::originator_return_address, "Originator-Return-Address"); + match(field::overwrite, "Overwrite"); + match(field::p3p, "P3P"); + match(field::path, "Path"); + match(field::pep, "PEP"); + match(field::pep_info, "Pep-Info"); + match(field::pics_label, "PICS-Label"); + match(field::position, "Position"); + match(field::posting_version, "Posting-Version"); + match(field::pragma, "Pragma"); + match(field::prefer, "Prefer"); + match(field::preference_applied, "Preference-Applied"); + match(field::prevent_nondelivery_report, "Prevent-NonDelivery-Report"); + match(field::priority, "Priority"); + match(field::privicon, "Privicon"); + match(field::profileobject, "ProfileObject"); + match(field::protocol, "Protocol"); + match(field::protocol_info, "Protocol-Info"); + match(field::protocol_query, "Protocol-Query"); + match(field::protocol_request, "Protocol-Request"); + match(field::proxy_authenticate, "Proxy-Authenticate"); + match(field::proxy_authentication_info, "Proxy-Authentication-Info"); + match(field::proxy_authorization, "Proxy-Authorization"); + match(field::proxy_connection, "Proxy-Connection"); + match(field::proxy_features, "Proxy-Features"); + match(field::proxy_instruction, "Proxy-Instruction"); + match(field::public_, "Public"); + match(field::public_key_pins, "Public-Key-Pins"); + match(field::public_key_pins_report_only, "Public-Key-Pins-Report-Only"); + match(field::range, "Range"); + match(field::received, "Received"); + match(field::received_spf, "Received-SPF"); + match(field::redirect_ref, "Redirect-Ref"); + match(field::references, "References"); + match(field::referer, "Referer"); + match(field::referer_root, "Referer-Root"); + match(field::relay_version, "Relay-Version"); + match(field::reply_by, "Reply-By"); + match(field::reply_to, "Reply-To"); + match(field::require_recipient_valid_since, "Require-Recipient-Valid-Since"); + match(field::resent_bcc, "Resent-Bcc"); + match(field::resent_cc, "Resent-Cc"); + match(field::resent_date, "Resent-Date"); + match(field::resent_from, "Resent-From"); + match(field::resent_message_id, "Resent-Message-ID"); + match(field::resent_reply_to, "Resent-Reply-To"); + match(field::resent_sender, "Resent-Sender"); + match(field::resent_to, "Resent-To"); + match(field::resolution_hint, "Resolution-Hint"); + match(field::resolver_location, "Resolver-Location"); + match(field::retry_after, "Retry-After"); + match(field::return_path, "Return-Path"); + match(field::safe, "Safe"); + match(field::schedule_reply, "Schedule-Reply"); + match(field::schedule_tag, "Schedule-Tag"); + match(field::sec_websocket_accept, "Sec-WebSocket-Accept"); + match(field::sec_websocket_extensions, "Sec-WebSocket-Extensions"); + match(field::sec_websocket_key, "Sec-WebSocket-Key"); + match(field::sec_websocket_protocol, "Sec-WebSocket-Protocol"); + match(field::sec_websocket_version, "Sec-WebSocket-Version"); + match(field::security_scheme, "Security-Scheme"); + match(field::see_also, "See-Also"); + match(field::sender, "Sender"); + match(field::sensitivity, "Sensitivity"); + match(field::server, "Server"); + match(field::set_cookie, "Set-Cookie"); + match(field::set_cookie2, "Set-Cookie2"); + match(field::setprofile, "SetProfile"); + match(field::sio_label, "SIO-Label"); + match(field::sio_label_history, "SIO-Label-History"); + match(field::slug, "SLUG"); + match(field::soapaction, "SoapAction"); + match(field::solicitation, "Solicitation"); + match(field::status_uri, "Status-URI"); + match(field::strict_transport_security, "Strict-Transport-Security"); + match(field::subject, "Subject"); + match(field::subok, "SubOK"); + match(field::subst, "Subst"); + match(field::summary, "Summary"); + match(field::supersedes, "Supersedes"); + match(field::surrogate_capability, "Surrogate-Capability"); + match(field::surrogate_control, "Surrogate-Control"); + match(field::tcn, "TCN"); + match(field::te, "TE"); + match(field::timeout, "Timeout"); + match(field::title, "Title"); + match(field::to, "To"); + match(field::topic, "Topic"); + match(field::trailer, "Trailer"); + match(field::transfer_encoding, "Transfer-Encoding"); + match(field::ttl, "TTL"); + match(field::ua_color, "UA-Color"); + match(field::ua_media, "UA-Media"); + match(field::ua_pixels, "UA-Pixels"); + match(field::ua_resolution, "UA-Resolution"); + match(field::ua_windowpixels, "UA-Windowpixels"); + match(field::upgrade, "Upgrade"); + match(field::urgency, "Urgency"); + match(field::uri, "URI"); + match(field::user_agent, "User-Agent"); + match(field::variant_vary, "Variant-Vary"); + match(field::vary, "Vary"); + match(field::vbr_info, "VBR-Info"); + match(field::version, "Version"); + match(field::via, "Via"); + match(field::want_digest, "Want-Digest"); + match(field::warning, "Warning"); + match(field::www_authenticate, "WWW-Authenticate"); + match(field::x_archived_at, "X-Archived-At"); + match(field::x_device_accept, "X-Device-Accept"); + match(field::x_device_accept_charset, "X-Device-Accept-Charset"); + match(field::x_device_accept_encoding, "X-Device-Accept-Encoding"); + match(field::x_device_accept_language, "X-Device-Accept-Language"); + match(field::x_device_user_agent, "X-Device-User-Agent"); + match(field::x_frame_options, "X-Frame-Options"); + match(field::x_mittente, "X-Mittente"); + match(field::x_pgp_sig, "X-PGP-Sig"); + match(field::x_ricevuta, "X-Ricevuta"); + match(field::x_riferimento_message_id, "X-Riferimento-Message-ID"); + match(field::x_tiporicevuta, "X-TipoRicevuta"); + match(field::x_trasporto, "X-Trasporto"); + match(field::x_verificasicurezza, "X-VerificaSicurezza"); + match(field::x400_content_identifier, "X400-Content-Identifier"); + match(field::x400_content_return, "X400-Content-Return"); + match(field::x400_content_type, "X400-Content-Type"); + match(field::x400_mts_identifier, "X400-MTS-Identifier"); + match(field::x400_originator, "X400-Originator"); + match(field::x400_received, "X400-Received"); + match(field::x400_recipients, "X400-Recipients"); + match(field::x400_trace, "X400-Trace"); + match(field::xref, "Xref"); + + auto const unknown = + [&](string_view s) + { + BEAST_EXPECT(string_to_field(s) == field::unknown); + }; + unknown(""); + unknown("x"); + } + + void run() override + { + testField(); + pass(); + } +}; + +BEAST_DEFINE_TESTSUITE(field,http,beast); + +} // http +} // beast diff --git a/src/beast/test/http/fields.cpp b/src/beast/test/http/fields.cpp index 8ac52d5bf2..0958952de9 100644 --- a/src/beast/test/http/fields.cpp +++ b/src/beast/test/http/fields.cpp @@ -7,3 +7,915 @@ // Test that header file is self-contained. #include + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +BOOST_STATIC_ASSERT(is_fields::value); + +class fields_test : public beast::unit_test::suite +{ +public: + template + using fa_t = basic_fields; + + using f_t = fa_t>; + + template + static + void + fill(std::size_t n, basic_fields& f) + { + for(std::size_t i = 1; i<= n; ++i) + f.insert(boost::lexical_cast(i), i); + } + + template + static + void + self_assign(U& u, V&& v) + { + u = std::forward(v); + } + + template + static + bool + empty(basic_fields const& f) + { + return f.begin() == f.end(); + } + + template + static + std::size_t + size(basic_fields const& f) + { + return std::distance(f.begin(), f.end()); + } + + void + testMembers() + { + using namespace test; + + // compare equal + using equal_t = test::test_allocator; + + // compare not equal + using unequal_t = test::test_allocator; + + // construction + { + { + fields f; + BEAST_EXPECT(f.begin() == f.end()); + } + { + unequal_t a1; + basic_fields f{a1}; + BEAST_EXPECT(f.get_allocator() == a1); + BEAST_EXPECT(f.get_allocator() != unequal_t{}); + } + } + + // move construction + { + { + basic_fields f1; + BEAST_EXPECT(f1.get_allocator()->nmove == 0); + f1.insert("1", "1"); + BEAST_EXPECT(f1["1"] == "1"); + basic_fields f2{std::move(f1)}; + BEAST_EXPECT(f2.get_allocator()->nmove == 1); + BEAST_EXPECT(f2["1"] == "1"); + BEAST_EXPECT(f1["1"] == ""); + } + // allocators equal + { + basic_fields f1; + f1.insert("1", "1"); + equal_t a; + basic_fields f2{std::move(f1), a}; + BEAST_EXPECT(f2["1"] == "1"); + BEAST_EXPECT(f1["1"] == ""); + } + { + // allocators unequal + basic_fields f1; + f1.insert("1", "1"); + unequal_t a; + basic_fields f2{std::move(f1), a}; + BEAST_EXPECT(f2["1"] == "1"); + } + } + + // copy construction + { + { + basic_fields f1; + f1.insert("1", "1"); + basic_fields f2{f1}; + BEAST_EXPECT(f1.get_allocator() == f2.get_allocator()); + BEAST_EXPECT(f1["1"] == "1"); + BEAST_EXPECT(f2["1"] == "1"); + } + { + basic_fields f1; + f1.insert("1", "1"); + unequal_t a; + basic_fields f2(f1, a); + BEAST_EXPECT(f1.get_allocator() != f2.get_allocator()); + BEAST_EXPECT(f1["1"] == "1"); + BEAST_EXPECT(f2["1"] == "1"); + } + { + basic_fields f1; + f1.insert("1", "1"); + basic_fields f2(f1); + BEAST_EXPECT(f1["1"] == "1"); + BEAST_EXPECT(f2["1"] == "1"); + } + { + basic_fields f1; + f1.insert("1", "1"); + equal_t a; + basic_fields f2(f1, a); + BEAST_EXPECT(f2.get_allocator() == a); + BEAST_EXPECT(f1["1"] == "1"); + BEAST_EXPECT(f2["1"] == "1"); + } + } + + // move assignment + { + { + fields f1; + f1.insert("1", "1"); + fields f2; + f2 = std::move(f1); + BEAST_EXPECT(f1.begin() == f1.end()); + BEAST_EXPECT(f2["1"] == "1"); + } + { + // propagate_on_container_move_assignment : true + using pocma_t = test::test_allocator; + basic_fields f1; + f1.insert("1", "1"); + basic_fields f2; + f2 = std::move(f1); + BEAST_EXPECT(f1.begin() == f1.end()); + BEAST_EXPECT(f2["1"] == "1"); + } + { + // propagate_on_container_move_assignment : false + using pocma_t = test::test_allocator; + basic_fields f1; + f1.insert("1", "1"); + basic_fields f2; + f2 = std::move(f1); + BEAST_EXPECT(f1.begin() == f1.end()); + BEAST_EXPECT(f2["1"] == "1"); + } + } + + // copy assignment + { + { + fields f1; + f1.insert("1", "1"); + fields f2; + f2 = f1; + BEAST_EXPECT(f1["1"] == "1"); + BEAST_EXPECT(f2["1"] == "1"); + basic_fields f3; + f3 = f2; + BEAST_EXPECT(f3["1"] == "1"); + } + { + // propagate_on_container_copy_assignment : true + using pocca_t = test::test_allocator; + basic_fields f1; + f1.insert("1", "1"); + basic_fields f2; + f2 = f1; + BEAST_EXPECT(f2["1"] == "1"); + } + { + // propagate_on_container_copy_assignment : false + using pocca_t = test::test_allocator; + basic_fields f1; + f1.insert("1", "1"); + basic_fields f2; + f2 = f1; + BEAST_EXPECT(f2["1"] == "1"); + } + } + + // swap + { + { + // propagate_on_container_swap : true + using pocs_t = test::test_allocator; + pocs_t a1, a2; + BEAST_EXPECT(a1 != a2); + basic_fields f1{a1}; + f1.insert("1", "1"); + basic_fields f2{a2}; + BEAST_EXPECT(f1.get_allocator() == a1); + BEAST_EXPECT(f2.get_allocator() == a2); + swap(f1, f2); + BEAST_EXPECT(f1.get_allocator() == a2); + BEAST_EXPECT(f2.get_allocator() == a1); + BEAST_EXPECT(f1.begin() == f1.end()); + BEAST_EXPECT(f2["1"] == "1"); + swap(f1, f2); + BEAST_EXPECT(f1.get_allocator() == a1); + BEAST_EXPECT(f2.get_allocator() == a2); + BEAST_EXPECT(f1["1"] == "1"); + BEAST_EXPECT(f2.begin() == f2.end()); + } + { + // propagate_on_container_swap : false + using pocs_t = test::test_allocator; + pocs_t a1, a2; + BEAST_EXPECT(a1 == a2); + BEAST_EXPECT(a1.id() != a2.id()); + basic_fields f1{a1}; + f1.insert("1", "1"); + basic_fields f2{a2}; + BEAST_EXPECT(f1.get_allocator() == a1); + BEAST_EXPECT(f2.get_allocator() == a2); + swap(f1, f2); + BEAST_EXPECT(f1.get_allocator().id() == a1.id()); + BEAST_EXPECT(f2.get_allocator().id() == a2.id()); + BEAST_EXPECT(f1.begin() == f1.end()); + BEAST_EXPECT(f2["1"] == "1"); + swap(f1, f2); + BEAST_EXPECT(f1.get_allocator().id() == a1.id()); + BEAST_EXPECT(f2.get_allocator().id() == a2.id()); + BEAST_EXPECT(f1["1"] == "1"); + BEAST_EXPECT(f2.begin() == f2.end()); + } + } + + // operations + { + fields f; + f.insert(field::user_agent, "x"); + BEAST_EXPECT(f.count(field::user_agent)); + BEAST_EXPECT(f.count(to_string(field::user_agent))); + BEAST_EXPECT(f.count(field::user_agent) == 1); + BEAST_EXPECT(f.count(to_string(field::user_agent)) == 1); + f.insert(field::user_agent, "y"); + BEAST_EXPECT(f.count(field::user_agent) == 2); + } + } + + void testHeaders() + { + f_t f1; + BEAST_EXPECT(empty(f1)); + fill(1, f1); + BEAST_EXPECT(size(f1) == 1); + f_t f2; + f2 = f1; + BEAST_EXPECT(size(f2) == 1); + f2.insert("2", "2"); + BEAST_EXPECT(std::distance(f2.begin(), f2.end()) == 2); + f1 = std::move(f2); + BEAST_EXPECT(size(f1) == 2); + BEAST_EXPECT(size(f2) == 0); + f_t f3(std::move(f1)); + BEAST_EXPECT(size(f3) == 2); + BEAST_EXPECT(size(f1) == 0); + self_assign(f3, std::move(f3)); + BEAST_EXPECT(size(f3) == 2); + BEAST_EXPECT(f2.erase("Not-Present") == 0); + } + + void testRFC2616() + { + f_t f; + f.insert("a", "w"); + f.insert("a", "x"); + f.insert("aa", "y"); + f.insert("f", "z"); + BEAST_EXPECT(f.count("a") == 2); + } + + void testErase() + { + f_t f; + f.insert("a", "w"); + f.insert("a", "x"); + f.insert("aa", "y"); + f.insert("f", "z"); + BEAST_EXPECT(size(f) == 4); + f.erase("a"); + BEAST_EXPECT(size(f) == 2); + } + + void + testContainer() + { + { + // group fields + fields f; + f.insert(field::age, 1); + f.insert(field::body, 2); + f.insert(field::close, 3); + f.insert(field::body, 4); + BEAST_EXPECT(std::next(f.begin(), 0)->name() == field::age); + BEAST_EXPECT(std::next(f.begin(), 1)->name() == field::body); + BEAST_EXPECT(std::next(f.begin(), 2)->name() == field::body); + BEAST_EXPECT(std::next(f.begin(), 3)->name() == field::close); + BEAST_EXPECT(std::next(f.begin(), 0)->name_string() == "Age"); + BEAST_EXPECT(std::next(f.begin(), 1)->name_string() == "Body"); + BEAST_EXPECT(std::next(f.begin(), 2)->name_string() == "Body"); + BEAST_EXPECT(std::next(f.begin(), 3)->name_string() == "Close"); + BEAST_EXPECT(std::next(f.begin(), 0)->value() == "1"); + BEAST_EXPECT(std::next(f.begin(), 1)->value() == "2"); + BEAST_EXPECT(std::next(f.begin(), 2)->value() == "4"); + BEAST_EXPECT(std::next(f.begin(), 3)->value() == "3"); + BEAST_EXPECT(f.erase(field::body) == 2); + BEAST_EXPECT(std::next(f.begin(), 0)->name_string() == "Age"); + BEAST_EXPECT(std::next(f.begin(), 1)->name_string() == "Close"); + } + { + // group fields, case insensitive + fields f; + f.insert("a", 1); + f.insert("ab", 2); + f.insert("b", 3); + f.insert("AB", 4); + BEAST_EXPECT(std::next(f.begin(), 0)->name() == field::unknown); + BEAST_EXPECT(std::next(f.begin(), 1)->name() == field::unknown); + BEAST_EXPECT(std::next(f.begin(), 2)->name() == field::unknown); + BEAST_EXPECT(std::next(f.begin(), 3)->name() == field::unknown); + BEAST_EXPECT(std::next(f.begin(), 0)->name_string() == "a"); + BEAST_EXPECT(std::next(f.begin(), 1)->name_string() == "ab"); + BEAST_EXPECT(std::next(f.begin(), 2)->name_string() == "AB"); + BEAST_EXPECT(std::next(f.begin(), 3)->name_string() == "b"); + BEAST_EXPECT(std::next(f.begin(), 0)->value() == "1"); + BEAST_EXPECT(std::next(f.begin(), 1)->value() == "2"); + BEAST_EXPECT(std::next(f.begin(), 2)->value() == "4"); + BEAST_EXPECT(std::next(f.begin(), 3)->value() == "3"); + BEAST_EXPECT(f.erase("Ab") == 2); + BEAST_EXPECT(std::next(f.begin(), 0)->name_string() == "a"); + BEAST_EXPECT(std::next(f.begin(), 1)->name_string() == "b"); + } + { + // verify insertion orde + fields f; + f.insert( "a", 1); + f.insert("dd", 2); + f.insert("b", 3); + f.insert("dD", 4); + f.insert("c", 5); + f.insert("Dd", 6); + f.insert("DD", 7); + f.insert( "e", 8); + BEAST_EXPECT(f.count("dd") == 4); + BEAST_EXPECT(std::next(f.begin(), 1)->name_string() == "dd"); + BEAST_EXPECT(std::next(f.begin(), 2)->name_string() == "dD"); + BEAST_EXPECT(std::next(f.begin(), 3)->name_string() == "Dd"); + BEAST_EXPECT(std::next(f.begin(), 4)->name_string() == "DD"); + f.set("dd", "-"); + BEAST_EXPECT(f.count("dd") == 1); + BEAST_EXPECT(f["dd"] == "-"); + } + } + + struct sized_body + { + using value_type = std::uint64_t; + + static + std::uint64_t + size(value_type const& v) + { + return v; + } + }; + + struct unsized_body + { + struct value_type {}; + }; + + void + testPreparePayload() + { + // GET, empty + { + request req; + req.version = 11; + req.method(verb::get); + + req.prepare_payload(); + BEAST_EXPECT(req.count(field::content_length) == 0); + BEAST_EXPECT(req.count(field::transfer_encoding) == 0); + + req.set(field::content_length, "0"); + req.set(field::transfer_encoding, "chunked"); + req.prepare_payload(); + + BEAST_EXPECT(req.count(field::content_length) == 0); + BEAST_EXPECT(req.count(field::transfer_encoding) == 0); + + req.set(field::transfer_encoding, "deflate"); + req.prepare_payload(); + BEAST_EXPECT(req.count(field::content_length) == 0); + BEAST_EXPECT(req[field::transfer_encoding] == "deflate"); + + req.set(field::transfer_encoding, "deflate, chunked"); + req.prepare_payload(); + BEAST_EXPECT(req.count(field::content_length) == 0); + BEAST_EXPECT(req[field::transfer_encoding] == "deflate"); + } + + // GET, sized + { + request req; + req.version = 11; + req.method(verb::get); + req.body = 50; + + req.prepare_payload(); + BEAST_EXPECT(req[field::content_length] == "50"); + BEAST_EXPECT(req[field::transfer_encoding] == ""); + + req.set(field::content_length, "0"); + req.set(field::transfer_encoding, "chunked"); + req.prepare_payload(); + BEAST_EXPECT(req[field::content_length] == "50"); + BEAST_EXPECT(req.count(field::transfer_encoding) == 0); + + req.set(field::transfer_encoding, "deflate, chunked"); + req.prepare_payload(); + BEAST_EXPECT(req[field::content_length] == "50"); + BEAST_EXPECT(req[field::transfer_encoding] == "deflate"); + } + + // PUT, empty + { + request req; + req.version = 11; + req.method(verb::put); + + req.prepare_payload(); + BEAST_EXPECT(req[field::content_length] == "0"); + BEAST_EXPECT(req.count(field::transfer_encoding) == 0); + + req.set(field::content_length, "50"); + req.set(field::transfer_encoding, "deflate, chunked"); + req.prepare_payload(); + BEAST_EXPECT(req[field::content_length] == "0"); + BEAST_EXPECT(req[field::transfer_encoding] == "deflate"); + } + + // PUT, sized + { + request req; + req.version = 11; + req.method(verb::put); + req.body = 50; + + req.prepare_payload(); + BEAST_EXPECT(req[field::content_length] == "50"); + BEAST_EXPECT(req.count(field::transfer_encoding) == 0); + + req.set(field::content_length, "25"); + req.set(field::transfer_encoding, "deflate, chunked"); + req.prepare_payload(); + BEAST_EXPECT(req[field::content_length] == "50"); + BEAST_EXPECT(req[field::transfer_encoding] == "deflate"); + } + + // POST, unsized + { + request req; + req.version = 11; + req.method(verb::post); + + req.prepare_payload(); + BEAST_EXPECT(req.count(field::content_length) == 0); + BEAST_EXPECT(req[field::transfer_encoding] == "chunked"); + + req.set(field::transfer_encoding, "deflate"); + req.prepare_payload(); + BEAST_EXPECT(req.count(field::content_length) == 0); + BEAST_EXPECT(req[field::transfer_encoding] == "deflate, chunked"); + } + + // POST, unsized HTTP/1.0 + { + request req; + req.version = 10; + req.method(verb::post); + + req.prepare_payload(); + BEAST_EXPECT(req.count(field::content_length) == 0); + BEAST_EXPECT(req.count(field::transfer_encoding) == 0); + + req.set(field::transfer_encoding, "deflate"); + req.prepare_payload(); + BEAST_EXPECT(req.count(field::content_length) == 0); + BEAST_EXPECT(req[field::transfer_encoding] == "deflate"); + } + + // OK, empty + { + response res; + res.version = 11; + + res.prepare_payload(); + BEAST_EXPECT(res[field::content_length] == "0"); + BEAST_EXPECT(res.count(field::transfer_encoding) == 0); + + res.erase(field::content_length); + res.set(field::transfer_encoding, "chunked"); + res.prepare_payload(); + BEAST_EXPECT(res[field::content_length] == "0"); + BEAST_EXPECT(res.count(field::transfer_encoding) == 0); + } + + // OK, sized + { + response res; + res.version = 11; + res.body = 50; + + res.prepare_payload(); + BEAST_EXPECT(res[field::content_length] == "50"); + BEAST_EXPECT(res.count(field::transfer_encoding) == 0); + + res.erase(field::content_length); + res.set(field::transfer_encoding, "chunked"); + res.prepare_payload(); + BEAST_EXPECT(res[field::content_length] == "50"); + BEAST_EXPECT(res.count(field::transfer_encoding) == 0); + } + + // OK, unsized + { + response res; + res.version = 11; + + res.prepare_payload(); + BEAST_EXPECT(res.count(field::content_length) == 0); + BEAST_EXPECT(res[field::transfer_encoding] == "chunked"); + } + } + + void + testKeepAlive() + { + response res; + auto const keep_alive = + [&](bool v) + { + res.keep_alive(v); + BEAST_EXPECT( + (res.keep_alive() && v) || + (! res.keep_alive() && ! v)); + }; + + BOOST_STATIC_ASSERT(fields::max_static_buffer == 4096); + std::string const big(4096 + 1, 'a'); + + // HTTP/1.0 + res.version = 10; + res.erase(field::connection); + + keep_alive(false); + BEAST_EXPECT(res.count(field::connection) == 0); + + res.set(field::connection, "close"); + keep_alive(false); + BEAST_EXPECT(res.count(field::connection) == 0); + + res.set(field::connection, "keep-alive"); + keep_alive(false); + BEAST_EXPECT(res.count(field::connection) == 0); + + res.set(field::connection, "keep-alive, close"); + keep_alive(false); + BEAST_EXPECT(res.count(field::connection) == 0); + + res.erase(field::connection); + keep_alive(true); + BEAST_EXPECT(res[field::connection] == "keep-alive"); + + res.set(field::connection, "close"); + keep_alive(true); + BEAST_EXPECT(res[field::connection] == "keep-alive"); + + res.set(field::connection, "keep-alive"); + keep_alive(true); + BEAST_EXPECT(res[field::connection] == "keep-alive"); + + res.set(field::connection, "keep-alive, close"); + keep_alive(true); + BEAST_EXPECT(res[field::connection] == "keep-alive"); + + auto const test10 = + [&](std::string s) + { + res.set(field::connection, s); + keep_alive(false); + BEAST_EXPECT(res[field::connection] == s); + + res.set(field::connection, s + ", close"); + keep_alive(false); + BEAST_EXPECT(res[field::connection] == s); + + res.set(field::connection, "keep-alive, " + s); + keep_alive(false); + BEAST_EXPECT(res[field::connection] == s); + + res.set(field::connection, "keep-alive, " + s + ", close"); + keep_alive(false); + BEAST_EXPECT(res[field::connection] == s); + + res.set(field::connection, s); + keep_alive(true); + BEAST_EXPECT(res[field::connection] == s + ", keep-alive"); + + res.set(field::connection, s + ", close"); + keep_alive(true); + BEAST_EXPECT(res[field::connection] == s + ", keep-alive"); + + res.set(field::connection, "keep-alive, " + s); + keep_alive(true); + BEAST_EXPECT(res[field::connection] == "keep-alive, " + s); + + res.set(field::connection, "keep-alive, " + s+ ", close"); + keep_alive(true); + BEAST_EXPECT(res[field::connection] == "keep-alive, " + s); + }; + + test10("foo"); + test10(big); + + // HTTP/1.1 + res.version = 11; + + res.erase(field::connection); + keep_alive(true); + BEAST_EXPECT(res.count(field::connection) == 0); + + res.set(field::connection, "close"); + keep_alive(true); + BEAST_EXPECT(res.count(field::connection) == 0); + + res.set(field::connection, "keep-alive"); + keep_alive(true); + BEAST_EXPECT(res.count(field::connection) == 0); + + res.set(field::connection, "keep-alive, close"); + keep_alive(true); + BEAST_EXPECT(res.count(field::connection) == 0); + + res.erase(field::connection); + keep_alive(false); + BEAST_EXPECT(res[field::connection] == "close"); + + res.set(field::connection, "close"); + keep_alive(false); + BEAST_EXPECT(res[field::connection] == "close"); + + res.set(field::connection, "keep-alive"); + keep_alive(false); + BEAST_EXPECT(res[field::connection] == "close"); + + res.set(field::connection, "keep-alive, close"); + keep_alive(false); + BEAST_EXPECT(res[field::connection] == "close"); + + auto const test11 = + [&](std::string s) + { + res.set(field::connection, s); + keep_alive(true); + BEAST_EXPECT(res[field::connection] == s); + + res.set(field::connection, s + ", close"); + keep_alive(true); + BEAST_EXPECT(res[field::connection] == s); + + res.set(field::connection, "keep-alive, " + s); + keep_alive(true); + BEAST_EXPECT(res[field::connection] == s); + + res.set(field::connection, "keep-alive, " + s + ", close"); + keep_alive(true); + BEAST_EXPECT(res[field::connection] == s); + + res.set(field::connection, s); + keep_alive(false); + BEAST_EXPECT(res[field::connection] == s + ", close"); + + res.set(field::connection, "close, " + s); + keep_alive(false); + BEAST_EXPECT(res[field::connection] == "close, " + s); + + res.set(field::connection, "keep-alive, " + s); + keep_alive(false); + BEAST_EXPECT(res[field::connection] == s + ", close"); + + res.set(field::connection, "close, " + s + ", keep-alive"); + keep_alive(false); + BEAST_EXPECT(res[field::connection] == "close, " + s); + }; + + test11("foo"); + test11(big); + } + + void + testContentLength() + { + response res{status::ok, 11}; + BEAST_EXPECT(res.count(field::content_length) == 0); + BEAST_EXPECT(res.count(field::transfer_encoding) == 0); + + res.content_length(0); + BEAST_EXPECT(res[field::content_length] == "0"); + + res.content_length(100); + BEAST_EXPECT(res[field::content_length] == "100"); + + res.content_length(boost::none); + BEAST_EXPECT(res.count(field::content_length) == 0); + + res.set(field::transfer_encoding, "chunked"); + res.content_length(0); + BEAST_EXPECT(res[field::content_length] == "0"); + BEAST_EXPECT(res.count(field::transfer_encoding) == 0); + + res.set(field::transfer_encoding, "chunked"); + res.content_length(100); + BEAST_EXPECT(res[field::content_length] == "100"); + BEAST_EXPECT(res.count(field::transfer_encoding) == 0); + + res.set(field::transfer_encoding, "chunked"); + res.content_length(boost::none); + BEAST_EXPECT(res.count(field::content_length) == 0); + BEAST_EXPECT(res.count(field::transfer_encoding) == 0); + + auto const check = [&](std::string s) + { + res.set(field::transfer_encoding, s); + res.content_length(0); + BEAST_EXPECT(res[field::content_length] == "0"); + BEAST_EXPECT(res[field::transfer_encoding] == s); + + res.set(field::transfer_encoding, s); + res.content_length(100); + BEAST_EXPECT(res[field::content_length] == "100"); + BEAST_EXPECT(res[field::transfer_encoding] == s); + + res.set(field::transfer_encoding, s); + res.content_length(boost::none); + BEAST_EXPECT(res.count(field::content_length) == 0); + BEAST_EXPECT(res[field::transfer_encoding] == s); + + res.set(field::transfer_encoding, s + ", chunked"); + res.content_length(0); + BEAST_EXPECT(res[field::content_length] == "0"); + BEAST_EXPECT(res[field::transfer_encoding] == s); + + res.set(field::transfer_encoding, s + ", chunked"); + res.content_length(100); + BEAST_EXPECT(res[field::content_length] == "100"); + BEAST_EXPECT(res[field::transfer_encoding] == s); + + res.set(field::transfer_encoding, s + ", chunked"); + res.content_length(boost::none); + BEAST_EXPECT(res.count(field::content_length) == 0); + BEAST_EXPECT(res[field::transfer_encoding] == s); + + res.set(field::transfer_encoding, "chunked, " + s); + res.content_length(0); + BEAST_EXPECT(res[field::content_length] == "0"); + BEAST_EXPECT(res[field::transfer_encoding] == "chunked, " + s); + + res.set(field::transfer_encoding, "chunked, " + s); + res.content_length(100); + BEAST_EXPECT(res[field::content_length] == "100"); + BEAST_EXPECT(res[field::transfer_encoding] == "chunked, " + s); + + res.set(field::transfer_encoding, "chunked, " + s); + res.content_length(boost::none); + BEAST_EXPECT(res.count(field::content_length) == 0); + BEAST_EXPECT(res[field::transfer_encoding] == "chunked, " + s); + }; + + check("foo"); + + BOOST_STATIC_ASSERT(fields::max_static_buffer == 4096); + std::string const big(4096 + 1, 'a'); + + check(big); + } + + void + testChunked() + { + response res{status::ok, 11}; + BEAST_EXPECT(res.count(field::content_length) == 0); + BEAST_EXPECT(res.count(field::transfer_encoding) == 0); + + auto const chunked = + [&](bool v) + { + res.chunked(v); + BEAST_EXPECT( + (res.chunked() && v) || + (! res.chunked() && ! v)); + BEAST_EXPECT(res.count( + field::content_length) == 0); + }; + + res.erase(field::transfer_encoding); + res.set(field::content_length, 32); + chunked(true); + BEAST_EXPECT(res[field::transfer_encoding] == "chunked"); + + res.set(field::transfer_encoding, "chunked"); + chunked(true); + BEAST_EXPECT(res[field::transfer_encoding] == "chunked"); + + res.erase(field::transfer_encoding); + res.set(field::content_length, 32); + chunked(false); + BEAST_EXPECT(res.count(field::transfer_encoding) == 0); + + res.set(field::transfer_encoding, "chunked"); + chunked(false); + BEAST_EXPECT(res.count(field::transfer_encoding) == 0); + + + + res.set(field::transfer_encoding, "foo"); + chunked(true); + BEAST_EXPECT(res[field::transfer_encoding] == "foo, chunked"); + + res.set(field::transfer_encoding, "chunked, foo"); + chunked(true); + BEAST_EXPECT(res[field::transfer_encoding] == "chunked, foo, chunked"); + + res.set(field::transfer_encoding, "chunked, foo, chunked"); + chunked(true); + BEAST_EXPECT(res[field::transfer_encoding] == "chunked, foo, chunked"); + + res.set(field::transfer_encoding, "foo, chunked"); + chunked(false); + BEAST_EXPECT(res[field::transfer_encoding] == "foo"); + + res.set(field::transfer_encoding, "chunked, foo"); + chunked(false); + BEAST_EXPECT(res[field::transfer_encoding] == "chunked, foo"); + + res.set(field::transfer_encoding, "chunked, foo, chunked"); + chunked(false); + BEAST_EXPECT(res[field::transfer_encoding] == "chunked, foo"); + } + + void + run() override + { + testMembers(); + testHeaders(); + testRFC2616(); + testErase(); + testContainer(); + testPreparePayload(); + + testKeepAlive(); + testContentLength(); + testChunked(); + } +}; + +BEAST_DEFINE_TESTSUITE(fields,http,beast); + +} // http +} // beast diff --git a/src/beast/test/http/file_body.cpp b/src/beast/test/http/file_body.cpp new file mode 100644 index 0000000000..3555be7108 --- /dev/null +++ b/src/beast/test/http/file_body.cpp @@ -0,0 +1,112 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +class file_body_test : public beast::unit_test::suite +{ +public: + struct lambda + { + flat_buffer buffer; + + template + void + operator()(error_code&, ConstBufferSequence const& buffers) + { + buffer.commit(boost::asio::buffer_copy( + buffer.prepare(boost::asio::buffer_size(buffers)), + buffers)); + } + }; + + template + void + doTestFileBody() + { + error_code ec; + string_view const s = + "HTTP/1.1 200 OK\r\n" + "Server: test\r\n" + "Content-Length: 3\r\n" + "\r\n" + "xyz"; + auto const temp = boost::filesystem::unique_path(); + { + response_parser> p; + p.eager(true); + + p.get().body.open( + temp.string().c_str(), file_mode::write, ec); + BEAST_EXPECTS(! ec, ec.message()); + + p.put(boost::asio::buffer(s.data(), s.size()), ec); + BEAST_EXPECTS(! ec, ec.message()); + } + { + File f; + f.open(temp.string().c_str(), file_mode::read, ec); + auto size = f.size(ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(size == 3); + std::string s1; + s1.resize(3); + f.read(&s1[0], s1.size(), ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECTS(s1 == "xyz", s); + } + { + lambda visit; + { + response> res{status::ok, 11}; + res.set(field::server, "test"); + res.body.open(temp.string().c_str(), + file_mode::scan, ec); + BEAST_EXPECTS(! ec, ec.message()); + res.prepare_payload(); + + serializer, fields> sr{res}; + sr.next(ec, visit); + BEAST_EXPECTS(! ec, ec.message()); + auto const cb = *visit.buffer.data().begin(); + string_view const s1{ + boost::asio::buffer_cast(cb), + boost::asio::buffer_size(cb)}; + BEAST_EXPECTS(s1 == s, s1); + } + } + boost::filesystem::remove(temp, ec); + BEAST_EXPECTS(! ec, ec.message()); + } + void + run() override + { + doTestFileBody(); + #if BEAST_USE_WIN32_FILE + doTestFileBody(); + #endif + #if BEAST_USE_POSIX_FILE + doTestFileBody(); + #endif + } +}; + +BEAST_DEFINE_TESTSUITE(file_body,http,beast); + +} // http +} // beast diff --git a/src/beast/test/http/header_parser_v1.cpp b/src/beast/test/http/header_parser_v1.cpp deleted file mode 100644 index 8200fe8c1a..0000000000 --- a/src/beast/test/http/header_parser_v1.cpp +++ /dev/null @@ -1,90 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -// Test that header file is self-contained. -#include - -#include -#include -#include - -namespace beast { -namespace http { - -class header_parser_v1_test : public beast::unit_test::suite -{ -public: - void testParser() - { - { - error_code ec; - header_parser_v1 p; - BEAST_EXPECT(! p.complete()); - auto const n = p.write(boost::asio::buffer( - "GET / HTTP/1.1\r\n" - "User-Agent: test\r\n" - "\r\n" - ), ec); - BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(p.complete()); - BEAST_EXPECT(n == 36); - } - { - error_code ec; - header_parser_v1 p; - BEAST_EXPECT(! p.complete()); - auto const n = p.write(boost::asio::buffer( - "GET / HTTP/1.1\r\n" - "User-Agent: test\r\n" - "Content-Length: 5\r\n" - "\r\n" - "*****" - ), ec); - BEAST_EXPECT(n == 55); - BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(p.complete()); - } - { - error_code ec; - header_parser_v1 p; - BEAST_EXPECT(! p.complete()); - auto const n = p.write(boost::asio::buffer( - "HTTP/1.1 200 OK\r\n" - "Server: test\r\n" - "\r\n" - ), ec); - BEAST_EXPECT(n == 33); - BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(p.complete()); - } - { - error_code ec; - header_parser_v1 p; - BEAST_EXPECT(! p.complete()); - auto const n = p.write(boost::asio::buffer( - "HTTP/1.1 200 OK\r\n" - "Server: test\r\n" - "Content-Length: 5\r\n" - "\r\n" - "*****" - ), ec); - BEAST_EXPECT(n == 52); - BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(p.complete()); - } - } - - void run() override - { - testParser(); - } -}; - -BEAST_DEFINE_TESTSUITE(header_parser_v1,http,beast); - -} // http -} // beast diff --git a/src/beast/test/http/message.cpp b/src/beast/test/http/message.cpp index 3b1c711af8..18159fee95 100644 --- a/src/beast/test/http/message.cpp +++ b/src/beast/test/http/message.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -70,212 +71,223 @@ public: }; }; - void testMessage() + // 0-arg + BOOST_STATIC_ASSERT(std::is_constructible< + request>::value); + + // 1-arg + BOOST_STATIC_ASSERT(! std::is_constructible + >::value); + + //BOOST_STATIC_ASSERT(! std::is_constructible, + // verb, string_view, unsigned>::value); + + BOOST_STATIC_ASSERT(std::is_constructible, + verb, string_view, unsigned, Arg1>::value); + + BOOST_STATIC_ASSERT(std::is_constructible, + verb, string_view, unsigned, Arg1&&>::value); + + BOOST_STATIC_ASSERT(std::is_constructible, + verb, string_view, unsigned, Arg1 const>::value); + + BOOST_STATIC_ASSERT(std::is_constructible, + verb, string_view, unsigned, Arg1 const&>::value); + + // 1-arg + fields + BOOST_STATIC_ASSERT(std::is_constructible, + verb, string_view, unsigned, Arg1, fields::allocator_type>::value); + + BOOST_STATIC_ASSERT(std::is_constructible, std::piecewise_construct_t, + std::tuple>::value); + + BOOST_STATIC_ASSERT(std::is_constructible, std::piecewise_construct_t, + std::tuple>::value); + + BOOST_STATIC_ASSERT(std::is_constructible, std::piecewise_construct_t, + std::tuple, std::tuple>::value); + + // special members + BOOST_STATIC_ASSERT(std::is_copy_constructible>::value); + BOOST_STATIC_ASSERT(std::is_move_constructible>::value); + BOOST_STATIC_ASSERT(std::is_copy_assignable>::value); + BOOST_STATIC_ASSERT(std::is_move_assignable>::value); + BOOST_STATIC_ASSERT(std::is_copy_constructible>::value); + BOOST_STATIC_ASSERT(std::is_move_constructible>::value); + BOOST_STATIC_ASSERT(std::is_copy_assignable>::value); + BOOST_STATIC_ASSERT(std::is_move_assignable>::value); + + void + testMessage() { - static_assert(std::is_constructible< - message>::value, ""); - - static_assert(std::is_constructible< - message, Arg1>::value, ""); - - static_assert(std::is_constructible< - message, Arg1 const>::value, ""); - - static_assert(std::is_constructible< - message, Arg1 const&>::value, ""); - - static_assert(std::is_constructible< - message, Arg1&&>::value, ""); - - static_assert(! std::is_constructible< - message>::value, ""); - - static_assert(std::is_constructible< - message, - Arg1, fields::allocator_type>::value, ""); - - static_assert(std::is_constructible< - message, std::piecewise_construct_t, - std::tuple>::value, ""); - - static_assert(std::is_constructible< - message, std::piecewise_construct_t, - std::tuple>::value, ""); - - static_assert(std::is_constructible< - message, std::piecewise_construct_t, - std::tuple, std::tuple>::value, ""); - { Arg1 arg1; - message{std::move(arg1)}; + request{verb::get, "/", 11, std::move(arg1)}; BEAST_EXPECT(arg1.moved); } { - fields h; - h.insert("User-Agent", "test"); - message m{Arg1{}, h}; - BEAST_EXPECT(h["User-Agent"] == "test"); - BEAST_EXPECT(m.fields["User-Agent"] == "test"); + header h; + h.set(field::user_agent, "test"); + BEAST_EXPECT(h[field::user_agent] == "test"); + request m{std::move(h)}; + BEAST_EXPECT(m[field::user_agent] == "test"); + BEAST_EXPECT(h.count(field::user_agent) == 0); } { - fields h; - h.insert("User-Agent", "test"); - message m{Arg1{}, std::move(h)}; - BEAST_EXPECT(! h.exists("User-Agent")); - BEAST_EXPECT(m.fields["User-Agent"] == "test"); + request h{verb::get, "/", 10}; + h.set(field::user_agent, "test"); + request m{std::move(h.base()), Arg1{}}; + BEAST_EXPECT(m["User-Agent"] == "test"); + BEAST_EXPECT(h.count(http::field::user_agent) == 0); + BEAST_EXPECT(m.method() == verb::get); + BEAST_EXPECT(m.target() == "/"); + BEAST_EXPECT(m.version == 10); } // swap - message m1; - message m2; - m1.url = "u"; + request m1; + request m2; + m1.target("u"); m1.body = "1"; - m1.fields.insert("h", "v"); - m2.method = "G"; + m1.insert("h", "v"); + m2.method_string("G"); m2.body = "2"; swap(m1, m2); - BEAST_EXPECT(m1.method == "G"); - BEAST_EXPECT(m2.method.empty()); - BEAST_EXPECT(m1.url.empty()); - BEAST_EXPECT(m2.url == "u"); + BEAST_EXPECT(m1.method_string() == "G"); + BEAST_EXPECT(m2.method_string().empty()); + BEAST_EXPECT(m1.target().empty()); + BEAST_EXPECT(m2.target() == "u"); BEAST_EXPECT(m1.body == "2"); BEAST_EXPECT(m2.body == "1"); - BEAST_EXPECT(! m1.fields.exists("h")); - BEAST_EXPECT(m2.fields.exists("h")); + BEAST_EXPECT(! m1.count("h")); + BEAST_EXPECT(m2.count("h")); } - struct MoveHeaders + struct MoveFields : fields { bool moved_to = false; bool moved_from = false; - MoveHeaders() = default; + MoveFields() = default; - MoveHeaders(MoveHeaders&& other) + MoveFields(MoveFields&& other) : moved_to(true) { other.moved_from = true; } - MoveHeaders& operator=(MoveHeaders&& other) + MoveFields& operator=(MoveFields&&) { return *this; } }; - void testHeaders() + struct token {}; + + struct test_fields + { + std::string target; + + test_fields() = delete; + test_fields(token) {} + string_view get_method_impl() const { return {}; } + string_view get_target_impl() const { return target; } + string_view get_reason_impl() const { return {}; } + bool get_chunked_impl() const { return false; } + bool get_keep_alive_impl(unsigned) const { return true; } + void set_method_impl(string_view) {} + void set_target_impl(string_view s) { target = s.to_string(); } + void set_reason_impl(string_view) {} + void set_chunked_impl(bool) {} + void set_content_length_impl(boost::optional) {} + void set_keep_alive_impl(unsigned, bool) {} + }; + + void + testMessageCtors() { { - using req_type = request_header; - static_assert(std::is_copy_constructible::value, ""); - static_assert(std::is_move_constructible::value, ""); - static_assert(std::is_copy_assignable::value, ""); - static_assert(std::is_move_assignable::value, ""); - - using res_type = response_header; - static_assert(std::is_copy_constructible::value, ""); - static_assert(std::is_move_constructible::value, ""); - static_assert(std::is_copy_assignable::value, ""); - static_assert(std::is_move_assignable::value, ""); + request req; + BEAST_EXPECT(req.version == 11); + BEAST_EXPECT(req.method() == verb::unknown); + BEAST_EXPECT(req.target() == ""); } - { - MoveHeaders h; - header r{std::move(h)}; - BEAST_EXPECT(h.moved_from); - BEAST_EXPECT(r.fields.moved_to); - request m{std::move(r)}; - BEAST_EXPECT(r.fields.moved_from); - BEAST_EXPECT(m.fields.moved_to); + request req{verb::get, "/", 11}; + BEAST_EXPECT(req.version == 11); + BEAST_EXPECT(req.method() == verb::get); + BEAST_EXPECT(req.target() == "/"); + } + { + request req{verb::get, "/", 11, "Hello"}; + BEAST_EXPECT(req.version == 11); + BEAST_EXPECT(req.method() == verb::get); + BEAST_EXPECT(req.target() == "/"); + BEAST_EXPECT(req.body == "Hello"); + } + { + request req{ + verb::get, "/", 11, "Hello", token{}}; + BEAST_EXPECT(req.version == 11); + BEAST_EXPECT(req.method() == verb::get); + BEAST_EXPECT(req.target() == "/"); + BEAST_EXPECT(req.body == "Hello"); + } + { + response res; + BEAST_EXPECT(res.version == 11); + BEAST_EXPECT(res.result() == status::ok); + BEAST_EXPECT(res.reason() == "OK"); + } + { + response res{status::bad_request, 10}; + BEAST_EXPECT(res.version == 10); + BEAST_EXPECT(res.result() == status::bad_request); + BEAST_EXPECT(res.reason() == "Bad Request"); + } + { + response res{status::bad_request, 10, "Hello"}; + BEAST_EXPECT(res.version == 10); + BEAST_EXPECT(res.result() == status::bad_request); + BEAST_EXPECT(res.reason() == "Bad Request"); + BEAST_EXPECT(res.body == "Hello"); + } + { + response res{ + status::bad_request, 10, "Hello", token{}}; + BEAST_EXPECT(res.version == 10); + BEAST_EXPECT(res.result() == status::bad_request); + BEAST_EXPECT(res.reason() == "Bad Request"); + BEAST_EXPECT(res.body == "Hello"); } } - void testFreeFunctions() + void + testSwap() { - { - request m; - m.method = "GET"; - m.url = "/"; - m.version = 11; - m.fields.insert("Upgrade", "test"); - BEAST_EXPECT(! is_upgrade(m)); - - prepare(m, connection::upgrade); - BEAST_EXPECT(is_upgrade(m)); - BEAST_EXPECT(m.fields["Connection"] == "upgrade"); - - m.version = 10; - BEAST_EXPECT(! is_upgrade(m)); - } - } - - void testPrepare() - { - request m; - m.version = 10; - BEAST_EXPECT(! is_upgrade(m)); - m.fields.insert("Transfer-Encoding", "chunked"); - try - { - prepare(m); - fail(); - } - catch(std::exception const&) - { - } - m.fields.erase("Transfer-Encoding"); - m.fields.insert("Content-Length", "0"); - try - { - prepare(m); - fail(); - } - catch(std::exception const&) - { - pass(); - } - m.fields.erase("Content-Length"); - m.fields.insert("Connection", "keep-alive"); - try - { - prepare(m); - fail(); - } - catch(std::exception const&) - { - pass(); - } - m.version = 11; - m.fields.erase("Connection"); - m.fields.insert("Connection", "close"); - BEAST_EXPECT(! is_keep_alive(m)); - } - - void testSwap() - { - message m1; - message m2; - m1.status = 200; + response m1; + response m2; + m1.result(status::ok); m1.version = 10; m1.body = "1"; - m1.fields.insert("h", "v"); - m2.status = 404; - m2.reason = "OK"; + m1.insert("h", "v"); + m2.result(status::not_found); m2.body = "2"; m2.version = 11; swap(m1, m2); - BEAST_EXPECT(m1.status == 404); - BEAST_EXPECT(m2.status == 200); - BEAST_EXPECT(m1.reason == "OK"); - BEAST_EXPECT(m2.reason.empty()); + BEAST_EXPECT(m1.result() == status::not_found); + BEAST_EXPECT(m1.result_int() == 404); + BEAST_EXPECT(m2.result() == status::ok); + BEAST_EXPECT(m2.result_int() == 200); + BEAST_EXPECT(m1.reason() == "Not Found"); + BEAST_EXPECT(m2.reason() == "OK"); BEAST_EXPECT(m1.version == 11); BEAST_EXPECT(m2.version == 10); BEAST_EXPECT(m1.body == "2"); BEAST_EXPECT(m2.body == "1"); - BEAST_EXPECT(! m1.fields.exists("h")); - BEAST_EXPECT(m2.fields.exists("h")); + BEAST_EXPECT(! m1.count("h")); + BEAST_EXPECT(m2.count("h")); } void @@ -291,14 +303,70 @@ public: }(); } - void run() override + void + testMethod() + { + header h; + auto const vcheck = + [&](verb v) + { + h.method(v); + BEAST_EXPECT(h.method() == v); + BEAST_EXPECT(h.method_string() == to_string(v)); + }; + auto const scheck = + [&](string_view s) + { + h.method_string(s); + BEAST_EXPECT(h.method() == string_to_verb(s)); + BEAST_EXPECT(h.method_string() == s); + }; + vcheck(verb::get); + vcheck(verb::head); + scheck("GET"); + scheck("HEAD"); + scheck("XYZ"); + } + + void + testStatus() + { + header h; + h.result(200); + BEAST_EXPECT(h.result_int() == 200); + BEAST_EXPECT(h.result() == status::ok); + h.result(status::switching_protocols); + BEAST_EXPECT(h.result_int() == 101); + BEAST_EXPECT(h.result() == status::switching_protocols); + h.result(1); + BEAST_EXPECT(h.result_int() == 1); + BEAST_EXPECT(h.result() == status::unknown); + } + + void + testReason() + { + header h; + h.result(status::ok); + BEAST_EXPECT(h.reason() == "OK"); + h.reason("Pepe"); + BEAST_EXPECT(h.reason() == "Pepe"); + h.result(status::not_found); + BEAST_EXPECT(h.reason() == "Pepe"); + h.reason({}); + BEAST_EXPECT(h.reason() == "Not Found"); + } + + void + run() override { testMessage(); - testHeaders(); - testFreeFunctions(); - testPrepare(); + testMessageCtors(); testSwap(); testSpecialMembers(); + testMethod(); + testStatus(); + testReason(); } }; diff --git a/src/beast/test/http/message_fuzz.hpp b/src/beast/test/http/message_fuzz.hpp index c0780a08d0..2c1ff25b0b 100644 --- a/src/beast/test/http/message_fuzz.hpp +++ b/src/beast/test/http/message_fuzz.hpp @@ -8,8 +8,7 @@ #ifndef BEAST_HTTP_TEST_MESSAGE_FUZZ_HPP #define BEAST_HTTP_TEST_MESSAGE_FUZZ_HPP -#include -#include +#include #include #include #include @@ -20,7 +19,7 @@ namespace http { template std::string -escaped_string(boost::string_ref const& s) +escaped_string(string_view s) { std::string out; out.reserve(s.size()); @@ -132,7 +131,7 @@ public: "msrp", "msrps", "mtqp", "mumble", "mupdate", "mvn", "news", "nfs", "ni", "nih", "nntp", "notes", "oid", "opaquelocktoken", "pack", "palm", "paparazzi", "pkcs11", "platform", "pop", "pres", "prospero", "proxy", "psyc", "query", - "redis", "rediss", "reload", "res", "resource", "rmi", "rsync", "rtmfp", + "redis", "rediss", "reload", "res", "target", "rmi", "rsync", "rtmfp", "rtmp", "rtsp", "rtsps", "rtspu", "secondlife", "service", "session", "sftp", "sgn", "shttp", "sieve", "sip", "sips", "skype", "smb", "sms", "smtp", "snews", "snmp", "soap.beep", "soap.beeps", "soldat", "spotify", "ssh", @@ -320,7 +319,7 @@ public: } std::string - uri() + target() { //switch(rand(4)) switch(1) @@ -349,7 +348,7 @@ public: #if 0 std::string - uri() + target() { static char constexpr alpha[63] = "0123456789" "ABCDEFGHIJ" "KLMNOPQRST" @@ -477,13 +476,13 @@ public: void fields(DynamicBuffer& db) { + auto os = ostream(db); while(rand(6)) - { - write(db, field()); - write(db, rand(4) ? ": " : ":"); - write(db, value()); - write(db, "\r\n"); - } + os << + field() << + (rand(4) ? ": " : ":") << + value() << + "\r\n"; } template @@ -492,14 +491,16 @@ public: { if(! rand(4)) { - write(db, "Content-Length: 0\r\n\r\n"); + ostream(db) << + "Content-Length: 0\r\n\r\n"; return; } if(rand(2)) { auto const len = rand(500); - write(db, "Content-Length: ", len, "\r\n\r\n"); - for(auto const& b : db.prepare(len)) + ostream(db) << + "Content-Length: " << len << "\r\n\r\n"; + for(boost::asio::mutable_buffer b : db.prepare(len)) { auto p = boost::asio::buffer_cast(b); auto n = boost::asio::buffer_size(b); @@ -511,13 +512,15 @@ public: else { auto len = rand(500); - write(db, "Transfer-Encoding: chunked\r\n\r\n"); + ostream(db) << + "Transfer-Encoding: chunked\r\n\r\n"; while(len > 0) { auto n = (std::min)(1 + rand(300), len); len -= n; - write(db, to_hex(n), "\r\n"); - for(auto const& b : db.prepare(n)) + ostream(db) << + to_hex(n) << "\r\n"; + for(boost::asio::mutable_buffer b : db.prepare(n)) { auto p = boost::asio::buffer_cast(b); auto m = boost::asio::buffer_size(b); @@ -525,9 +528,9 @@ public: *p++ = static_cast(32 + rand(26+26+10+6)); } db.commit(n); - write(db, "\r\n"); + ostream(db) << "\r\n"; } - write(db, "0\r\n\r\n"); + ostream(db) << "0\r\n\r\n"; } } @@ -535,7 +538,8 @@ public: void request(DynamicBuffer& db) { - write(db, method(), " ", uri(), " HTTP/1.1\r\n"); + ostream(db) << + method() << " " << target() << " HTTP/1.1\r\n"; fields(db); body(db); } @@ -544,14 +548,15 @@ public: void response(DynamicBuffer& db) { - write(db, "HTTP/1."); - write(db, rand(2) ? "0" : "1"); - write(db, " ", 100 + rand(401), " "); - write(db, token()); - write(db, "\r\n"); + ostream(db) << + "HTTP/1." << + (rand(2) ? "0" : "1") << " " << + (100 + rand(401)) << " " << + token() << + "\r\n"; fields(db); body(db); - write(db, "\r\n"); + ostream(db) << "\r\n"; } }; diff --git a/src/beast/test/http/parse_error.cpp b/src/beast/test/http/parse_error.cpp deleted file mode 100644 index ea23bc1a1c..0000000000 --- a/src/beast/test/http/parse_error.cpp +++ /dev/null @@ -1,60 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -// Test that header file is self-contained. -#include - -#include -#include - -namespace beast { -namespace http { - -class parse_error_test : public unit_test::suite -{ -public: - void check(char const* name, parse_error ev) - { - auto const ec = make_error_code(ev); - BEAST_EXPECT(std::string{ec.category().name()} == name); - BEAST_EXPECT(! ec.message().empty()); - BEAST_EXPECT(std::addressof(ec.category()) == - std::addressof(detail::get_parse_error_category())); - BEAST_EXPECT(detail::get_parse_error_category().equivalent( - static_cast::type>(ev), - ec.category().default_error_condition( - static_cast::type>(ev)))); - BEAST_EXPECT(detail::get_parse_error_category().equivalent( - ec, static_cast::type>(ev))); - } - - void run() override - { - check("http", parse_error::connection_closed); - check("http", parse_error::bad_method); - check("http", parse_error::bad_uri); - check("http", parse_error::bad_version); - check("http", parse_error::bad_crlf); - check("http", parse_error::bad_status); - check("http", parse_error::bad_reason); - check("http", parse_error::bad_field); - check("http", parse_error::bad_value); - check("http", parse_error::bad_content_length); - check("http", parse_error::illegal_content_length); - check("http", parse_error::invalid_chunk_size); - check("http", parse_error::invalid_ext_name); - check("http", parse_error::invalid_ext_val); - check("http", parse_error::header_too_big); - check("http", parse_error::body_too_big); - check("http", parse_error::short_read); - } -}; - -BEAST_DEFINE_TESTSUITE(parse_error,http,beast); - -} // http -} // beast diff --git a/src/beast/test/http/parser.cpp b/src/beast/test/http/parser.cpp new file mode 100644 index 0000000000..cae9d90457 --- /dev/null +++ b/src/beast/test/http/parser.cpp @@ -0,0 +1,389 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include "test_parser.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +class parser_test + : public beast::unit_test::suite + , public beast::test::enable_yield_to +{ +public: + template + using parser_type = + parser; + + static + boost::asio::const_buffers_1 + buf(string_view s) + { + return {s.data(), s.size()}; + } + + template + static + void + put(ConstBufferSequence const& buffers, + basic_parser& p, + error_code& ec) + { + using boost::asio::buffer_size; + consuming_buffers cb{buffers}; + for(;;) + { + auto const used = p.put(cb, ec); + cb.consume(used); + if(ec) + return; + if(p.need_eof() && + buffer_size(cb) == 0) + { + p.put_eof(ec); + if(ec) + return; + } + if(p.is_done()) + break; + } + } + + template + void + doMatrix(string_view s0, F const& f) + { + using boost::asio::buffer; + // parse a single buffer + { + auto s = s0; + error_code ec; + parser_type p; + put(buffer(s.data(), s.size()), p, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + f(p); + } + // parse two buffers + for(auto n = s0.size() - 1; n >= 1; --n) + { + auto s = s0; + error_code ec; + parser_type p; + p.eager(true); + auto used = + p.put(buffer(s.data(), n), ec); + s.remove_prefix(used); + if(ec == error::need_more) + ec.assign(0, ec.category()); + if(! BEAST_EXPECTS(! ec, ec.message())) + continue; + BEAST_EXPECT(! p.is_done()); + used = p.put( + buffer(s.data(), s.size()), ec); + s.remove_prefix(used); + if(! BEAST_EXPECTS(! ec, ec.message())) + continue; + BEAST_EXPECT(s.empty()); + if(p.need_eof()) + { + p.put_eof(ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + continue; + } + if(BEAST_EXPECT(p.is_done())) + f(p); + } + } + + void + testParse() + { + doMatrix( + "HTTP/1.0 200 OK\r\n" + "Server: test\r\n" + "\r\n" + "Hello, world!", + [&](parser_type const& p) + { + auto const& m = p.get(); + BEAST_EXPECT(! p.is_chunked()); + BEAST_EXPECT(p.need_eof()); + BEAST_EXPECT(p.content_length() == boost::none); + BEAST_EXPECT(m.version == 10); + BEAST_EXPECT(m.result() == status::ok); + BEAST_EXPECT(m.reason() == "OK"); + BEAST_EXPECT(m["Server"] == "test"); + BEAST_EXPECT(m.body == "Hello, world!"); + } + ); + doMatrix( + "HTTP/1.1 200 OK\r\n" + "Server: test\r\n" + "Expect: Expires, MD5-Fingerprint\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "5\r\n" + "*****\r\n" + "2;a;b=1;c=\"2\"\r\n" + "--\r\n" + "0;d;e=3;f=\"4\"\r\n" + "Expires: never\r\n" + "MD5-Fingerprint: -\r\n" + "\r\n", + [&](parser_type const& p) + { + auto const& m = p.get(); + BEAST_EXPECT(! p.need_eof()); + BEAST_EXPECT(p.is_chunked()); + BEAST_EXPECT(p.content_length() == boost::none); + BEAST_EXPECT(m.version == 11); + BEAST_EXPECT(m.result() == status::ok); + BEAST_EXPECT(m.reason() == "OK"); + BEAST_EXPECT(m["Server"] == "test"); + BEAST_EXPECT(m["Transfer-Encoding"] == "chunked"); + BEAST_EXPECT(m["Expires"] == "never"); + BEAST_EXPECT(m["MD5-Fingerprint"] == "-"); + BEAST_EXPECT(m.body == "*****--"); + } + ); + doMatrix( + "HTTP/1.0 200 OK\r\n" + "Server: test\r\n" + "Content-Length: 5\r\n" + "\r\n" + "*****", + [&](parser_type const& p) + { + auto const& m = p.get(); + BEAST_EXPECT(m.body == "*****"); + } + ); + doMatrix( + "GET / HTTP/1.1\r\n" + "User-Agent: test\r\n" + "\r\n", + [&](parser_type const& p) + { + auto const& m = p.get(); + BEAST_EXPECT(m.method() == verb::get); + BEAST_EXPECT(m.target() == "/"); + BEAST_EXPECT(m.version == 11); + BEAST_EXPECT(! p.need_eof()); + BEAST_EXPECT(! p.is_chunked()); + BEAST_EXPECT(p.content_length() == boost::none); + } + ); + doMatrix( + "GET / HTTP/1.1\r\n" + "User-Agent: test\r\n" + "X: \t x \t \r\n" + "\r\n", + [&](parser_type const& p) + { + auto const& m = p.get(); + BEAST_EXPECT(m["X"] == "x"); + } + ); + + // test eager(true) + { + error_code ec; + parser_type p; + p.eager(true); + p.put(buf( + "GET / HTTP/1.1\r\n" + "User-Agent: test\r\n" + "Content-Length: 1\r\n" + "\r\n" + "*") + , ec); + auto const& m = p.get(); + BEAST_EXPECT(! ec); + BEAST_EXPECT(p.is_done()); + BEAST_EXPECT(p.is_header_done()); + BEAST_EXPECT(! p.need_eof()); + BEAST_EXPECT(m.method() == verb::get); + BEAST_EXPECT(m.target() == "/"); + BEAST_EXPECT(m.version == 11); + BEAST_EXPECT(m["User-Agent"] == "test"); + BEAST_EXPECT(m.body == "*"); + } + { + // test partial parsing of final chunk + // parse through the chunk body + error_code ec; + flat_buffer b; + parser_type p; + p.eager(true); + ostream(b) << + "PUT / HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "1\r\n" + "*"; + auto used = p.put(b.data(), ec); + b.consume(used); + BEAST_EXPECT(! ec); + BEAST_EXPECT(! p.is_done()); + BEAST_EXPECT(p.get().body == "*"); + ostream(b) << + "\r\n" + "0;d;e=3;f=\"4\"\r\n" + "Expires: never\r\n" + "MD5-Fingerprint: -\r\n"; + // incomplete parse, missing the final crlf + used = p.put(b.data(), ec); + b.consume(used); + BEAST_EXPECT(ec == error::need_more); + ec.assign(0, ec.category()); + BEAST_EXPECT(! p.is_done()); + ostream(b) << + "\r\n"; // final crlf to end message + used = p.put(b.data(), ec); + b.consume(used); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_done()); + } + // skip body + { + error_code ec; + response_parser p; + p.skip(true); + p.put(buf( + "HTTP/1.1 200 OK\r\n" + "Content-Length: 5\r\n" + "\r\n" + "*****") + , ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_done()); + BEAST_EXPECT(p.is_header_done()); + BEAST_EXPECT(p.content_length() && + *p.content_length() == 5); + } + } + + //-------------------------------------------------------------------------- + + template + void + testNeedMore() + { + error_code ec; + std::size_t used; + { + DynamicBuffer b; + parser_type p; + ostream(b) << + "GET / HTTP/1.1\r\n"; + used = p.put(b.data(), ec); + BEAST_EXPECTS(ec == error::need_more, ec.message()); + b.consume(used); + ec.assign(0, ec.category()); + ostream(b) << + "User-Agent: test\r\n" + "\r\n"; + used = p.put(b.data(), ec); + BEAST_EXPECTS(! ec, ec.message()); + b.consume(used); + BEAST_EXPECT(p.is_done()); + BEAST_EXPECT(p.is_header_done()); + } + } + + void + testGotSome() + { + error_code ec; + parser_type p; + auto used = p.put(buf(""), ec); + BEAST_EXPECT(ec == error::need_more); + BEAST_EXPECT(! p.got_some()); + BEAST_EXPECT(used == 0); + ec.assign(0, ec.category()); + used = p.put(buf("G"), ec); + BEAST_EXPECT(ec == error::need_more); + BEAST_EXPECT(p.got_some()); + BEAST_EXPECT(used == 0); + } + + void + testCallback() + { + { + multi_buffer b; + ostream(b) << + "POST / HTTP/1.1\r\n" + "Content-Length: 2\r\n" + "\r\n" + "**"; + error_code ec; + parser p; + p.eager(true); + p.put(b.data(), ec); + p.on_header( + [this](parser& p, error_code& ec) + { + BEAST_EXPECT(p.is_header_done()); + ec.assign(0, ec.category()); + }); + BEAST_EXPECTS(! ec, ec.message()); + } + { + multi_buffer b; + ostream(b) << + "POST / HTTP/1.1\r\n" + "Content-Length: 2\r\n" + "\r\n" + "**"; + error_code ec; + parser p; + p.eager(true); + p.put(b.data(), ec); + p.on_header( + [this](parser&, error_code& ec) + { + ec.assign(errc::bad_message, + generic_category()); + }); + BEAST_EXPECTS(! ec, ec.message()); + } + } + + void + run() override + { + testParse(); + testNeedMore(); + testNeedMore(); + testGotSome(); + testCallback(); + } +}; + +BEAST_DEFINE_TESTSUITE(parser,http,beast); + +} // http +} // beast + diff --git a/src/beast/test/http/parser_bench.cpp b/src/beast/test/http/parser_bench.cpp deleted file mode 100644 index 768d267d70..0000000000 --- a/src/beast/test/http/parser_bench.cpp +++ /dev/null @@ -1,155 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#include "nodejs_parser.hpp" -#include "message_fuzz.hpp" - -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace http { - -class parser_bench_test : public beast::unit_test::suite -{ -public: - static std::size_t constexpr N = 2000; - - using corpus = std::vector; - - corpus creq_; - corpus cres_; - std::size_t size_ = 0; - - parser_bench_test() - { - creq_ = build_corpus(N/2, std::true_type{}); - cres_ = build_corpus(N/2, std::false_type{}); - } - - corpus - build_corpus(std::size_t n, std::true_type) - { - corpus v; - v.resize(N); - message_fuzz mg; - for(std::size_t i = 0; i < n; ++i) - { - mg.request(v[i]); - size_ += v[i].size(); - } - return v; - } - - corpus - build_corpus(std::size_t n, std::false_type) - { - corpus v; - v.resize(N); - message_fuzz mg; - for(std::size_t i = 0; i < n; ++i) - { - mg.response(v[i]); - size_ += v[i].size(); - } - return v; - } - - template - void - testParser(std::size_t repeat, corpus const& v) - { - while(repeat--) - for(auto const& sb : v) - { - Parser p; - error_code ec; - p.write(sb.data(), ec); - if(! BEAST_EXPECTS(! ec, ec.message())) - log << to_string(sb.data()) << std::endl; - } - } - - template - void - timedTest(std::size_t repeat, std::string const& name, Function&& f) - { - using namespace std::chrono; - using clock_type = std::chrono::high_resolution_clock; - log << name << std::endl; - for(std::size_t trial = 1; trial <= repeat; ++trial) - { - auto const t0 = clock_type::now(); - f(); - auto const elapsed = clock_type::now() - t0; - log << - "Trial " << trial << ": " << - duration_cast(elapsed).count() << " ms" << std::endl; - } - } - - template - struct null_parser : basic_parser_v1> - { - }; - - void - testSpeed() - { - static std::size_t constexpr Trials = 3; - static std::size_t constexpr Repeat = 50; - - log << "sizeof(request parser) == " << - sizeof(basic_parser_v1>) << '\n'; - - log << "sizeof(response parser) == " << - sizeof(basic_parser_v1>)<< '\n'; - - testcase << "Parser speed test, " << - ((Repeat * size_ + 512) / 1024) << "KB in " << - (Repeat * (creq_.size() + cres_.size())) << " messages"; - - timedTest(Trials, "nodejs_parser", - [&] - { - testParser>( - Repeat, creq_); - testParser>( - Repeat, cres_); - }); - timedTest(Trials, "http::basic_parser_v1", - [&] - { - testParser>( - Repeat, creq_); - testParser>( - Repeat, cres_); - }); - pass(); - } - - void run() override - { - pass(); - testSpeed(); - } -}; - -BEAST_DEFINE_TESTSUITE(parser_bench,http,beast); - -} // http -} // beast - diff --git a/src/beast/test/http/parser_v1.cpp b/src/beast/test/http/parser_v1.cpp deleted file mode 100644 index 20c207d7d1..0000000000 --- a/src/beast/test/http/parser_v1.cpp +++ /dev/null @@ -1,160 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -// Test that header file is self-contained. -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace http { - -class parser_v1_test - : public beast::unit_test::suite - , public test::enable_yield_to -{ -public: - void - testParse() - { - using boost::asio::buffer; - { - error_code ec; - parser_v1>> p; - std::string const s = - "GET / HTTP/1.1\r\n" - "User-Agent: test\r\n" - "Content-Length: 1\r\n" - "\r\n" - "*"; - p.write(buffer(s), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(p.complete()); - auto m = p.release(); - BEAST_EXPECT(m.method == "GET"); - BEAST_EXPECT(m.url == "/"); - BEAST_EXPECT(m.version == 11); - BEAST_EXPECT(m.fields["User-Agent"] == "test"); - BEAST_EXPECT(m.body == "*"); - } - { - error_code ec; - parser_v1>> p; - std::string const s = - "HTTP/1.1 200 OK\r\n" - "Server: test\r\n" - "Content-Length: 1\r\n" - "\r\n" - "*"; - p.write(buffer(s), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(p.complete()); - auto m = p.release(); - BEAST_EXPECT(m.status == 200); - BEAST_EXPECT(m.reason == "OK"); - BEAST_EXPECT(m.version == 11); - BEAST_EXPECT(m.fields["Server"] == "test"); - BEAST_EXPECT(m.body == "*"); - } - // skip body - { - error_code ec; - parser_v1 p; - std::string const s = - "HTTP/1.1 200 Connection Established\r\n" - "Proxy-Agent: Zscaler/5.1\r\n" - "\r\n"; - p.set_option(skip_body{true}); - p.write(buffer(s), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(p.complete()); - } - } - - void - testWithBody() - { - std::string const raw = - "GET / HTTP/1.1\r\n" - "User-Agent: test\r\n" - "Content-Length: 1\r\n" - "\r\n" - "*"; - test::string_istream ss{ - ios_, raw, raw.size() - 1}; - - streambuf rb; - header_parser_v1 p0; - parse(ss, rb, p0); - request_header const& reqh = p0.get(); - BEAST_EXPECT(reqh.method == "GET"); - BEAST_EXPECT(reqh.url == "/"); - BEAST_EXPECT(reqh.version == 11); - BEAST_EXPECT(reqh.fields["User-Agent"] == "test"); - BEAST_EXPECT(reqh.fields["Content-Length"] == "1"); - parser_v1 p = - with_body(p0); - BEAST_EXPECT(p.get().method == "GET"); - BEAST_EXPECT(p.get().url == "/"); - BEAST_EXPECT(p.get().version == 11); - BEAST_EXPECT(p.get().fields["User-Agent"] == "test"); - BEAST_EXPECT(p.get().fields["Content-Length"] == "1"); - parse(ss, rb, p); - request req = p.release(); - BEAST_EXPECT(req.body == "*"); - } - - void - testRegressions() - { - using boost::asio::buffer; - - // consecutive empty header values - { - error_code ec; - parser_v1 p; - std::string const s = - "GET / HTTP/1.1\r\n" - "X1:\r\n" - "X2:\r\n" - "X3:x\r\n" - "\r\n"; - p.write(buffer(s), ec); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - BEAST_EXPECT(p.complete()); - auto const msg = p.release(); - BEAST_EXPECT(msg.fields.exists("X1")); - BEAST_EXPECT(msg.fields["X1"] == ""); - BEAST_EXPECT(msg.fields.exists("X2")); - BEAST_EXPECT(msg.fields["X2"] == ""); - BEAST_EXPECT(msg.fields.exists("X3")); - BEAST_EXPECT(msg.fields["X3"] == "x"); - } - } - - void run() override - { - testParse(); - testWithBody(); - testRegressions(); - } -}; - -BEAST_DEFINE_TESTSUITE(parser_v1,http,beast); - -} // http -} // beast diff --git a/src/beast/test/http/read.cpp b/src/beast/test/http/read.cpp index 54356d4f06..3e28ea5083 100644 --- a/src/beast/test/http/read.cpp +++ b/src/beast/test/http/read.cpp @@ -8,11 +8,16 @@ // Test that header file is self-contained. #include -#include "fail_parser.hpp" +#include "test_parser.hpp" +#include +#include #include -#include +#include +#include +#include #include +#include #include #include #include @@ -27,62 +32,9 @@ class read_test , public test::enable_yield_to { public: - struct fail_body - { - class reader; - - class value_type - { - friend class reader; - - std::string s_; - test::fail_counter& fc_; - - public: - explicit - value_type(test::fail_counter& fc) - : fc_(fc) - { - } - - value_type& - operator=(std::string s) - { - s_ = std::move(s); - return *this; - } - }; - - class reader - { - value_type& body_; - - public: - template - explicit - reader(message& msg) noexcept - : body_(msg.body) - { - } - - void - init(error_code& ec) noexcept - { - body_.fc_.fail(ec); - } - - void - write(void const* data, - std::size_t size, error_code& ec) noexcept - { - if(body_.fc_.fail(ec)) - return; - } - }; - }; - template - void failMatrix(char const* s, yield_context do_yield) + void + failMatrix(char const* s, yield_context do_yield) { using boost::asio::buffer; using boost::asio::buffer_copy; @@ -91,15 +43,15 @@ public: auto const len = strlen(s); for(n = 0; n < limit; ++n) { - streambuf sb; - sb.commit(buffer_copy( - sb.prepare(len), buffer(s, len))); + multi_buffer b; + b.commit(buffer_copy( + b.prepare(len), buffer(s, len))); test::fail_counter fc(n); test::fail_stream< test::string_istream> fs{fc, ios_, ""}; - fail_parser p(fc); - error_code ec; - parse(fs, sb, p, ec); + test_parser p(fc); + error_code ec = test::error::fail_error; + read(fs, b, p, ec); if(! ec) break; } @@ -107,30 +59,30 @@ public: for(n = 0; n < limit; ++n) { static std::size_t constexpr pre = 10; - streambuf sb; - sb.commit(buffer_copy( - sb.prepare(pre), buffer(s, pre))); + multi_buffer b; + b.commit(buffer_copy( + b.prepare(pre), buffer(s, pre))); test::fail_counter fc(n); test::fail_stream fs{ fc, ios_, std::string{s + pre, len - pre}}; - fail_parser p(fc); - error_code ec; - parse(fs, sb, p, ec); + test_parser p(fc); + error_code ec = test::error::fail_error; + read(fs, b, p, ec); if(! ec) break; } BEAST_EXPECT(n < limit); for(n = 0; n < limit; ++n) { - streambuf sb; - sb.commit(buffer_copy( - sb.prepare(len), buffer(s, len))); + multi_buffer b; + b.commit(buffer_copy( + b.prepare(len), buffer(s, len))); test::fail_counter fc(n); test::fail_stream< test::string_istream> fs{fc, ios_, ""}; - fail_parser p(fc); - error_code ec; - async_parse(fs, sb, p, do_yield[ec]); + test_parser p(fc); + error_code ec = test::error::fail_error; + async_read(fs, b, p, do_yield[ec]); if(! ec) break; } @@ -138,29 +90,15 @@ public: for(n = 0; n < limit; ++n) { static std::size_t constexpr pre = 10; - streambuf sb; - sb.commit(buffer_copy( - sb.prepare(pre), buffer(s, pre))); + multi_buffer b; + b.commit(buffer_copy( + b.prepare(pre), buffer(s, pre))); test::fail_counter fc(n); test::fail_stream fs{ fc, ios_, std::string{s + pre, len - pre}}; - fail_parser p(fc); - error_code ec; - async_parse(fs, sb, p, do_yield[ec]); - if(! ec) - break; - } - BEAST_EXPECT(n < limit); - for(n = 0; n < limit; ++n) - { - streambuf sb; - sb.commit(buffer_copy( - sb.prepare(len), buffer(s, len))); - test::fail_counter fc{n}; - test::string_istream ss{ios_, s}; - parser_v1 p{fc}; - error_code ec; - parse(ss, sb, p, ec); + test_parser p(fc); + error_code ec = test::error::fail_error; + async_read(fs, b, p, do_yield[ec]); if(! ec) break; } @@ -171,10 +109,10 @@ public: { try { - streambuf sb; + multi_buffer b; test::string_istream ss(ios_, "GET / X"); - parser_v1 p; - parse(ss, sb, p); + request_parser p; + read(ss, b, p); fail(); } catch(std::exception const&) @@ -183,6 +121,52 @@ public: } } + void + testBufferOverflow() + { + { + test::pipe p{ios_}; + ostream(p.server.buffer) << + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "User-Agent: test\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "10\r\n" + "****************\r\n" + "0\r\n\r\n"; + static_buffer_n<1024> b; + request req; + try + { + read(p.server, b, req); + pass(); + } + catch(std::exception const& e) + { + fail(e.what(), __FILE__, __LINE__); + } + } + { + test::pipe p{ios_}; + ostream(p.server.buffer) << + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "User-Agent: test\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "10\r\n" + "****************\r\n" + "0\r\n\r\n"; + error_code ec = test::error::fail_error; + static_buffer_n<10> b; + request req; + read(p.server, b, req, ec); + BEAST_EXPECTS(ec == error::buffer_overflow, + ec.message()); + } + } + void testFailures(yield_context do_yield) { char const* req[] = { @@ -245,52 +229,6 @@ public: failMatrix(res[i], do_yield); } - void testReadHeaders(yield_context do_yield) - { - static std::size_t constexpr limit = 100; - std::size_t n; - - for(n = 0; n < limit; ++n) - { - test::fail_stream fs{n, ios_, - "GET / HTTP/1.1\r\n" - "Host: localhost\r\n" - "User-Agent: test\r\n" - "Content-Length: 5\r\n" - "\r\n" - }; - request_header m; - try - { - streambuf sb; - read(fs, sb, m); - break; - } - catch(std::exception const&) - { - } - } - BEAST_EXPECT(n < limit); - - for(n = 0; n < limit; ++n) - { - test::fail_stream fs(n, ios_, - "GET / HTTP/1.1\r\n" - "Host: localhost\r\n" - "User-Agent: test\r\n" - "Content-Length: 0\r\n" - "\r\n" - ); - request_header m; - error_code ec; - streambuf sb; - async_read(fs, sb, m, do_yield[ec]); - if(! ec) - break; - } - BEAST_EXPECT(n < limit); - } - void testRead(yield_context do_yield) { static std::size_t constexpr limit = 100; @@ -305,11 +243,11 @@ public: "Content-Length: 0\r\n" "\r\n" ); - request m; + request m; try { - streambuf sb; - read(fs, sb, m); + multi_buffer b; + read(fs, b, m); break; } catch(std::exception const&) @@ -327,10 +265,10 @@ public: "Content-Length: 0\r\n" "\r\n" ); - request m; - error_code ec; - streambuf sb; - read(fs, sb, m, ec); + request m; + error_code ec = test::error::fail_error; + multi_buffer b; + read(fs, b, m, ec); if(! ec) break; } @@ -345,33 +283,34 @@ public: "Content-Length: 0\r\n" "\r\n" ); - request m; - error_code ec; - streambuf sb; - async_read(fs, sb, m, do_yield[ec]); + request m; + error_code ec = test::error::fail_error; + multi_buffer b; + async_read(fs, b, m, do_yield[ec]); if(! ec) break; } BEAST_EXPECT(n < limit); } - void testEof(yield_context do_yield) + void + testEof(yield_context do_yield) { { - streambuf sb; + multi_buffer b; test::string_istream ss(ios_, ""); - parser_v1 p; + request_parser p; error_code ec; - parse(ss, sb, p, ec); - BEAST_EXPECT(ec == boost::asio::error::eof); + read(ss, b, p, ec); + BEAST_EXPECT(ec == http::error::end_of_stream); } { - streambuf sb; + multi_buffer b; test::string_istream ss(ios_, ""); - parser_v1 p; + request_parser p; error_code ec; - async_parse(ss, sb, p, do_yield[ec]); - BEAST_EXPECT(ec == boost::asio::error::eof); + async_read(ss, b, p, do_yield[ec]); + BEAST_EXPECT(ec == http::error::end_of_stream); } } @@ -396,9 +335,9 @@ public: test::string_istream is{ios, "GET / HTTP/1.1\r\n\r\n"}; BEAST_EXPECT(handler::count() == 0); - streambuf sb; - message m; - async_read(is, sb, m, handler{}); + multi_buffer b; + request m; + async_read(is, b, m, handler{}); BEAST_EXPECT(handler::count() > 0); ios.stop(); BEAST_EXPECT(handler::count() > 0); @@ -415,25 +354,107 @@ public: test::string_istream is{ios, "GET / HTTP/1.1\r\n\r\n"}; BEAST_EXPECT(handler::count() == 0); - streambuf sb; - message m; - async_read(is, sb, m, handler{}); + multi_buffer b; + request m; + async_read(is, b, m, handler{}); BEAST_EXPECT(handler::count() > 0); } BEAST_EXPECT(handler::count() == 0); } } - void run() override + // https://github.com/vinniefalco/Beast/issues/430 + void + testRegression430() + { + test::pipe c{ios_}; + c.server.read_size(1); + ostream(c.server.buffer) << + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "Content-Type: application/octet-stream\r\n" + "\r\n" + "4\r\nabcd\r\n" + "0\r\n\r\n"; + error_code ec; + flat_buffer fb; + parser p; + read(c.server, fb, p, ec); + BEAST_EXPECTS(! ec, ec.message()); + } + + //-------------------------------------------------------------------------- + + template + void + readgrind(string_view s, Pred&& pred) + { + using boost::asio::buffer; + for(std::size_t n = 1; n < s.size() - 1; ++n) + { + Parser p; + error_code ec = test::error::fail_error; + flat_buffer b; + test::pipe c{ios_}; + ostream(c.server.buffer) << s; + c.server.read_size(n); + read(c.server, b, p, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + continue; + pred(p); + } + } + + void + testReadGrind() + { + readgrind>( + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "Content-Type: application/octet-stream\r\n" + "\r\n" + "4\r\nabcd\r\n" + "0\r\n\r\n" + ,[&](test_parser const& p) + { + BEAST_EXPECT(p.body == "abcd"); + }); + readgrind>( + "HTTP/1.1 200 OK\r\n" + "Server: test\r\n" + "Expect: Expires, MD5-Fingerprint\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "5\r\n" + "*****\r\n" + "2;a;b=1;c=\"2\"\r\n" + "--\r\n" + "0;d;e=3;f=\"4\"\r\n" + "Expires: never\r\n" + "MD5-Fingerprint: -\r\n" + "\r\n" + ,[&](test_parser const& p) + { + BEAST_EXPECT(p.body == "*****--"); + }); + } + + void + run() override { testThrow(); + testBufferOverflow(); - yield_to(&read_test::testFailures, this); - yield_to(&read_test::testReadHeaders, this); - yield_to(&read_test::testRead, this); - yield_to(&read_test::testEof, this); + yield_to([&](yield_context yield){ + testFailures(yield); }); + yield_to([&](yield_context yield){ + testRead(yield); }); + yield_to([&](yield_context yield){ + testEof(yield); }); testIoService(); + testRegression430(); + testReadGrind(); } }; diff --git a/src/beast/test/http/reason.cpp b/src/beast/test/http/reason.cpp deleted file mode 100644 index c5987d3590..0000000000 --- a/src/beast/test/http/reason.cpp +++ /dev/null @@ -1,29 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -// Test that header file is self-contained. -#include - -#include - -namespace beast { -namespace http { - -class reason_test : public unit_test::suite -{ -public: - void run() override - { - for(int i = 1; i <= 999; ++i) - BEAST_EXPECT(reason_string(i) != nullptr); - } -}; - -BEAST_DEFINE_TESTSUITE(reason,http,beast); - -} // http -} // beast diff --git a/src/beast/test/http/rfc7230.cpp b/src/beast/test/http/rfc7230.cpp index 1e486bfa5f..dfa5b068f4 100644 --- a/src/beast/test/http/rfc7230.cpp +++ b/src/beast/test/http/rfc7230.cpp @@ -13,9 +13,10 @@ #include #include +#include + namespace beast { namespace http { -namespace test { class rfc7230_test : public beast::unit_test::suite { @@ -29,7 +30,7 @@ public: static std::string - str(boost::string_ref const& s) + str(string_view s) { return std::string(s.data(), s.size()); } @@ -62,18 +63,18 @@ public: BEAST_EXPECTS(got == s, fmt(got)); }; auto const cs = - [&](std::string const& s, std::string const& good) + [&](std::string const& s, std::string const& answer) { - ce(good); + ce(answer); auto const got = str(param_list{s}); ce(got); - BEAST_EXPECTS(got == good, fmt(got)); + BEAST_EXPECTS(got == answer, fmt(got)); }; auto const cq = - [&](std::string const& s, std::string const& good) + [&](std::string const& s, std::string const& answer) { auto const got = str(param_list{s}); - BEAST_EXPECTS(got == good, fmt(got)); + BEAST_EXPECTS(got == answer, fmt(got)); }; ce(""); @@ -135,9 +136,9 @@ public: BEAST_EXPECTS(got == good, fmt(got)); }; /* - ext-list = *( "," OWS ) ext *( OWS "," [ OWS ext ] ) - ext = token param-list - param-list = *( OWS ";" OWS param ) + ext-basic_parsed_list = *( "," OWS ) ext *( OWS "," [ OWS ext ] ) + ext = token param-basic_parsed_list + param-basic_parsed_list = *( OWS ";" OWS param ) param = token OWS "=" OWS ( token / quoted-string ) */ cs(",", ""); @@ -237,17 +238,120 @@ public: cs("x y", "x"); } + template + static + std::vector + to_vector(string_view in) + { + std::vector v; + detail::basic_parsed_list list{in}; + for(auto const& s : + detail::basic_parsed_list{in}) + v.emplace_back(s.data(), s.size()); + return v; + } + + template + void + validate(string_view in, + std::vector const& v) + { + BEAST_EXPECT(to_vector(in) == v); + } + + template + void + good(string_view in) + { + BEAST_EXPECT(validate_list( + detail::basic_parsed_list{in})); + } + + template + void + good(string_view in, + std::vector const& v) + { + BEAST_EXPECT(validate_list( + detail::basic_parsed_list{in})); + validate(in, v); + } + + template + void + bad(string_view in) + { + BEAST_EXPECT(! validate_list( + detail::basic_parsed_list{in})); + } + + void + testOptTokenList() + { + /* + #token = [ ( "," / token ) *( OWS "," [ OWS token ] ) ] + */ + using type = detail::opt_token_list_policy; + + good("", {}); + good(" ", {}); + good("\t", {}); + good(" \t", {}); + good(",", {}); + good(",,", {}); + good(", ,", {}); + good(",\t,", {}); + good(", \t,", {}); + good(", \t, ", {}); + good(", \t,\t", {}); + good(", \t, \t", {}); + + good("x", {"x"}); + good(" x", {"x"}); + good("x,,", {"x"}); + good("x, ,", {"x"}); + good("x,, ", {"x"}); + good("x,,,", {"x"}); + + good("x,y", {"x","y"}); + good("x ,y", {"x","y"}); + good("x\t,y", {"x","y"}); + good("x \t,y", {"x","y"}); + good(" x,y", {"x","y"}); + good(" x,y ", {"x","y"}); + good(",x,y", {"x","y"}); + good("x,y,", {"x","y"}); + good(",,x,y", {"x","y"}); + good(",x,,y", {"x","y"}); + good(",x,y,", {"x","y"}); + good("x ,, y", {"x","y"}); + good("x , ,y", {"x","y"}); + + good("x,y,z", {"x","y","z"}); + + bad("("); + bad("x("); + bad("(x"); + bad(",("); + bad("(,"); + bad("x,("); + bad("(,x"); + bad("x y"); + } + void run() { + testOptTokenList(); +#if 0 testParamList(); testExtList(); testTokenList(); +#endif } }; BEAST_DEFINE_TESTSUITE(rfc7230,http,beast); -} // test } // http } // beast diff --git a/src/beast/test/http/serializer.cpp b/src/beast/test/http/serializer.cpp new file mode 100644 index 0000000000..d39dbcb50e --- /dev/null +++ b/src/beast/test/http/serializer.cpp @@ -0,0 +1,125 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include +#include + +namespace beast { +namespace http { + +class serializer_test : public beast::unit_test::suite +{ +public: + struct const_body + { + struct value_type{}; + + struct reader + { + using const_buffers_type = + boost::asio::const_buffers_1; + + template + reader(message const&); + + void + init(error_code& ec); + + boost::optional> + get(error_code&); + }; + }; + + struct mutable_body + { + struct value_type{}; + + struct reader + { + using const_buffers_type = + boost::asio::const_buffers_1; + + template + reader(message&); + + void + init(error_code& ec); + + boost::optional> + get(error_code&); + }; + }; + + BOOST_STATIC_ASSERT(std::is_const< serializer< + true, const_body>::value_type>::value); + + BOOST_STATIC_ASSERT(! std::is_const::value_type>::value); + + BOOST_STATIC_ASSERT(std::is_constructible< + serializer, + message &>::value); + + BOOST_STATIC_ASSERT(std::is_constructible< + serializer, + message const&>::value); + + BOOST_STATIC_ASSERT(std::is_constructible< + serializer, + message &>::value); + + BOOST_STATIC_ASSERT(! std::is_constructible< + serializer, + message const&>::value); + + struct lambda + { + std::size_t size; + + template + void + operator()(error_code&, + ConstBufferSequence const& buffers) + { + size = boost::asio::buffer_size(buffers); + } + }; + + void + testWriteLimit() + { + auto const limit = 30; + lambda visit; + error_code ec; + response res; + res.body.append(1000, '*'); + serializer sr{res}; + sr.limit(limit); + for(;;) + { + sr.next(ec, visit); + BEAST_EXPECT(visit.size <= limit); + sr.consume(visit.size); + if(sr.is_done()) + break; + } + } + + void + run() override + { + testWriteLimit(); + } +}; + +BEAST_DEFINE_TESTSUITE(serializer,http,beast); + +} // http +} // beast diff --git a/src/beast/test/http/span_body.cpp b/src/beast/test/http/span_body.cpp new file mode 100644 index 0000000000..3e9e3b178f --- /dev/null +++ b/src/beast/test/http/span_body.cpp @@ -0,0 +1,76 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include +#include + +namespace beast { +namespace http { + +struct span_body_test + : public beast::unit_test::suite +{ + void + testSpanBody() + { + { + using B = span_body; + request req; + + BEAST_EXPECT(req.body.size() == 0); + BEAST_EXPECT(B::size(req.body) == 0); + + req.body = B::value_type("xyz", 3); + BEAST_EXPECT(req.body.size() == 3); + BEAST_EXPECT(B::size(req.body) == 3); + + B::reader r{req}; + error_code ec; + r.init(ec); + BEAST_EXPECTS(! ec, ec.message()); + auto const buf = r.get(ec); + BEAST_EXPECTS(! ec, ec.message()); + if(! BEAST_EXPECT(buf != boost::none)) + return; + BEAST_EXPECT(boost::asio::buffer_size(buf->first) == 3); + BEAST_EXPECT(! buf->second); + } + { + char buf[5]; + using B = span_body; + request req; + req.body = span{buf, sizeof(buf)}; + B::writer w{req}; + error_code ec; + w.init(boost::none, ec); + BEAST_EXPECTS(! ec, ec.message()); + w.put(boost::asio::const_buffers_1{ + "123", 3}, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(buf[0] == '1'); + BEAST_EXPECT(buf[1] == '2'); + BEAST_EXPECT(buf[2] == '3'); + w.put(boost::asio::const_buffers_1{ + "456", 3}, ec); + BEAST_EXPECTS(ec == error::buffer_overflow, ec.message()); + } + } + + void + run() override + { + testSpanBody(); + } +}; + +BEAST_DEFINE_TESTSUITE(span_body,http,beast); + +} // http +} // beast diff --git a/src/beast/test/http/status.cpp b/src/beast/test/http/status.cpp new file mode 100644 index 0000000000..69cdcbffa6 --- /dev/null +++ b/src/beast/test/http/status.cpp @@ -0,0 +1,176 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include + +namespace beast { +namespace http { + +class status_test + : public beast::unit_test::suite +{ +public: + void + testStatus() + { + auto const check = [&](status s, int i, status_class sc) + { + BEAST_EXPECT(int_to_status(i) == s); + BEAST_EXPECT(to_status_class(i) == sc); + BEAST_EXPECT(to_status_class(int_to_status(i)) == sc); + }; + check(status::continue_ ,100, status_class::informational); + check(status::switching_protocols ,101, status_class::informational); + check(status::processing ,102, status_class::informational); + + check(status::ok ,200, status_class::successful); + check(status::created ,201, status_class::successful); + check(status::accepted ,202, status_class::successful); + check(status::non_authoritative_information ,203, status_class::successful); + check(status::no_content ,204, status_class::successful); + check(status::reset_content ,205, status_class::successful); + check(status::partial_content ,206, status_class::successful); + check(status::multi_status ,207, status_class::successful); + check(status::already_reported ,208, status_class::successful); + check(status::im_used ,226, status_class::successful); + + check(status::multiple_choices ,300, status_class::redirection); + check(status::moved_permanently ,301, status_class::redirection); + check(status::found ,302, status_class::redirection); + check(status::see_other ,303, status_class::redirection); + check(status::not_modified ,304, status_class::redirection); + check(status::use_proxy ,305, status_class::redirection); + check(status::temporary_redirect ,307, status_class::redirection); + check(status::permanent_redirect ,308, status_class::redirection); + + check(status::bad_request ,400, status_class::client_error); + check(status::unauthorized ,401, status_class::client_error); + check(status::payment_required ,402, status_class::client_error); + check(status::forbidden ,403, status_class::client_error); + check(status::not_found ,404, status_class::client_error); + check(status::method_not_allowed ,405, status_class::client_error); + check(status::not_acceptable ,406, status_class::client_error); + check(status::proxy_authentication_required ,407, status_class::client_error); + check(status::request_timeout ,408, status_class::client_error); + check(status::conflict ,409, status_class::client_error); + check(status::gone ,410, status_class::client_error); + check(status::length_required ,411, status_class::client_error); + check(status::precondition_failed ,412, status_class::client_error); + check(status::payload_too_large ,413, status_class::client_error); + check(status::uri_too_long ,414, status_class::client_error); + check(status::unsupported_media_type ,415, status_class::client_error); + check(status::range_not_satisfiable ,416, status_class::client_error); + check(status::expectation_failed ,417, status_class::client_error); + check(status::misdirected_request ,421, status_class::client_error); + check(status::unprocessable_entity ,422, status_class::client_error); + check(status::locked ,423, status_class::client_error); + check(status::failed_dependency ,424, status_class::client_error); + check(status::upgrade_required ,426, status_class::client_error); + check(status::precondition_required ,428, status_class::client_error); + check(status::too_many_requests ,429, status_class::client_error); + check(status::request_header_fields_too_large ,431, status_class::client_error); + check(status::connection_closed_without_response ,444, status_class::client_error); + check(status::unavailable_for_legal_reasons ,451, status_class::client_error); + check(status::client_closed_request ,499, status_class::client_error); + + check(status::internal_server_error ,500, status_class::server_error); + check(status::not_implemented ,501, status_class::server_error); + check(status::bad_gateway ,502, status_class::server_error); + check(status::service_unavailable ,503, status_class::server_error); + check(status::gateway_timeout ,504, status_class::server_error); + check(status::http_version_not_supported ,505, status_class::server_error); + check(status::variant_also_negotiates ,506, status_class::server_error); + check(status::insufficient_storage ,507, status_class::server_error); + check(status::loop_detected ,508, status_class::server_error); + check(status::not_extended ,510, status_class::server_error); + check(status::network_authentication_required ,511, status_class::server_error); + check(status::network_connect_timeout_error ,599, status_class::server_error); + + BEAST_EXPECT(to_status_class(1) == status_class::unknown); + BEAST_EXPECT(to_status_class(status::unknown) == status_class::unknown); + + auto const good = + [&](status v) + { + BEAST_EXPECT(obsolete_reason(v) != "Unknown Status"); + }; + good(status::continue_); + good(status::switching_protocols); + good(status::processing); + good(status::ok); + good(status::created); + good(status::accepted); + good(status::non_authoritative_information); + good(status::no_content); + good(status::reset_content); + good(status::partial_content); + good(status::multi_status); + good(status::already_reported); + good(status::im_used); + good(status::multiple_choices); + good(status::moved_permanently); + good(status::found); + good(status::see_other); + good(status::not_modified); + good(status::use_proxy); + good(status::temporary_redirect); + good(status::permanent_redirect); + good(status::bad_request); + good(status::unauthorized); + good(status::payment_required); + good(status::forbidden); + good(status::not_found); + good(status::method_not_allowed); + good(status::not_acceptable); + good(status::proxy_authentication_required); + good(status::request_timeout); + good(status::conflict); + good(status::gone); + good(status::length_required); + good(status::precondition_failed); + good(status::payload_too_large); + good(status::uri_too_long); + good(status::unsupported_media_type); + good(status::range_not_satisfiable); + good(status::expectation_failed); + good(status::misdirected_request); + good(status::unprocessable_entity); + good(status::locked); + good(status::failed_dependency); + good(status::upgrade_required); + good(status::precondition_required); + good(status::too_many_requests); + good(status::request_header_fields_too_large); + good(status::unavailable_for_legal_reasons); + good(status::internal_server_error); + good(status::not_implemented); + good(status::bad_gateway); + good(status::service_unavailable); + good(status::gateway_timeout); + good(status::http_version_not_supported); + good(status::variant_also_negotiates); + good(status::insufficient_storage); + good(status::loop_detected); + good(status::not_extended); + good(status::network_authentication_required); + } + + void + run() + { + testStatus(); + } +}; + +BEAST_DEFINE_TESTSUITE(status,http,beast); + +} // http +} // beast + diff --git a/src/beast/test/http/string_body.cpp b/src/beast/test/http/string_body.cpp index 4b84a939be..71e9dea274 100644 --- a/src/beast/test/http/string_body.cpp +++ b/src/beast/test/http/string_body.cpp @@ -7,3 +7,13 @@ // Test that header file is self-contained. #include + +namespace beast { +namespace http { + +BOOST_STATIC_ASSERT(is_body::value); +BOOST_STATIC_ASSERT(is_body_reader::value); +BOOST_STATIC_ASSERT(is_body_writer::value); + +} // http +} // beast diff --git a/src/beast/test/http/test_parser.hpp b/src/beast/test/http/test_parser.hpp new file mode 100644 index 0000000000..7a17ee9f86 --- /dev/null +++ b/src/beast/test/http/test_parser.hpp @@ -0,0 +1,158 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_TEST_PARSER_HPP +#define BEAST_HTTP_TEST_PARSER_HPP + +#include +#include +#include +#include + +namespace beast { +namespace http { + +template +class test_parser + : public basic_parser> +{ + test::fail_counter* fc_ = nullptr; + +public: + using mutable_buffers_type = + boost::asio::mutable_buffers_1; + + int status = 0; + int version = 0; + std::string method; + std::string path; + std::string reason; + std::string body; + int got_on_begin = 0; + int got_on_field = 0; + int got_on_header = 0; + int got_on_body = 0; + int got_content_length = 0; + int got_on_chunk = 0; + int got_on_complete = 0; + std::unordered_map< + std::string, std::string> fields; + + test_parser() = default; + + explicit + test_parser(test::fail_counter& fc) + : fc_(&fc) + { + } + + void + on_request(verb, string_view method_str_, + string_view path_, int version_, error_code& ec) + { + method = std::string( + method_str_.data(), method_str_.size()); + path = std::string( + path_.data(), path_.size()); + version = version_; + ++got_on_begin; + if(fc_) + fc_->fail(ec); + else + ec.assign(0, ec.category()); + } + + void + on_response(int code, + string_view reason_, + int version_, error_code& ec) + { + status = code; + reason = std::string( + reason_.data(), reason_.size()); + version = version_; + ++got_on_begin; + if(fc_) + fc_->fail(ec); + else + ec.assign(0, ec.category()); + } + + void + on_field(field, string_view name, + string_view value, error_code& ec) + { + ++got_on_field; + if(fc_) + fc_->fail(ec); + else + ec.assign(0, ec.category()); + fields[name.to_string()] = value.to_string(); + } + + void + on_header(error_code& ec) + { + ++got_on_header; + if(fc_) + fc_->fail(ec); + else + ec.assign(0, ec.category()); + } + + void + on_body(boost::optional< + std::uint64_t> const& content_length_, + error_code& ec) + { + ++got_on_body; + got_content_length = + static_cast(content_length_); + if(fc_) + fc_->fail(ec); + else + ec.assign(0, ec.category()); + } + + std::size_t + on_data(string_view s, + error_code& ec) + { + body.append(s.data(), s.size()); + if(fc_) + fc_->fail(ec); + else + ec.assign(0, ec.category()); + return s.size(); + } + + void + on_chunk(std::uint64_t, + string_view, error_code& ec) + { + ++got_on_chunk; + if(fc_) + fc_->fail(ec); + else + ec.assign(0, ec.category()); + } + + void + on_complete(error_code& ec) + { + ++got_on_complete; + if(fc_) + fc_->fail(ec); + else + ec.assign(0, ec.category()); + } +}; + +} // http +} // beast + +#endif diff --git a/src/beast/test/http/type_traits.cpp b/src/beast/test/http/type_traits.cpp new file mode 100644 index 0000000000..a461c5b0c2 --- /dev/null +++ b/src/beast/test/http/type_traits.cpp @@ -0,0 +1,32 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include +#include + +namespace beast { +namespace http { + +BOOST_STATIC_ASSERT(! is_body_reader::value); + +BOOST_STATIC_ASSERT(is_body_reader::value); + +BOOST_STATIC_ASSERT(! is_body_writer::value); + +namespace { + +struct not_fields {}; + +} // (anonymous) + +BOOST_STATIC_ASSERT(! is_fields::value); + +} // http +} // beast diff --git a/src/beast/test/core/handler_concepts.cpp b/src/beast/test/http/vector_body.cpp similarity index 56% rename from src/beast/test/core/handler_concepts.cpp rename to src/beast/test/http/vector_body.cpp index ba3c3a5559..91252137ec 100644 --- a/src/beast/test/core/handler_concepts.cpp +++ b/src/beast/test/http/vector_body.cpp @@ -6,18 +6,14 @@ // // Test that header file is self-contained. -#include +#include namespace beast { +namespace http { -namespace { -struct T -{ - void operator()(int); -}; -} - -static_assert(is_CompletionHandler::value, ""); -static_assert(! is_CompletionHandler::value, ""); +BOOST_STATIC_ASSERT(is_body>::value); +BOOST_STATIC_ASSERT(is_body_reader>::value); +BOOST_STATIC_ASSERT(is_body_writer>::value); +} // http } // beast diff --git a/src/beast/test/http/verb.cpp b/src/beast/test/http/verb.cpp new file mode 100644 index 0000000000..ec90033d98 --- /dev/null +++ b/src/beast/test/http/verb.cpp @@ -0,0 +1,128 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include + +namespace beast { +namespace http { + +class verb_test + : public beast::unit_test::suite +{ +public: + void + testVerb() + { + auto const good = + [&](verb v) + { + BEAST_EXPECT(string_to_verb(to_string(v)) == v); + }; + + good(verb::unknown); + + good(verb::delete_); + good(verb::get); + good(verb::head); + good(verb::post); + good(verb::put); + good(verb::connect); + good(verb::options); + good(verb::trace); + good(verb::copy); + good(verb::lock); + good(verb::mkcol); + good(verb::move); + good(verb::propfind); + good(verb::proppatch); + good(verb::search); + good(verb::unlock); + good(verb::bind); + good(verb::rebind); + good(verb::unbind); + good(verb::acl); + good(verb::report); + good(verb::mkactivity); + good(verb::checkout); + good(verb::merge); + good(verb::msearch); + good(verb::notify); + good(verb::subscribe); + good(verb::unsubscribe); + good(verb::patch); + good(verb::purge); + good(verb::mkcalendar); + good(verb::link); + good(verb::unlink); + + auto const bad = + [&](string_view s) + { + auto const v = string_to_verb(s); + BEAST_EXPECTS(v == verb::unknown, to_string(v)); + }; + + bad("AC_"); + bad("BIN_"); + bad("CHECKOU_"); + bad("CONNEC_"); + bad("COP_"); + bad("DELET_"); + bad("GE_"); + bad("HEA_"); + bad("LIN_"); + bad("LOC_"); + bad("M-SEARC_"); + bad("MERG_"); + bad("MKACTIVIT_"); + bad("MKCALENDA_"); + bad("MKCO_"); + bad("MOV_"); + bad("NOTIF_"); + bad("OPTION_"); + bad("PATC_"); + bad("POS_"); + bad("PROPFIN_"); + bad("PROPPATC_"); + bad("PURG_"); + bad("PU_"); + bad("REBIN_"); + bad("REPOR_"); + bad("SEARC_"); + bad("SUBSCRIB_"); + bad("TRAC_"); + bad("UNBIN_"); + bad("UNLIN_"); + bad("UNLOC_"); + bad("UNSUBSCRIB_"); + + try + { + to_string(static_cast(-1)); + fail("", __FILE__, __LINE__); + } + catch(std::exception const&) + { + pass(); + } + } + + void + run() + { + testVerb(); + } +}; + +BEAST_DEFINE_TESTSUITE(verb,http,beast); + +} // http +} // beast + diff --git a/src/beast/test/http/write.cpp b/src/beast/test/http/write.cpp index 79fe11ab55..bb0a7f638f 100644 --- a/src/beast/test/http/write.cpp +++ b/src/beast/test/http/write.cpp @@ -8,15 +8,16 @@ // Test that header file is self-contained. #include +#include #include #include -#include +#include #include -#include #include -#include -#include +#include #include +#include +#include #include #include #include @@ -36,60 +37,179 @@ public: { using value_type = std::string; - class writer + class reader { value_type const& body_; public: + using const_buffers_type = + boost::asio::const_buffers_1; + template explicit - writer(message const& msg) noexcept + reader(message const& msg) : body_(msg.body) { } void - init(error_code& ec) noexcept + init(error_code& ec) { - beast::detail::ignore_unused(ec); + ec.assign(0, ec.category()); } - template - bool - write(error_code&, WriteFunction&& wf) noexcept + boost::optional> + get(error_code& ec) { - wf(boost::asio::buffer(body_)); - return true; + ec.assign(0, ec.category()); + return {{const_buffers_type{ + body_.data(), body_.size()}, false}}; + } + }; + }; + + template< + bool isSplit, + bool isFinalEmpty + > + struct test_body + { + struct value_type + { + std::string s; + bool mutable read = false; + }; + + class reader + { + int step_ = 0; + value_type const& body_; + + public: + using const_buffers_type = + boost::asio::const_buffers_1; + + template + explicit + reader(message const& msg) + : body_(msg.body) + { + } + + void + init(error_code& ec) + { + ec.assign(0, ec.category()); + } + + boost::optional> + get(error_code& ec) + { + ec.assign(0, ec.category()); + body_.read = true; + return get( + std::integral_constant{}, + std::integral_constant{}); + } + + private: + boost::optional> + get( + std::false_type, // isSplit + std::false_type) // isFinalEmpty + { + using boost::asio::buffer; + if(body_.s.empty()) + return boost::none; + return {{buffer(body_.s.data(), body_.s.size()), false}}; + } + + boost::optional> + get( + std::false_type, // isSplit + std::true_type) // isFinalEmpty + { + using boost::asio::buffer; + if(body_.s.empty()) + return boost::none; + switch(step_) + { + case 0: + step_ = 1; + return {{buffer( + body_.s.data(), body_.s.size()), true}}; + default: + return boost::none; + } + } + + boost::optional> + get( + std::true_type, // isSplit + std::false_type) // isFinalEmpty + { + using boost::asio::buffer; + auto const n = (body_.s.size() + 1) / 2; + switch(step_) + { + case 0: + if(n == 0) + return boost::none; + step_ = 1; + return {{buffer(body_.s.data(), n), + body_.s.size() > 1}}; + default: + return {{buffer(body_.s.data() + n, + body_.s.size() - n), false}}; + } + } + + boost::optional> + get( + std::true_type, // isSplit + std::true_type) // isFinalEmpty + { + using boost::asio::buffer; + auto const n = (body_.s.size() + 1) / 2; + switch(step_) + { + case 0: + if(n == 0) + return boost::none; + step_ = body_.s.size() > 1 ? 1 : 2; + return {{buffer(body_.s.data(), n), true}}; + case 1: + BOOST_ASSERT(body_.s.size() > 1); + step_ = 2; + return {{buffer(body_.s.data() + n, + body_.s.size() - n), true}}; + default: + return boost::none; + } } }; }; struct fail_body { - class writer; + class reader; class value_type { - friend class writer; + friend class reader; std::string s_; test::fail_counter& fc_; - boost::asio::io_service& ios_; public: - value_type(test::fail_counter& fc, - boost::asio::io_service& ios) + explicit + value_type(test::fail_counter& fc) : fc_(fc) - , ios_(ios) { } - boost::asio::io_service& - get_io_service() const - { - return ios_; - } - value_type& operator=(std::string s) { @@ -98,101 +218,88 @@ public: } }; - class writer + class reader { std::size_t n_ = 0; value_type const& body_; public: + using const_buffers_type = + boost::asio::const_buffers_1; + template explicit - writer(message const& msg) noexcept + reader(message const& msg) : body_(msg.body) { } void - init(error_code& ec) noexcept + init(error_code& ec) { body_.fc_.fail(ec); } - template - bool - write(error_code& ec, WriteFunction&& wf) noexcept + boost::optional> + get(error_code& ec) { if(body_.fc_.fail(ec)) - return false; + return boost::none; if(n_ >= body_.s_.size()) - return true; - wf(boost::asio::buffer(body_.s_.data() + n_, 1)); - ++n_; - return n_ == body_.s_.size(); + return boost::none; + return {{const_buffers_type{ + body_.s_.data() + n_++, 1}, true}}; } }; }; + template + bool + equal_body(string_view sv, string_view body) + { + test::string_istream si{ + get_io_service(), sv.to_string()}; + message m; + multi_buffer b; + try + { + read(si, b, m); + return m.body == body; + } + catch(std::exception const& e) + { + log << "equal_body: " << e.what() << std::endl; + return false; + } + } + template std::string str(message const& m) { test::string_ostream ss(ios_); - write(ss, m); + error_code ec; + write(ss, m, ec); + if(ec && ec != error::end_of_stream) + BOOST_THROW_EXCEPTION(system_error{ec}); return ss.str; } - void - testAsyncWriteHeaders(yield_context do_yield) - { - { - header m; - m.version = 11; - m.method = "GET"; - m.url = "/"; - m.fields.insert("User-Agent", "test"); - error_code ec; - test::string_ostream ss{ios_}; - async_write(ss, m, do_yield[ec]); - if(BEAST_EXPECTS(! ec, ec.message())) - BEAST_EXPECT(ss.str == - "GET / HTTP/1.1\r\n" - "User-Agent: test\r\n" - "\r\n"); - } - { - header m; - m.version = 10; - m.status = 200; - m.reason = "OK"; - m.fields.insert("Server", "test"); - m.fields.insert("Content-Length", "5"); - error_code ec; - test::string_ostream ss{ios_}; - async_write(ss, m, do_yield[ec]); - if(BEAST_EXPECTS(! ec, ec.message())) - BEAST_EXPECT(ss.str == - "HTTP/1.0 200 OK\r\n" - "Server: test\r\n" - "Content-Length: 5\r\n" - "\r\n"); - } - } - void testAsyncWrite(yield_context do_yield) { { - message m; + response m; m.version = 10; - m.status = 200; - m.reason = "OK"; - m.fields.insert("Server", "test"); - m.fields.insert("Content-Length", "5"); + m.result(status::ok); + m.set(field::server, "test"); + m.set(field::content_length, "5"); m.body = "*****"; error_code ec; test::string_ostream ss{ios_}; async_write(ss, m, do_yield[ec]); - if(BEAST_EXPECTS(! ec, ec.message())) + if(BEAST_EXPECTS(ec == error::end_of_stream, ec.message())) BEAST_EXPECT(ss.str == "HTTP/1.0 200 OK\r\n" "Server: test\r\n" @@ -201,12 +308,11 @@ public: "*****"); } { - message m; + response m; m.version = 11; - m.status = 200; - m.reason = "OK"; - m.fields.insert("Server", "test"); - m.fields.insert("Transfer-Encoding", "chunked"); + m.result(status::ok); + m.set(field::server, "test"); + m.set(field::transfer_encoding, "chunked"); m.body = "*****"; error_code ec; test::string_ostream ss(ios_); @@ -234,14 +340,10 @@ public: test::fail_counter fc(n); test::fail_stream< test::string_ostream> fs(fc, ios_); - message m( - std::piecewise_construct, - std::forward_as_tuple(fc, ios_)); - m.method = "GET"; - m.url = "/"; - m.version = 10; - m.fields.insert("User-Agent", "test"); - m.fields.insert("Content-Length", "5"); + request m(verb::get, "/", 10, fc); + m.set(field::user_agent, "test"); + m.set(field::connection, "keep-alive"); + m.set(field::content_length, "5"); m.body = "*****"; try { @@ -249,6 +351,7 @@ public: BEAST_EXPECT(fs.next_layer().str == "GET / HTTP/1.0\r\n" "User-Agent: test\r\n" + "Connection: keep-alive\r\n" "Content-Length: 5\r\n" "\r\n" "*****" @@ -267,18 +370,13 @@ public: test::fail_counter fc(n); test::fail_stream< test::string_ostream> fs(fc, ios_); - message m( - std::piecewise_construct, - std::forward_as_tuple(fc, ios_)); - m.method = "GET"; - m.url = "/"; - m.version = 10; - m.fields.insert("User-Agent", "test"); - m.fields.insert("Transfer-Encoding", "chunked"); + request m{verb::get, "/", 10, fc}; + m.set(field::user_agent, "test"); + m.set(field::transfer_encoding, "chunked"); m.body = "*****"; - error_code ec; + error_code ec = test::error::fail_error; write(fs, m, ec); - if(ec == boost::asio::error::eof) + if(ec == error::end_of_stream) { BEAST_EXPECT(fs.next_layer().str == "GET / HTTP/1.0\r\n" @@ -302,18 +400,13 @@ public: test::fail_counter fc(n); test::fail_stream< test::string_ostream> fs(fc, ios_); - message m( - std::piecewise_construct, - std::forward_as_tuple(fc, ios_)); - m.method = "GET"; - m.url = "/"; - m.version = 10; - m.fields.insert("User-Agent", "test"); - m.fields.insert("Transfer-Encoding", "chunked"); + request m{verb::get, "/", 10, fc}; + m.set(field::user_agent, "test"); + m.set(field::transfer_encoding, "chunked"); m.body = "*****"; - error_code ec; + error_code ec = test::error::fail_error; async_write(fs, m, do_yield[ec]); - if(ec == boost::asio::error::eof) + if(ec == error::end_of_stream) { BEAST_EXPECT(fs.next_layer().str == "GET / HTTP/1.0\r\n" @@ -337,22 +430,19 @@ public: test::fail_counter fc(n); test::fail_stream< test::string_ostream> fs(fc, ios_); - message m( - std::piecewise_construct, - std::forward_as_tuple(fc, ios_)); - m.method = "GET"; - m.url = "/"; - m.version = 10; - m.fields.insert("User-Agent", "test"); - m.fields.insert("Content-Length", "5"); + request m{verb::get, "/", 10, fc}; + m.set(field::user_agent, "test"); + m.set(field::connection, "keep-alive"); + m.set(field::content_length, "5"); m.body = "*****"; - error_code ec; + error_code ec = test::error::fail_error; write(fs, m, ec); if(! ec) { BEAST_EXPECT(fs.next_layer().str == "GET / HTTP/1.0\r\n" "User-Agent: test\r\n" + "Connection: keep-alive\r\n" "Content-Length: 5\r\n" "\r\n" "*****" @@ -367,22 +457,19 @@ public: test::fail_counter fc(n); test::fail_stream< test::string_ostream> fs(fc, ios_); - message m( - std::piecewise_construct, - std::forward_as_tuple(fc, ios_)); - m.method = "GET"; - m.url = "/"; - m.version = 10; - m.fields.insert("User-Agent", "test"); - m.fields.insert("Content-Length", "5"); + request m{verb::get, "/", 10, fc}; + m.set(field::user_agent, "test"); + m.set(field::connection, "keep-alive"); + m.set(field::content_length, "5"); m.body = "*****"; - error_code ec; + error_code ec = test::error::fail_error; async_write(fs, m, do_yield[ec]); if(! ec) { BEAST_EXPECT(fs.next_layer().str == "GET / HTTP/1.0\r\n" "User-Agent: test\r\n" + "Connection: keep-alive\r\n" "Content-Length: 5\r\n" "\r\n" "*****" @@ -398,13 +485,13 @@ public: { // auto content-length HTTP/1.0 { - message m; - m.method = "GET"; - m.url = "/"; + request m; + m.method(verb::get); + m.target("/"); m.version = 10; - m.fields.insert("User-Agent", "test"); + m.set(field::user_agent, "test"); m.body = "*"; - prepare(m); + m.prepare_payload(); BEAST_EXPECT(str(m) == "GET / HTTP/1.0\r\n" "User-Agent: test\r\n" @@ -413,55 +500,19 @@ public: "*" ); } - // keep-alive HTTP/1.0 - { - message m; - m.method = "GET"; - m.url = "/"; - m.version = 10; - m.fields.insert("User-Agent", "test"); - m.body = "*"; - prepare(m, connection::keep_alive); - BEAST_EXPECT(str(m) == - "GET / HTTP/1.0\r\n" - "User-Agent: test\r\n" - "Content-Length: 1\r\n" - "Connection: keep-alive\r\n" - "\r\n" - "*" - ); - } - // upgrade HTTP/1.0 - { - message m; - m.method = "GET"; - m.url = "/"; - m.version = 10; - m.fields.insert("User-Agent", "test"); - m.body = "*"; - try - { - prepare(m, connection::upgrade); - fail(); - } - catch(std::exception const&) - { - pass(); - } - } // no content-length HTTP/1.0 { - message m; - m.method = "GET"; - m.url = "/"; + request m; + m.method(verb::get); + m.target("/"); m.version = 10; - m.fields.insert("User-Agent", "test"); + m.set(field::user_agent, "test"); m.body = "*"; - prepare(m); + m.prepare_payload(); test::string_ostream ss(ios_); error_code ec; write(ss, m, ec); - BEAST_EXPECT(ec == boost::asio::error::eof); + BEAST_EXPECT(ec == error::end_of_stream); BEAST_EXPECT(ss.str == "GET / HTTP/1.0\r\n" "User-Agent: test\r\n" @@ -471,13 +522,13 @@ public: } // auto content-length HTTP/1.1 { - message m; - m.method = "GET"; - m.url = "/"; + request m; + m.method(verb::get); + m.target("/"); m.version = 11; - m.fields.insert("User-Agent", "test"); + m.set(field::user_agent, "test"); m.body = "*"; - prepare(m); + m.prepare_payload(); BEAST_EXPECT(str(m) == "GET / HTTP/1.1\r\n" "User-Agent: test\r\n" @@ -486,52 +537,15 @@ public: "*" ); } - // close HTTP/1.1 - { - message m; - m.method = "GET"; - m.url = "/"; - m.version = 11; - m.fields.insert("User-Agent", "test"); - m.body = "*"; - prepare(m, connection::close); - test::string_ostream ss(ios_); - error_code ec; - write(ss, m, ec); - BEAST_EXPECT(ec == boost::asio::error::eof); - BEAST_EXPECT(ss.str == - "GET / HTTP/1.1\r\n" - "User-Agent: test\r\n" - "Content-Length: 1\r\n" - "Connection: close\r\n" - "\r\n" - "*" - ); - } - // upgrade HTTP/1.1 - { - message m; - m.method = "GET"; - m.url = "/"; - m.version = 11; - m.fields.insert("User-Agent", "test"); - prepare(m, connection::upgrade); - BEAST_EXPECT(str(m) == - "GET / HTTP/1.1\r\n" - "User-Agent: test\r\n" - "Connection: upgrade\r\n" - "\r\n" - ); - } // no content-length HTTP/1.1 { - message m; - m.method = "GET"; - m.url = "/"; + request m; + m.method(verb::get); + m.target("/"); m.version = 11; - m.fields.insert("User-Agent", "test"); + m.set(field::user_agent, "test"); m.body = "*"; - prepare(m); + m.prepare_payload(); test::string_ostream ss(ios_); error_code ec; write(ss, m, ec); @@ -550,65 +564,14 @@ public: void test_std_ostream() { // Conversion to std::string via operator<< - message m; - m.method = "GET"; - m.url = "/"; + request m; + m.method(verb::get); + m.target("/"); m.version = 11; - m.fields.insert("User-Agent", "test"); + m.set(field::user_agent, "test"); m.body = "*"; BEAST_EXPECT(boost::lexical_cast(m) == "GET / HTTP/1.1\r\nUser-Agent: test\r\n\r\n*"); - BEAST_EXPECT(boost::lexical_cast(m.base()) == - "GET / HTTP/1.1\r\nUser-Agent: test\r\n\r\n"); - // Cause exceptions in operator<< - { - std::stringstream ss; - ss.setstate(ss.rdstate() | - std::stringstream::failbit); - try - { - // header - ss << m.base(); - fail("", __FILE__, __LINE__); - } - catch(std::exception const&) - { - pass(); - } - try - { - // message - ss << m; - fail("", __FILE__, __LINE__); - } - catch(std::exception const&) - { - pass(); - } - } - } - - void testOstream() - { - message m; - m.method = "GET"; - m.url = "/"; - m.version = 11; - m.fields.insert("User-Agent", "test"); - m.body = "*"; - prepare(m); - std::stringstream ss; - ss.setstate(ss.rdstate() | - std::stringstream::failbit); - try - { - ss << m; - fail(); - } - catch(std::exception const&) - { - pass(); - } } // Ensure completion handlers are not leaked @@ -631,11 +594,11 @@ public: boost::asio::io_service ios; test::string_ostream os{ios}; BEAST_EXPECT(handler::count() == 0); - message m; - m.method = "GET"; + request m; + m.method(verb::get); m.version = 11; - m.url = "/"; - m.fields["Content-Length"] = "5"; + m.target("/"); + m.set("Content-Length", 5); m.body = "*****"; async_write(os, m, handler{}); BEAST_EXPECT(handler::count() > 0); @@ -653,11 +616,11 @@ public: boost::asio::io_service ios; test::string_ostream is{ios}; BEAST_EXPECT(handler::count() == 0); - message m; - m.method = "GET"; + request m; + m.method(verb::get); m.version = 11; - m.url = "/"; - m.fields["Content-Length"] = "5"; + m.target("/"); + m.set("Content-Length", 5); m.body = "*****"; async_write(is, m, handler{}); BEAST_EXPECT(handler::count() > 0); @@ -666,15 +629,224 @@ public: } } + template + void + do_write(Stream& stream, message< + isRequest, Body, Fields> const& m, error_code& ec, + Decorator const& decorator = Decorator{}) + { + serializer sr{m, decorator}; + for(;;) + { + stream.nwrite = 0; + write_some(stream, sr, ec); + if(ec) + return; + BEAST_EXPECT(stream.nwrite <= 1); + if(sr.is_done()) + break; + } + } + + template + void + do_async_write(Stream& stream, + message const& m, + error_code& ec, yield_context yield, + Decorator const& decorator = Decorator{}) + { + serializer sr{m, decorator}; + for(;;) + { + stream.nwrite = 0; + async_write_some(stream, sr, yield[ec]); + if(ec) + return; + BEAST_EXPECT(stream.nwrite <= 1); + if(sr.is_done()) + break; + } + } + + struct test_decorator + { + std::string s; + + template + string_view + operator()(ConstBufferSequence const& buffers) + { + s = ";x=" + std::to_string(boost::asio::buffer_size(buffers)); + return s; + } + + string_view + operator()(boost::asio::null_buffers) + { + return "Result: OK\r\n"; + } + }; + + template + void + testWriteStream(boost::asio::yield_context yield) + { + test::pipe p{ios_}; + p.client.write_size(3); + + response m0; + m0.version = 11; + m0.result(status::ok); + m0.reason("OK"); + m0.set(field::server, "test"); + m0.body.s = "Hello, world!\n"; + + { + std::string const result = + "HTTP/1.1 200 OK\r\n" + "Server: test\r\n" + "\r\n" + "Hello, world!\n"; + { + auto m = m0; + error_code ec; + do_write(p.client, m, ec); + BEAST_EXPECT(p.server.str() == result); + BEAST_EXPECT(equal_body( + p.server.str(), m.body.s)); + p.server.clear(); + } + { + auto m = m0; + error_code ec; + do_async_write(p.client, m, ec, yield); + BEAST_EXPECT(p.server.str() == result); + BEAST_EXPECT(equal_body( + p.server.str(), m.body.s)); + p.server.clear(); + } + { + auto m = m0; + error_code ec; + response_serializer sr{m}; + sr.split(true); + for(;;) + { + write_some(p.client, sr); + if(sr.is_header_done()) + break; + } + BEAST_EXPECT(! m.body.read); + p.server.clear(); + } + { + auto m = m0; + error_code ec; + response_serializer sr{m}; + sr.split(true); + for(;;) + { + async_write_some(p.client, sr, yield); + if(sr.is_header_done()) + break; + } + BEAST_EXPECT(! m.body.read); + p.server.clear(); + } + } + { + m0.set("Transfer-Encoding", "chunked"); + { + auto m = m0; + error_code ec; + do_write(p.client, m, ec); + BEAST_EXPECT(equal_body( + p.server.str(), m.body.s)); + p.server.clear(); + } + { + auto m = m0; + error_code ec; + do_write(p.client, m, ec, test_decorator{}); + BEAST_EXPECT(equal_body( + p.server.str(), m.body.s)); + p.server.clear(); + } + { + auto m = m0; + error_code ec; + do_async_write(p.client, m, ec, yield); + BEAST_EXPECT(equal_body( + p.server.str(), m.body.s)); + p.server.clear(); + } + { + auto m = m0; + error_code ec; + do_async_write(p.client, m, ec, yield, test_decorator{}); + BEAST_EXPECT(equal_body( + p.server.str(), m.body.s)); + p.server.clear(); + } + { + auto m = m0; + error_code ec; + test::string_ostream so{get_io_service(), 3}; + response_serializer sr{m}; + sr.split(true); + for(;;) + { + write_some(p.client, sr); + if(sr.is_header_done()) + break; + } + BEAST_EXPECT(! m.body.read); + p.server.clear(); + } + { + auto m = m0; + error_code ec; + response_serializer sr{m}; + sr.split(true); + for(;;) + { + async_write_some(p.client, sr, yield); + if(sr.is_header_done()) + break; + } + BEAST_EXPECT(! m.body.read); + p.server.clear(); + } + } + } + void run() override { - yield_to(&write_test::testAsyncWriteHeaders, this); - yield_to(&write_test::testAsyncWrite, this); - yield_to(&write_test::testFailures, this); + yield_to( + [&](yield_context yield) + { + testAsyncWrite(yield); + }); + yield_to( + [&](yield_context yield) + { + testFailures(yield); + }); testOutput(); test_std_ostream(); - testOstream(); testIoService(); + yield_to( + [&](yield_context yield) + { + testWriteStream>(yield); + testWriteStream>(yield); + testWriteStream>(yield); + testWriteStream>(yield); + }); } }; diff --git a/src/beast/test/server/CMakeLists.txt b/src/beast/test/server/CMakeLists.txt new file mode 100644 index 0000000000..fe7cbbdeab --- /dev/null +++ b/src/beast/test/server/CMakeLists.txt @@ -0,0 +1,40 @@ +# Part of Beast + +GroupSources(example/server-framework framework) +GroupSources(example/common common) +GroupSources(extras/beast extras) +GroupSources(include/beast beast) + +GroupSources(test/server "/") + +add_executable (server-test + ${BEAST_INCLUDES} + ${COMMON_INCLUDES} + ${SERVER_INCLUDES} + ../../extras/beast/unit_test/main.cpp + file_service.cpp + framework.cpp + http_async_port.cpp + http_base.cpp + http_sync_port.cpp + https_ports.cpp + multi_port.cpp + server.cpp + service_list.cpp + ssl_certificate + tests.cpp + ws_async_port.cpp + ws_sync_port.cpp + ws_upgrade_service.cpp + wss_ports.cpp +) + +target_link_libraries(server-test + Beast + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ) + +if (OPENSSL_FOUND) + target_link_libraries (server-test ${OPENSSL_LIBRARIES}) +endif() diff --git a/src/beast/test/server/Jamfile b/src/beast/test/server/Jamfile new file mode 100644 index 0000000000..2011716038 --- /dev/null +++ b/src/beast/test/server/Jamfile @@ -0,0 +1,28 @@ +# +# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +# + +unit-test server-test : + ../../extras/beast/unit_test/main.cpp + file_service.cpp + framework.cpp + http_async_port.cpp + http_base.cpp + http_sync_port.cpp + https_ports.cpp + multi_port.cpp + server.cpp + service_list.cpp + ssl_certificate.cpp + tests.cpp + ws_async_port.cpp + ws_sync_port.cpp + ws_upgrade_service.cpp + wss_ports.cpp + : + coverage:no + ubasan:no + ; diff --git a/src/beast/test/server/file_service.cpp b/src/beast/test/server/file_service.cpp new file mode 100644 index 0000000000..334c60b23e --- /dev/null +++ b/src/beast/test/server/file_service.cpp @@ -0,0 +1,10 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include "../../example/server-framework/file_service.hpp" + diff --git a/src/beast/test/server/framework.cpp b/src/beast/test/server/framework.cpp new file mode 100644 index 0000000000..ac99542706 --- /dev/null +++ b/src/beast/test/server/framework.cpp @@ -0,0 +1,10 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include "../../example/server-framework/framework.hpp" + diff --git a/src/beast/test/server/http_async_port.cpp b/src/beast/test/server/http_async_port.cpp new file mode 100644 index 0000000000..dfed5a0a75 --- /dev/null +++ b/src/beast/test/server/http_async_port.cpp @@ -0,0 +1,10 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include "../../example/server-framework/http_async_port.hpp" + diff --git a/src/beast/test/server/http_base.cpp b/src/beast/test/server/http_base.cpp new file mode 100644 index 0000000000..b27661d179 --- /dev/null +++ b/src/beast/test/server/http_base.cpp @@ -0,0 +1,10 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include "../../example/server-framework/http_base.hpp" + diff --git a/src/beast/test/server/http_sync_port.cpp b/src/beast/test/server/http_sync_port.cpp new file mode 100644 index 0000000000..4625ad94ed --- /dev/null +++ b/src/beast/test/server/http_sync_port.cpp @@ -0,0 +1,10 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include "../../example/server-framework/http_sync_port.hpp" + diff --git a/src/beast/test/server/https_ports.cpp b/src/beast/test/server/https_ports.cpp new file mode 100644 index 0000000000..f8414cfca1 --- /dev/null +++ b/src/beast/test/server/https_ports.cpp @@ -0,0 +1,13 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#if BEAST_USE_OPENSSL + +// Test that header file is self-contained. +#include "../../example/server-framework/https_ports.hpp" + +#endif diff --git a/src/beast/test/server/multi_port.cpp b/src/beast/test/server/multi_port.cpp new file mode 100644 index 0000000000..a86ee0f3c3 --- /dev/null +++ b/src/beast/test/server/multi_port.cpp @@ -0,0 +1,14 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#if BEAST_USE_OPENSSL + +// Test that header file is self-contained. +#include "../../example/server-framework/multi_port.hpp" + +#endif + diff --git a/src/beast/test/server/server.cpp b/src/beast/test/server/server.cpp new file mode 100644 index 0000000000..0920a34482 --- /dev/null +++ b/src/beast/test/server/server.cpp @@ -0,0 +1,10 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include "../../example/server-framework/server.hpp" + diff --git a/src/beast/test/server/service_list.cpp b/src/beast/test/server/service_list.cpp new file mode 100644 index 0000000000..dd1569f8ad --- /dev/null +++ b/src/beast/test/server/service_list.cpp @@ -0,0 +1,10 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include "../../example/server-framework/service_list.hpp" + diff --git a/src/beast/test/server/ssl_certificate.cpp b/src/beast/test/server/ssl_certificate.cpp new file mode 100644 index 0000000000..19b0baeee1 --- /dev/null +++ b/src/beast/test/server/ssl_certificate.cpp @@ -0,0 +1,13 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#if BEAST_USE_OPENSSL + +// Test that header file is self-contained. +#include "../../example/server-framework/ssl_certificate.hpp" + +#endif diff --git a/src/beast/test/server/tests.cpp b/src/beast/test/server/tests.cpp new file mode 100644 index 0000000000..efd4b456e4 --- /dev/null +++ b/src/beast/test/server/tests.cpp @@ -0,0 +1,328 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include "example/server-framework/http_sync_port.hpp" +#include "example/server-framework/http_async_port.hpp" +#include "example/server-framework/ws_sync_port.hpp" +#include "example/server-framework/ws_async_port.hpp" +#include "example/server-framework/ws_upgrade_service.hpp" + +#if BEAST_USE_OPENSSL +# include "example/server-framework/https_ports.hpp" +# include "example/server-framework/multi_port.hpp" +# include "example/server-framework/ssl_certificate.hpp" +# include "example/server-framework/wss_ports.hpp" +#endif + +#include +#include +#include +#include + +namespace beast { +namespace websocket { + +class server_test + : public beast::unit_test::suite + , public test::enable_yield_to +{ +public: + static unsigned short constexpr port_num = 6000; + + class set_ws_options + { + beast::websocket::permessage_deflate pmd_; + + public: + set_ws_options(beast::websocket::permessage_deflate const& pmd) + : pmd_(pmd) + { + } + + template + void + operator()(beast::websocket::stream& ws) const + { + ws.auto_fragment(false); + ws.set_option(pmd_); + ws.read_message_max(64 * 1024 * 1024); + } + }; + + set_ws_options + get_ws_options() + { + beast::websocket::permessage_deflate pmd; + pmd.client_enable = true; + pmd.server_enable = true; + pmd.compLevel = 3; + return set_ws_options{pmd}; + } + + template + void + doOptions(Stream& stream, error_code& ec) + { + beast::http::request req; + req.version = 11; + req.method(beast::http::verb::options); + req.target("*"); + req.set(beast::http::field::user_agent, "test"); + req.set(beast::http::field::connection, "close"); + + beast::http::write(stream, req, ec); + if(! BEAST_EXPECTS( + ec == beast::http::error::end_of_stream, + ec.message())) + return; + + beast::flat_buffer b; + beast::http::response res; + beast::http::read(stream, b, res, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + } + + template + void + doHello(stream& ws, error_code& ec) + { + ws.handshake("localhost", "/", ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + ws.write(boost::asio::buffer(std::string("Hello, world!")), ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + beast::multi_buffer b; + ws.read(b, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + ws.close(beast::websocket::close_code::normal, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + // VFALCO Verify the buffer's contents + drain_buffer drain; + for(;;) + { + ws.read(drain, ec); + if(ec == beast::websocket::error::closed) + break; + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + } + } + + void + httpClient(framework::endpoint_type const& ep) + { + error_code ec; + boost::asio::ip::tcp::socket con{ios_}; + con.connect(ep, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + doOptions(con, ec); + } + + void + wsClient(framework::endpoint_type const& ep) + { + error_code ec; + stream ws{ios_}; + ws.next_layer().connect(ep, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + doHello(ws, ec); + } + + void + testPlain() + { + using namespace framework; + + // ws sync + { + error_code ec; + server instance; + auto const ep1 = endpoint_type{ + address_type::from_string("127.0.0.1"), port_num}; + auto const wsp = instance.make_port( + ec, ep1, instance, log, get_ws_options()); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + auto const ep2 = endpoint_type{ + address_type::from_string("127.0.0.1"), + static_cast(port_num + 1)}; + auto const sp = instance.make_port< + http_sync_port>>( + ec, ep2, instance, log); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + sp->template init<0>(ec, *wsp); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + + wsClient(ep1); + wsClient(ep2); + + httpClient(ep2); + } + + // ws async + { + error_code ec; + server instance; + auto const ep1 = endpoint_type{ + address_type::from_string("127.0.0.1"), port_num}; + auto const wsp = instance.make_port( + ec, ep1, instance, log, get_ws_options()); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + auto const ep2 = endpoint_type{ + address_type::from_string("127.0.0.1"), + static_cast(port_num + 1)}; + auto const sp = instance.make_port< + http_async_port>>( + ec, ep2, instance, log); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + sp->template init<0>(ec, *wsp); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + + wsClient(ep1); + wsClient(ep2); + + httpClient(ep2); + } + } + +#if BEAST_USE_OPENSSL + // + // OpenSSL enabled ports + // + + void + httpsClient(framework::endpoint_type const& ep, + boost::asio::ssl::context& ctx) + { + error_code ec; + boost::asio::ssl::stream con{ios_, ctx}; + con.next_layer().connect(ep, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + con.handshake( + boost::asio::ssl::stream_base::client, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + doOptions(con, ec); + if(ec) + return; + con.shutdown(ec); + // VFALCO No idea why we get eof in the normal case + if(ec == boost::asio::error::eof) + ec.assign(0, ec.category()); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + } + + void + wssClient(framework::endpoint_type const& ep, + boost::asio::ssl::context& ctx) + { + error_code ec; + stream> wss{ios_, ctx}; + wss.next_layer().next_layer().connect(ep, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + wss.next_layer().handshake( + boost::asio::ssl::stream_base::client, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + doHello(wss, ec); + } + + void + testSSL() + { + using namespace framework; + + ssl_certificate cert; + + // wss sync + { + error_code ec; + server instance; + auto const ep1 = endpoint_type{ + address_type::from_string("127.0.0.1"), port_num}; + auto const wsp = instance.make_port( + ec, ep1, instance, log, cert.get(), get_ws_options()); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + auto const ep2 = endpoint_type{ + address_type::from_string("127.0.0.1"), + static_cast(port_num + 1)}; + auto const sp = instance.make_port< + https_sync_port>>( + ec, ep2, instance, log, cert.get()); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + sp->template init<0>(ec, *wsp); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + + wssClient(ep1, cert.get()); + wssClient(ep2, cert.get()); + + httpsClient(ep2, cert.get()); + } + + // wss async + { + error_code ec; + server instance; + auto const ep1 = endpoint_type{ + address_type::from_string("127.0.0.1"), port_num}; + auto const wsp = instance.make_port( + ec, ep1, instance, log, cert.get(), get_ws_options()); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + auto const ep2 = endpoint_type{ + address_type::from_string("127.0.0.1"), + static_cast(port_num + 1)}; + auto const sp = instance.make_port< + https_async_port>>( + ec, ep2, instance, log, cert.get()); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + sp->template init<0>(ec, *wsp); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + + wssClient(ep1, cert.get()); + wssClient(ep2, cert.get()); + + httpsClient(ep2, cert.get()); + } + } +#endif + + void + run() override + { + testPlain(); + + #if BEAST_USE_OPENSSL + testSSL(); + #endif + } +}; + +BEAST_DEFINE_TESTSUITE(server,websocket,beast); + +} // websocket +} // beast + diff --git a/src/beast/test/server/ws_async_port.cpp b/src/beast/test/server/ws_async_port.cpp new file mode 100644 index 0000000000..e826d97c1d --- /dev/null +++ b/src/beast/test/server/ws_async_port.cpp @@ -0,0 +1,10 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include "../../example/server-framework/ws_async_port.hpp" + diff --git a/src/beast/test/server/ws_sync_port.cpp b/src/beast/test/server/ws_sync_port.cpp new file mode 100644 index 0000000000..8bba84d8a3 --- /dev/null +++ b/src/beast/test/server/ws_sync_port.cpp @@ -0,0 +1,10 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include "../../example/server-framework/ws_sync_port.hpp" + diff --git a/src/beast/test/server/ws_upgrade_service.cpp b/src/beast/test/server/ws_upgrade_service.cpp new file mode 100644 index 0000000000..dcabff756a --- /dev/null +++ b/src/beast/test/server/ws_upgrade_service.cpp @@ -0,0 +1,10 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include "../../example/server-framework/ws_upgrade_service.hpp" + diff --git a/src/beast/test/server/wss_ports.cpp b/src/beast/test/server/wss_ports.cpp new file mode 100644 index 0000000000..6307f532c6 --- /dev/null +++ b/src/beast/test/server/wss_ports.cpp @@ -0,0 +1,13 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#if BEAST_USE_OPENSSL + +// Test that header file is self-contained. +#include "../../example/server-framework/wss_ports.hpp" + +#endif diff --git a/src/beast/test/websocket/CMakeLists.txt b/src/beast/test/websocket/CMakeLists.txt index 5d2856edb3..2807477d34 100644 --- a/src/beast/test/websocket/CMakeLists.txt +++ b/src/beast/test/websocket/CMakeLists.txt @@ -2,14 +2,18 @@ GroupSources(extras/beast extras) GroupSources(include/beast beast) + GroupSources(test/websocket "/") +#include_directories(../../example/) + add_executable (websocket-tests ${BEAST_INCLUDES} ${EXTRAS_INCLUDES} ../../extras/beast/unit_test/main.cpp websocket_async_echo_server.hpp websocket_sync_echo_server.hpp + doc_snippets.cpp error.cpp option.cpp rfc6455.cpp @@ -20,12 +24,15 @@ add_executable (websocket-tests utf8_checker.cpp ) -if (NOT WIN32) - target_link_libraries(websocket-tests ${Boost_LIBRARIES} Threads::Threads) -else() - target_link_libraries(websocket-tests ${Boost_LIBRARIES}) -endif() +target_link_libraries(websocket-tests + Beast + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_COROUTINE_LIBRARY} + ${Boost_THREAD_LIBRARY} + ${Boost_CONTEXT_LIBRARY} + ) -if (MINGW) - set_target_properties(websocket-tests PROPERTIES COMPILE_FLAGS "-Wa,-mbig-obj -Og") +if (OPENSSL_FOUND) + target_link_libraries(websocket-tests ${OPENSSL_LIBRARIES}) endif() diff --git a/src/beast/test/websocket/Jamfile b/src/beast/test/websocket/Jamfile new file mode 100644 index 0000000000..4781f16aca --- /dev/null +++ b/src/beast/test/websocket/Jamfile @@ -0,0 +1,19 @@ +# +# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +# + +unit-test websocket-tests : + ../../extras/beast/unit_test/main.cpp + doc_snippets.cpp + error.cpp + option.cpp + rfc6455.cpp + stream.cpp + teardown.cpp + frame.cpp + mask.cpp + utf8_checker.cpp + ; diff --git a/src/beast/test/websocket/doc_snippets.cpp b/src/beast/test/websocket/doc_snippets.cpp new file mode 100644 index 0000000000..654c2ca3ad --- /dev/null +++ b/src/beast/test/websocket/doc_snippets.cpp @@ -0,0 +1,282 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include +#include +#include +#include +#include +#include +#include +#include + +//[ws_snippet_1 +#include +using namespace beast::websocket; +//] + +using namespace beast; + +namespace doc_ws_snippets { + +void fxx() { + +boost::asio::io_service ios; +boost::asio::io_service::work work{ios}; +std::thread t{[&](){ ios.run(); }}; +error_code ec; +boost::asio::ip::tcp::socket sock{ios}; + +{ +//[ws_snippet_2 + stream ws{ios}; +//] +} + +{ +//[ws_snippet_3 + stream ws{std::move(sock)}; +//] +} + +{ +//[ws_snippet_4 + stream ws{sock}; +//] + +//[ws_snippet_5 + ws.next_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_send); +//] +} + +{ +//[ws_snippet_6 + std::string const host = "mywebapp.com"; + boost::asio::ip::tcp::resolver r{ios}; + stream ws{ios}; + boost::asio::connect(ws.next_layer(), r.resolve({host, "ws"})); +//] +} + +{ +//[ws_snippet_7 + boost::asio::ip::tcp::acceptor acceptor{ios}; + stream ws{acceptor.get_io_service()}; + acceptor.accept(ws.next_layer()); +//] +} + +{ + stream ws{ios}; +//[ws_snippet_8 + ws.handshake("localhost", "/"); +//] + +//[ws_snippet_9 + ws.handshake_ex("localhost", "/", + [](request_type& m) + { + m.insert(http::field::sec_websocket_protocol, "xmpp;ws-chat"); + }); +//] + +//[ws_snippet_10 + response_type res; + ws.handshake(res, "localhost", "/"); + if(! res.count(http::field::sec_websocket_protocol)) + throw std::invalid_argument("missing subprotocols"); +//] + +//[ws_snippet_11 + ws.accept(); +//] + +//[ws_snippet_12 + ws.accept_ex( + [](response_type& m) + { + m.insert(http::field::server, "MyServer"); + }); +//] +} + +{ +//[ws_snippet_13] + // Buffer required for reading HTTP messages + flat_buffer buffer; + + // Read the HTTP request ourselves + http::request req; + http::read(sock, buffer, req); + + // See if its a WebSocket upgrade request + if(websocket::is_upgrade(req)) + { + // Construct the stream, transferring ownership of the socket + stream ws{std::move(sock)}; + + // Accept the request from our message. Clients SHOULD NOT + // begin sending WebSocket frames until the server has + // provided a response, but just in case they did, we pass + // any leftovers in the buffer to the accept function. + // + ws.accept(req, buffer.data()); + } + else + { + // Its not a WebSocket upgrade, so + // handle it like a normal HTTP request. + } +//] +} + +{ + stream ws{ios}; +//[ws_snippet_14 + // Read into our buffer until we reach the end of the HTTP request. + // No parsing takes place here, we are just accumulating data. + boost::asio::streambuf buffer; + boost::asio::read_until(sock, buffer, "\r\n\r\n"); + + // Now accept the connection, using the buffered data. + ws.accept(buffer.data()); +//] +} +{ + stream ws{ios}; +//[ws_snippet_15 + multi_buffer buffer; + ws.read(buffer); + + ws.text(ws.got_text()); + ws.write(buffer.data()); + buffer.consume(buffer.size()); +//] +} + +{ + stream ws{ios}; +//[ws_snippet_16 + multi_buffer buffer; + for(;;) + if(ws.read_frame(buffer)) + break; + ws.binary(ws.got_binary()); + consuming_buffers cb{buffer.data()}; + for(;;) + { + using boost::asio::buffer_size; + if(buffer_size(cb) > 512) + { + ws.write_frame(false, buffer_prefix(512, cb)); + cb.consume(512); + } + else + { + ws.write_frame(true, cb); + break; + } + } +//] +} + +{ + stream ws{ios}; +//[ws_snippet_17 + ws.control_callback( + [](frame_type kind, string_view payload) + { + // Do something with the payload + boost::ignore_unused(kind, payload); + }); +//] + +//[ws_snippet_18 + ws.close(close_code::normal); +//] + +//[ws_snippet_19 + ws.auto_fragment(true); + ws.write_buffer_size(16384); +//] + +//[ws_snippet_20 + multi_buffer buffer; + ws.async_read(buffer, + [](error_code) + { + // Do something with the buffer + }); +//] +} + +} // fxx() + +// workaround for https://github.com/chriskohlhoff/asio/issues/112 +#ifdef BOOST_MSVC +//[ws_snippet_21 +void echo(stream& ws, + multi_buffer& buffer, boost::asio::yield_context yield) +{ + ws.async_read(buffer, yield); + std::future fut = + ws.async_write(buffer.data(), boost::asio::use_future); +} +//] +#endif + +} // doc_ws_snippets + +//------------------------------------------------------------------------------ + +#if BEAST_USE_OPENSSL + +//[wss_snippet_1 +#include +#include +//] + +namespace doc_wss_snippets { + +void fxx() { + +boost::asio::io_service ios; +boost::asio::io_service::work work{ios}; +std::thread t{[&](){ ios.run(); }}; +error_code ec; +boost::asio::ip::tcp::socket sock{ios}; + +{ +//[wss_snippet_2 + boost::asio::ssl::context ctx{boost::asio::ssl::context::sslv23}; + stream> wss{ios, ctx}; +//] +} + +{ +//[wss_snippet_3 + boost::asio::ip::tcp::endpoint ep; + boost::asio::ssl::context ctx{boost::asio::ssl::context::sslv23}; + stream> ws{ios, ctx}; + + // connect the underlying TCP/IP socket + ws.next_layer().next_layer().connect(ep); + + // perform SSL handshake + ws.next_layer().handshake(boost::asio::ssl::stream_base::client); + + // perform WebSocket handshake + ws.handshake("localhost", "/"); +//] +} + +} // fxx() + +} // doc_wss_snippets + +#endif + diff --git a/src/beast/test/websocket/error.cpp b/src/beast/test/websocket/error.cpp index 9c02468cfa..3032915d5c 100644 --- a/src/beast/test/websocket/error.cpp +++ b/src/beast/test/websocket/error.cpp @@ -34,17 +34,9 @@ public: void run() override { - check("websocket", error::closed); - check("websocket", error::failed); - check("websocket", error::handshake_failed); - check("websocket", error::keep_alive); - check("websocket", error::response_malformed); - check("websocket", error::response_failed); - check("websocket", error::response_denied); - check("websocket", error::request_malformed); - check("websocket", error::request_invalid); - check("websocket", error::request_denied); - check("websocket", error::general); + check("beast.websocket", error::closed); + check("beast.websocket", error::failed); + check("beast.websocket", error::handshake_failed); } }; diff --git a/src/beast/test/websocket/frame.cpp b/src/beast/test/websocket/frame.cpp index 13d2c51039..2dc21c4ecd 100644 --- a/src/beast/test/websocket/frame.cpp +++ b/src/beast/test/websocket/frame.cpp @@ -5,9 +5,11 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // +#include #include -#include #include +#include +#include #include #include @@ -30,32 +32,34 @@ operator==(frame_header const& lhs, frame_header const& rhs) lhs.key == rhs.key; } -class frame_test : public beast::unit_test::suite +class frame_test + : public beast::unit_test::suite + , public test::enable_yield_to { public: void testCloseCodes() { - BEAST_EXPECT(! is_valid(0)); - BEAST_EXPECT(! is_valid(1)); - BEAST_EXPECT(! is_valid(999)); - BEAST_EXPECT(! is_valid(1004)); - BEAST_EXPECT(! is_valid(1005)); - BEAST_EXPECT(! is_valid(1006)); - BEAST_EXPECT(! is_valid(1016)); - BEAST_EXPECT(! is_valid(2000)); - BEAST_EXPECT(! is_valid(2999)); - BEAST_EXPECT(is_valid(1000)); - BEAST_EXPECT(is_valid(1002)); - BEAST_EXPECT(is_valid(3000)); - BEAST_EXPECT(is_valid(4000)); - BEAST_EXPECT(is_valid(5000)); + BEAST_EXPECT(! is_valid_close_code(0)); + BEAST_EXPECT(! is_valid_close_code(1)); + BEAST_EXPECT(! is_valid_close_code(999)); + BEAST_EXPECT(! is_valid_close_code(1004)); + BEAST_EXPECT(! is_valid_close_code(1005)); + BEAST_EXPECT(! is_valid_close_code(1006)); + BEAST_EXPECT(! is_valid_close_code(1016)); + BEAST_EXPECT(! is_valid_close_code(2000)); + BEAST_EXPECT(! is_valid_close_code(2999)); + BEAST_EXPECT(is_valid_close_code(1000)); + BEAST_EXPECT(is_valid_close_code(1002)); + BEAST_EXPECT(is_valid_close_code(3000)); + BEAST_EXPECT(is_valid_close_code(4000)); + BEAST_EXPECT(is_valid_close_code(5000)); } struct test_fh : frame_header { test_fh() { - op = opcode::text; + op = detail::opcode::text; fin = false; mask = false; rsv1 = false; @@ -68,29 +72,34 @@ public: void testFrameHeader() { + using stream_type = + beast::websocket::stream; + test::pipe p{ios_}; + // good frame fields { - role_type role = role_type::client; + stream_type::role_type role = + stream_type::role_type::client; auto check = [&](frame_header const& fh) { - fh_streambuf sb; - write(sb, fh); - close_code::value code; - stream_base stream; + fh_streambuf b; + write(b, fh); + close_code code; + stream_type stream{p.server}; stream.open(role); detail::frame_header fh1; auto const n = - stream.read_fh1(fh1, sb, code); + stream.read_fh1(fh1, b, code); if(! BEAST_EXPECT(! code)) return; - if(! BEAST_EXPECT(sb.size() == n)) + if(! BEAST_EXPECT(b.size() == n)) return; - stream.read_fh2(fh1, sb, code); + stream.read_fh2(fh1, b, code); if(! BEAST_EXPECT(! code)) return; - if(! BEAST_EXPECT(sb.size() == 0)) + if(! BEAST_EXPECT(b.size() == 0)) return; BEAST_EXPECT(fh1 == fh); }; @@ -99,7 +108,7 @@ public: check(fh); - role = role_type::server; + role = stream_type::role_type::server; fh.mask = true; fh.key = 1; check(fh); @@ -122,36 +131,36 @@ public: // bad frame fields { - role_type role = role_type::client; + stream_type::role_type role = stream_type::role_type::client; auto check = [&](frame_header const& fh) { - fh_streambuf sb; - write(sb, fh); - close_code::value code; - stream_base stream; + fh_streambuf b; + write(b, fh); + close_code code; + stream_type stream{p.server}; stream.open(role); - detail::frame_header fh1; + frame_header fh1; auto const n = - stream.read_fh1(fh1, sb, code); + stream.read_fh1(fh1, b, code); if(code) { pass(); return; } - if(! BEAST_EXPECT(sb.size() == n)) + if(! BEAST_EXPECT(b.size() == n)) return; - stream.read_fh2(fh1, sb, code); + stream.read_fh2(fh1, b, code); if(! BEAST_EXPECT(code)) return; - if(! BEAST_EXPECT(sb.size() == 0)) + if(! BEAST_EXPECT(b.size() == 0)) return; }; test_fh fh; - fh.op = opcode::close; + fh.op = detail::opcode::close; fh.fin = true; fh.len = 126; check(fh); @@ -169,11 +178,11 @@ public: check(fh); fh.rsv3 = false; - fh.op = opcode::rsv3; + fh.op = detail::opcode::rsv3; check(fh); - fh.op = opcode::text; + fh.op = detail::opcode::text; - fh.op = opcode::ping; + fh.op = detail::opcode::ping; fh.fin = false; check(fh); fh.fin = true; @@ -181,7 +190,7 @@ public: fh.mask = true; check(fh); - role = role_type::server; + role = stream_type::role_type::server; fh.mask = false; check(fh); } @@ -189,29 +198,32 @@ public: void bad(std::initializer_list bs) { + using stream_type = + beast::websocket::stream; using boost::asio::buffer; using boost::asio::buffer_copy; - static role_type constexpr role = role_type::client; + test::pipe p{ios_}; + static stream_type::role_type constexpr role = stream_type::role_type::client; std::vector v{bs}; - fh_streambuf sb; - sb.commit(buffer_copy(sb.prepare(v.size()), buffer(v))); - stream_base stream; + fh_streambuf b; + b.commit(buffer_copy(b.prepare(v.size()), buffer(v))); + stream_type stream{p.server}; stream.open(role); - close_code::value code; + close_code code; detail::frame_header fh; auto const n = - stream.read_fh1(fh, sb, code); + stream.read_fh1(fh, b, code); if(code) { pass(); return; } - if(! BEAST_EXPECT(sb.size() == n)) + if(! BEAST_EXPECT(b.size() == n)) return; - stream.read_fh2(fh, sb, code); + stream.read_fh2(fh, b, code); if(! BEAST_EXPECT(code)) return; - if(! BEAST_EXPECT(sb.size() == 0)) + if(! BEAST_EXPECT(b.size() == 0)) return; } diff --git a/src/beast/test/websocket/rfc6455.cpp b/src/beast/test/websocket/rfc6455.cpp index c34daf312f..169ba81000 100644 --- a/src/beast/test/websocket/rfc6455.cpp +++ b/src/beast/test/websocket/rfc6455.cpp @@ -7,3 +7,43 @@ // Test that header file is self-contained. #include + +#include + +namespace beast { +namespace websocket { + +class rfc6455_test + : public beast::unit_test::suite +{ +public: + void + test_is_upgrade() + { + http::header req; + req.version = 10; + BEAST_EXPECT(! is_upgrade(req)); + req.version = 11; + req.method(http::verb::post); + req.target("/"); + BEAST_EXPECT(! is_upgrade(req)); + req.method(http::verb::get); + req.insert("Connection", "upgrade"); + BEAST_EXPECT(! is_upgrade(req)); + req.insert("Upgrade", "websocket"); + BEAST_EXPECT(! is_upgrade(req)); + req.insert("Sec-WebSocket-Version", "13"); + BEAST_EXPECT(is_upgrade(req)); + } + + void + run() override + { + test_is_upgrade(); + } +}; + +BEAST_DEFINE_TESTSUITE(rfc6455,websocket,beast); + +} // websocket +} // beast diff --git a/src/beast/test/websocket/stream.cpp b/src/beast/test/websocket/stream.cpp index 47e0c833a0..02bab544a6 100644 --- a/src/beast/test/websocket/stream.cpp +++ b/src/beast/test/websocket/stream.cpp @@ -11,10 +11,12 @@ #include "websocket_async_echo_server.hpp" #include "websocket_sync_echo_server.hpp" -#include -#include +#include +#include #include #include +#include +#include #include #include #include @@ -36,6 +38,15 @@ public: using address_type = boost::asio::ip::address; using socket_type = boost::asio::ip::tcp::socket; + template + static + std::string + to_string(ConstBufferSequence const& bs) + { + return boost::lexical_cast< + std::string>(buffers(bs)); + } + struct con { stream ws; @@ -109,30 +120,432 @@ public: return false; } - struct test_decorator + struct SyncClient { - int& what; - - test_decorator(test_decorator const&) = default; - - test_decorator(int& what_) - : what(what_) + template + void + accept(stream& ws) const { - what = 0; + ws.accept(); } - template - void - operator()(http::header&) const + template + typename std::enable_if< + ! http::detail::is_header::value>::type + accept(stream& ws, + Buffers const& buffers) const { - what |= 1; + ws.accept(buffers); } - template + template void - operator()(http::header&) const + accept(stream& ws, + http::header const& req) const { - what |= 2; + ws.accept(req); + } + + template + void + accept(stream& ws, + http::header const& req, + Buffers const& buffers) const + { + ws.accept(req, buffers); + } + + template + void + accept_ex(stream& ws, + Decorator const& d) const + { + ws.accept_ex(d); + } + + template + typename std::enable_if< + ! http::detail::is_header::value>::type + accept_ex(stream& ws, + Buffers const& buffers, + Decorator const& d) const + { + ws.accept_ex(buffers, d); + } + + template + void + accept_ex(stream& ws, + http::header const& req, + Decorator const& d) const + { + ws.accept_ex(req, d); + } + + template + void + accept_ex(stream& ws, + http::header const& req, + Buffers const& buffers, + Decorator const& d) const + { + ws.accept_ex(req, buffers, d); + } + + template + void + handshake(stream& ws, + string_view uri, + string_view path) const + { + ws.handshake(uri, path); + } + + template + void + handshake(stream& ws, + response_type& res, + string_view uri, + string_view path) const + { + ws.handshake(res, uri, path); + } + + template + void + handshake_ex(stream& ws, + string_view uri, + string_view path, + Decorator const& d) const + { + ws.handshake_ex(uri, path, d); + } + + template + void + handshake_ex(stream& ws, + response_type& res, + string_view uri, + string_view path, + Decorator const& d) const + { + ws.handshake_ex(res, uri, path, d); + } + + template + void + ping(stream& ws, + ping_data const& payload) const + { + ws.ping(payload); + } + + template + void + pong(stream& ws, + ping_data const& payload) const + { + ws.pong(payload); + } + + template + void + close(stream& ws, + close_reason const& cr) const + { + ws.close(cr); + } + + template< + class NextLayer, class DynamicBuffer> + void + read(stream& ws, + DynamicBuffer& buffer) const + { + ws.read(buffer); + } + + template< + class NextLayer, class ConstBufferSequence> + void + write(stream& ws, + ConstBufferSequence const& buffers) const + { + ws.write(buffers); + } + + template< + class NextLayer, class ConstBufferSequence> + void + write_frame(stream& ws, bool fin, + ConstBufferSequence const& buffers) const + { + ws.write_frame(fin, buffers); + } + + template< + class NextLayer, class ConstBufferSequence> + void + write_raw(stream& ws, + ConstBufferSequence const& buffers) const + { + boost::asio::write( + ws.next_layer(), buffers); + } + }; + + class AsyncClient + { + yield_context& yield_; + + public: + explicit + AsyncClient(yield_context& yield) + : yield_(yield) + { + } + + template + void + accept(stream& ws) const + { + error_code ec; + ws.async_accept(yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + typename std::enable_if< + ! http::detail::is_header::value>::type + accept(stream& ws, + Buffers const& buffers) const + { + error_code ec; + ws.async_accept(buffers, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + accept(stream& ws, + http::header const& req) const + { + error_code ec; + ws.async_accept(req, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + accept(stream& ws, + http::header const& req, + Buffers const& buffers) const + { + error_code ec; + ws.async_accept(req, buffers, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + accept_ex(stream& ws, + Decorator const& d) const + { + error_code ec; + ws.async_accept_ex(d, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + typename std::enable_if< + ! http::detail::is_header::value>::type + accept_ex(stream& ws, + Buffers const& buffers, + Decorator const& d) const + { + error_code ec; + ws.async_accept_ex(buffers, d, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + accept_ex(stream& ws, + http::header const& req, + Decorator const& d) const + { + error_code ec; + ws.async_accept_ex(req, d, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + accept_ex(stream& ws, + http::header const& req, + Buffers const& buffers, + Decorator const& d) const + { + error_code ec; + ws.async_accept_ex( + req, buffers, d, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + handshake(stream& ws, + string_view uri, + string_view path) const + { + error_code ec; + ws.async_handshake( + uri, path, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + handshake(stream& ws, + response_type& res, + string_view uri, + string_view path) const + { + error_code ec; + ws.async_handshake( + res, uri, path, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + handshake_ex(stream& ws, + string_view uri, + string_view path, + Decorator const &d) const + { + error_code ec; + ws.async_handshake_ex( + uri, path, d, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + handshake_ex(stream& ws, + response_type& res, + string_view uri, + string_view path, + Decorator const &d) const + { + error_code ec; + ws.async_handshake_ex( + res, uri, path, d, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + ping(stream& ws, + ping_data const& payload) const + { + error_code ec; + ws.async_ping(payload, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + pong(stream& ws, + ping_data const& payload) const + { + error_code ec; + ws.async_pong(payload, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + close(stream& ws, + close_reason const& cr) const + { + error_code ec; + ws.async_close(cr, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template< + class NextLayer, class DynamicBuffer> + void + read(stream& ws, + DynamicBuffer& buffer) const + { + error_code ec; + ws.async_read(buffer, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template< + class NextLayer, class ConstBufferSequence> + void + write(stream& ws, + ConstBufferSequence const& buffers) const + { + error_code ec; + ws.async_write(buffers, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template< + class NextLayer, class ConstBufferSequence> + void + write_frame(stream& ws, bool fin, + ConstBufferSequence const& buffers) const + { + error_code ec; + ws.async_write_frame(fin, buffers, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template< + class NextLayer, class ConstBufferSequence> + void + write_raw(stream& ws, + ConstBufferSequence const& buffers) const + { + error_code ec; + boost::asio::async_write( + ws.next_layer(), buffers, yield_[ec]); + if(ec) + throw system_error{ec}; } }; @@ -140,24 +553,14 @@ public: testOptions() { stream ws(ios_); - ws.set_option(auto_fragment{true}); - ws.set_option(keep_alive{false}); - ws.set_option(write_buffer_size{2048}); - ws.set_option(message_type{opcode::text}); - ws.set_option(read_buffer_size{8192}); - ws.set_option(read_message_max{1 * 1024 * 1024}); + ws.auto_fragment(true); + ws.write_buffer_size(2048); + ws.binary(false); + ws.read_buffer_size(8192); + ws.read_message_max(1 * 1024 * 1024); try { - ws.set_option(write_buffer_size{7}); - fail(); - } - catch(std::exception const&) - { - pass(); - } - try - { - message_type{opcode::close}; + ws.write_buffer_size(7); fail(); } catch(std::exception const&) @@ -166,75 +569,419 @@ public: } } - void testAccept() + //-------------------------------------------------------------------------- + + class res_decorator { + bool& b_; + + public: + res_decorator(res_decorator const&) = default; + + explicit + res_decorator(bool& b) + : b_(b) { - static std::size_t constexpr limit = 100; - std::size_t n; - for(n = 0; n < limit; ++n) - { - // valid - http::request req; - req.method = "GET"; - req.url = "/"; - req.version = 11; - req.fields.insert("Host", "localhost"); - req.fields.insert("Upgrade", "websocket"); - req.fields.insert("Connection", "upgrade"); - req.fields.insert("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ=="); - req.fields.insert("Sec-WebSocket-Version", "13"); - stream> ws(n, ios_, ""); - try - { - ws.accept(req); - break; - } - catch(system_error const&) - { - } - } - BEAST_EXPECT(n < limit); } + + void + operator()(response_type&) const { - // valid - stream ws(ios_, - "GET / HTTP/1.1\r\n" - "Host: localhost:80\r\n" - "Upgrade: WebSocket\r\n" - "Connection: upgrade\r\n" - "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" - "Sec-WebSocket-Version: 13\r\n" - "\r\n" - ); + b_ = true; + } + }; + + template + void + testAccept(Client const& c) + { + static std::size_t constexpr limit = 200; + std::size_t n; + for(n = 0; n < limit; ++n) + { + test::fail_counter fc{n}; try { - ws.accept(); - pass(); + // request in stream + { + stream> ws{fc, ios_, + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + , 20}; + c.accept(ws); + // VFALCO validate contents of ws.next_layer().str? + } + { + stream> ws{fc, ios_, + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + , 20}; + bool called = false; + c.accept_ex(ws, res_decorator{called}); + BEAST_EXPECT(called); + } + // request in buffers + { + stream> ws{fc, ios_}; + c.accept(ws, sbuf( + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + )); + } + { + stream> ws{fc, ios_}; + bool called = false; + c.accept_ex(ws, sbuf( + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n"), + res_decorator{called}); + BEAST_EXPECT(called); + } + // request in buffers and stream + { + stream> ws{fc, ios_, + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + , 16}; + c.accept(ws, sbuf( + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + )); + } + { + stream> ws{fc, ios_, + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + , 16}; + bool called = false; + c.accept_ex(ws, sbuf( + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n"), + res_decorator{called}); + BEAST_EXPECT(called); + } + // request in message + { + request_type req; + req.method(http::verb::get); + req.target("/"); + req.version = 11; + req.insert("Host", "localhost"); + req.insert("Upgrade", "websocket"); + req.insert("Connection", "upgrade"); + req.insert("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ=="); + req.insert("Sec-WebSocket-Version", "13"); + stream> ws{fc, ios_}; + c.accept(ws, req); + } + { + request_type req; + req.method(http::verb::get); + req.target("/"); + req.version = 11; + req.insert("Host", "localhost"); + req.insert("Upgrade", "websocket"); + req.insert("Connection", "upgrade"); + req.insert("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ=="); + req.insert("Sec-WebSocket-Version", "13"); + stream> ws{fc, ios_}; + bool called = false; + c.accept_ex(ws, req, + res_decorator{called}); + BEAST_EXPECT(called); + } + // request in message, close frame in buffers + { + request_type req; + req.method(http::verb::get); + req.target("/"); + req.version = 11; + req.insert("Host", "localhost"); + req.insert("Upgrade", "websocket"); + req.insert("Connection", "upgrade"); + req.insert("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ=="); + req.insert("Sec-WebSocket-Version", "13"); + stream> ws{fc, ios_}; + c.accept(ws, req, + cbuf(0x88, 0x82, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x17)); + try + { + multi_buffer b; + c.read(ws, b); + fail("success", __FILE__, __LINE__); + } + catch(system_error const& e) + { + if(e.code() != websocket::error::closed) + throw; + } + } + { + request_type req; + req.method(http::verb::get); + req.target("/"); + req.version = 11; + req.insert("Host", "localhost"); + req.insert("Upgrade", "websocket"); + req.insert("Connection", "upgrade"); + req.insert("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ=="); + req.insert("Sec-WebSocket-Version", "13"); + stream> ws{fc, ios_}; + bool called = false; + c.accept_ex(ws, req, + cbuf(0x88, 0x82, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x17), + res_decorator{called}); + BEAST_EXPECT(called); + try + { + multi_buffer b; + c.read(ws, b); + fail("success", __FILE__, __LINE__); + } + catch(system_error const& e) + { + if(e.code() != websocket::error::closed) + throw; + } + } + // request in message, close frame in stream + { + request_type req; + req.method(http::verb::get); + req.target("/"); + req.version = 11; + req.insert("Host", "localhost"); + req.insert("Upgrade", "websocket"); + req.insert("Connection", "upgrade"); + req.insert("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ=="); + req.insert("Sec-WebSocket-Version", "13"); + stream> ws{fc, ios_, + "\x88\x82\xff\xff\xff\xff\xfc\x17"}; + c.accept(ws, req); + try + { + multi_buffer b; + c.read(ws, b); + fail("success", __FILE__, __LINE__); + } + catch(system_error const& e) + { + if(e.code() != websocket::error::closed) + throw; + } + } + // request in message, close frame in stream and buffers + { + request_type req; + req.method(http::verb::get); + req.target("/"); + req.version = 11; + req.insert("Host", "localhost"); + req.insert("Upgrade", "websocket"); + req.insert("Connection", "upgrade"); + req.insert("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ=="); + req.insert("Sec-WebSocket-Version", "13"); + stream> ws{fc, ios_, + "xff\xff\xfc\x17"}; + c.accept(ws, req, + cbuf(0x88, 0x82, 0xff, 0xff)); + try + { + multi_buffer b; + c.read(ws, b); + fail("success", __FILE__, __LINE__); + } + catch(system_error const& e) + { + if(e.code() != websocket::error::closed) + throw; + } + } + // failed handshake (missing Sec-WebSocket-Key) + { + stream> ws{fc, ios_, + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + , 20}; + try + { + c.accept(ws); + fail("success", __FILE__, __LINE__); + } + catch(system_error const& e) + { + if( e.code() != + websocket::error::handshake_failed && + e.code() != + boost::asio::error::eof) + throw; + } + } } catch(system_error const&) { - fail(); - } - } - { - // invalid - stream ws(ios_, - "GET / HTTP/1.0\r\n" - "\r\n" - ); - try - { - ws.accept(); - fail(); - } - catch(system_error const&) - { - pass(); + continue; } + break; } + BEAST_EXPECT(n < limit); } + void + testAccept() + { + testAccept(SyncClient{}); + yield_to( + [&](yield_context yield) + { + testAccept(AsyncClient{yield}); + }); + } + + //-------------------------------------------------------------------------- + + class req_decorator + { + bool& b_; + + public: + req_decorator(req_decorator const&) = default; + + explicit + req_decorator(bool& b) + : b_(b) + { + } + + void + operator()(request_type&) const + { + b_ = true; + } + }; + + template + void + testHandshake(endpoint_type const& ep, Client const& c) + { + static std::size_t constexpr limit = 200; + std::size_t n; + for(n = 199; n < limit; ++n) + { + test::fail_counter fc{n}; + try + { + // handshake + { + stream> ws{fc, ios_}; + ws.next_layer().next_layer().connect(ep); + c.handshake(ws, "localhost", "/"); + } + // handshake, response + { + stream> ws{fc, ios_}; + ws.next_layer().next_layer().connect(ep); + response_type res; + c.handshake(ws, res, "localhost", "/"); + // VFALCO validate res? + } + // handshake_ex + { + stream> ws{fc, ios_}; + ws.next_layer().next_layer().connect(ep); + bool called = false; + c.handshake_ex(ws, "localhost", "/", + req_decorator{called}); + BEAST_EXPECT(called); + } + // handshake_ex, response + { + stream> ws{fc, ios_}; + ws.next_layer().next_layer().connect(ep); + bool called = false; + response_type res; + c.handshake_ex(ws, res, "localhost", "/", + req_decorator{called}); + // VFALCO validate res? + BEAST_EXPECT(called); + } + } + catch(system_error const&) + { + continue; + } + break; + } + BEAST_EXPECT(n < limit); + } + + void + testHandshake() + { + error_code ec = test::error::fail_error; + ::websocket::async_echo_server server{nullptr, 1}; + auto const any = endpoint_type{ + address_type::from_string("127.0.0.1"), 0}; + server.open(any, ec); + BEAST_EXPECTS(! ec, ec.message()); + auto const ep = server.local_endpoint(); + testHandshake(ep, SyncClient{}); + yield_to( + [&](yield_context yield) + { + testHandshake(ep, AsyncClient{yield}); + }); + } + + //-------------------------------------------------------------------------- + void testBadHandshakes() { auto const check = @@ -244,7 +991,6 @@ public: { stream ws(ios_, s.substr(i, s.size() - i)); - ws.set_option(keep_alive{true}); try { ws.accept(boost::asio::buffer( @@ -258,7 +1004,7 @@ public: } }; // wrong version - check(error::handshake_failed, + check(http::error::end_of_stream, "GET / HTTP/1.0\r\n" "Host: localhost:80\r\n" "Upgrade: WebSocket\r\n" @@ -359,7 +1105,7 @@ public: } catch(system_error const& se) { - BEAST_EXPECT(se.code() == error::response_failed); + BEAST_EXPECT(se.code() == error::handshake_failed); } }; // wrong HTTP version @@ -424,31 +1170,14 @@ public: } void - testDecorator(endpoint_type const& ep) - { - error_code ec; - socket_type sock{ios_}; - sock.connect(ep, ec); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - stream ws{sock}; - int what; - ws.set_option(decorate(test_decorator{what})); - BEAST_EXPECT(what == 0); - ws.handshake("localhost", "/", ec); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - BEAST_EXPECT(what == 1); - } - - void testMask(endpoint_type const& ep, + testMask(endpoint_type const& ep, yield_context do_yield) { { std::vector v; for(char n = 0; n < 20; ++n) { - error_code ec; + error_code ec = test::error::fail_error; socket_type sock(ios_); sock.connect(ep, ec); if(! BEAST_EXPECTS(! ec, ec.message())) @@ -460,9 +1189,8 @@ public: ws.write(boost::asio::buffer(v), ec); if(! BEAST_EXPECTS(! ec, ec.message())) break; - opcode op; - streambuf db; - ws.read(op, db, ec); + multi_buffer db; + ws.read(db, ec); if(! BEAST_EXPECTS(! ec, ec.message())) break; BEAST_EXPECT(to_string(db.data()) == @@ -474,7 +1202,7 @@ public: std::vector v; for(char n = 0; n < 20; ++n) { - error_code ec; + error_code ec = test::error::fail_error; socket_type sock(ios_); sock.connect(ep, ec); if(! BEAST_EXPECTS(! ec, ec.message())) @@ -486,9 +1214,8 @@ public: ws.async_write(boost::asio::buffer(v), do_yield[ec]); if(! BEAST_EXPECTS(! ec, ec.message())) break; - opcode op; - streambuf db; - ws.async_read(op, db, do_yield[ec]); + multi_buffer db; + ws.async_read(db, do_yield[ec]); if(! BEAST_EXPECTS(! ec, ec.message())) break; BEAST_EXPECT(to_string(db.data()) == @@ -529,7 +1256,7 @@ public: } #if 0 - void testInvokable1(endpoint_type const& ep) + void testPausation1(endpoint_type const& ep) { boost::asio::io_service ios; stream ws(ios); @@ -537,7 +1264,7 @@ public: ws.handshake("localhost", "/"); // Make remote send a ping frame - ws.set_option(message_type(opcode::text)); + ws.binary(false); ws.write(buffer_cat(sbuf("PING"), sbuf("ping"))); std::size_t count = 0; @@ -551,10 +1278,9 @@ public: }); // Read - opcode op; - streambuf db; + multi_buffer db; ++count; - ws.async_read(op, db, + ws.async_read(db, [&](error_code ec) { --count; @@ -563,7 +1289,7 @@ public: while(! ws.wr_block_) ios.run_one(); // Write a text message, leaving - // the write_op suspended as invokable. + // the write_op suspended as pausation. ws.async_write(sbuf("Hello"), [&](error_code ec) { @@ -596,7 +1322,7 @@ public: } #endif - void testInvokable2(endpoint_type const& ep) + void testPausation2(endpoint_type const& ep) { boost::asio::io_service ios; stream ws(ios); @@ -604,15 +1330,14 @@ public: ws.handshake("localhost", "/"); // Make remote send a text message with bad utf8. - ws.set_option(message_type(opcode::binary)); + ws.binary(true); ws.write(buffer_cat(sbuf("TEXT"), cbuf(0x03, 0xea, 0xf0, 0x28, 0x8c, 0xbc))); - opcode op; - streambuf db; + multi_buffer db; std::size_t count = 0; // Read text message with bad utf8. // Causes a close to be sent, blocking writes. - ws.async_read(op, db, + ws.async_read(db, [&](error_code ec) { // Read should fail with protocol error @@ -620,7 +1345,7 @@ public: BEAST_EXPECTS( ec == error::failed, ec.message()); // Reads after failure are aborted - ws.async_read(op, db, + ws.async_read(db, [&](error_code ec) { ++count; @@ -633,7 +1358,7 @@ public: while(! ws.wr_block_) ios.run_one(); // Write a text message, leaving - // the write_op suspended as invokable. + // the write_op suspended as a pausation. ws.async_write(sbuf("Hello"), [&](error_code ec) { @@ -665,7 +1390,7 @@ public: ios.run(); } - void testInvokable3(endpoint_type const& ep) + void testPausation3(endpoint_type const& ep) { boost::asio::io_service ios; stream ws(ios); @@ -673,14 +1398,13 @@ public: ws.handshake("localhost", "/"); // Cause close to be received - ws.set_option(message_type(opcode::binary)); + ws.binary(true); ws.write(sbuf("CLOSE")); - opcode op; - streambuf db; + multi_buffer db; std::size_t count = 0; // Read a close frame. // Sends a close frame, blocking writes. - ws.async_read(op, db, + ws.async_read(db, [&](error_code ec) { // Read should complete with error::closed @@ -731,7 +1455,7 @@ public: ios.run(); } - void testInvokable4(endpoint_type const& ep) + void testPausation4(endpoint_type const& ep) { boost::asio::io_service ios; stream ws(ios); @@ -739,12 +1463,11 @@ public: ws.handshake("localhost", "/"); // Cause close to be received - ws.set_option(message_type(opcode::binary)); + ws.binary(true); ws.write(sbuf("CLOSE")); - opcode op; - streambuf db; + multi_buffer db; std::size_t count = 0; - ws.async_read(op, db, + ws.async_read(db, [&](error_code ec) { ++count; @@ -775,7 +1498,7 @@ public: } #if 0 - void testInvokable5(endpoint_type const& ep) + void testPausation5(endpoint_type const& ep) { boost::asio::io_service ios; stream ws(ios); @@ -792,9 +1515,8 @@ public: BEAST_EXPECT(! ec); }); }); - opcode op; - streambuf db; - ws.async_read(op, db, + multi_buffer db; + ws.async_read(db, [&](error_code ec) { BEAST_EXPECTS(ec == error::closed, ec.message()); @@ -824,9 +1546,8 @@ public: return; ws.write_frame(false, sbuf("u")); ws.write_frame(true, sbuf("v")); - streambuf sb; - opcode op; - ws.read(op, sb, ec); + multi_buffer b; + ws.read(b, ec); if(! BEAST_EXPECTS(! ec, ec.message())) return; } @@ -863,185 +1584,6 @@ public: } } - struct SyncClient - { - template - void - handshake(stream& ws, - boost::string_ref const& uri, - boost::string_ref const& path) const - { - ws.handshake(uri, path); - } - - template - void - ping(stream& ws, - ping_data const& payload) const - { - ws.ping(payload); - } - - template - void - pong(stream& ws, - ping_data const& payload) const - { - ws.pong(payload); - } - - template - void - close(stream& ws, - close_reason const& cr) const - { - ws.close(cr); - } - - template< - class NextLayer, class DynamicBuffer> - void - read(stream& ws, - opcode& op, DynamicBuffer& dynabuf) const - { - ws.read(op, dynabuf); - } - - template< - class NextLayer, class ConstBufferSequence> - void - write(stream& ws, - ConstBufferSequence const& buffers) const - { - ws.write(buffers); - } - - template< - class NextLayer, class ConstBufferSequence> - void - write_frame(stream& ws, bool fin, - ConstBufferSequence const& buffers) const - { - ws.write_frame(fin, buffers); - } - - template< - class NextLayer, class ConstBufferSequence> - void - write_raw(stream& ws, - ConstBufferSequence const& buffers) const - { - boost::asio::write( - ws.next_layer(), buffers); - } - }; - - class AsyncClient - { - yield_context& yield_; - - public: - explicit - AsyncClient(yield_context& yield) - : yield_(yield) - { - } - - template - void - handshake(stream& ws, - boost::string_ref const& uri, - boost::string_ref const& path) const - { - error_code ec; - ws.async_handshake(uri, path, yield_[ec]); - if(ec) - throw system_error{ec}; - } - - template - void - ping(stream& ws, - ping_data const& payload) const - { - error_code ec; - ws.async_ping(payload, yield_[ec]); - if(ec) - throw system_error{ec}; - } - - template - void - pong(stream& ws, - ping_data const& payload) const - { - error_code ec; - ws.async_pong(payload, yield_[ec]); - if(ec) - throw system_error{ec}; - } - - template - void - close(stream& ws, - close_reason const& cr) const - { - error_code ec; - ws.async_close(cr, yield_[ec]); - if(ec) - throw system_error{ec}; - } - - template< - class NextLayer, class DynamicBuffer> - void - read(stream& ws, - opcode& op, DynamicBuffer& dynabuf) const - { - error_code ec; - ws.async_read(op, dynabuf, yield_[ec]); - if(ec) - throw system_error{ec}; - } - - template< - class NextLayer, class ConstBufferSequence> - void - write(stream& ws, - ConstBufferSequence const& buffers) const - { - error_code ec; - ws.async_write(buffers, yield_[ec]); - if(ec) - throw system_error{ec}; - } - - template< - class NextLayer, class ConstBufferSequence> - void - write_frame(stream& ws, bool fin, - ConstBufferSequence const& buffers) const - { - error_code ec; - ws.async_write_frame(fin, buffers, yield_[ec]); - if(ec) - throw system_error{ec}; - } - - template< - class NextLayer, class ConstBufferSequence> - void - write_raw(stream& ws, - ConstBufferSequence const& buffers) const - { - error_code ec; - boost::asio::async_write( - ws.next_layer(), buffers, yield_[ec]); - if(ec) - throw system_error{ec}; - } - }; - struct abort_test { }; @@ -1063,9 +1605,8 @@ public: { try { - opcode op; - streambuf db; - c.read(ws, op, db); + multi_buffer db; + c.read(ws, db); fail(); throw abort_test{}; } @@ -1092,15 +1633,14 @@ public: c.handshake(ws, "localhost", "/"); // send message - ws.set_option(auto_fragment{false}); - ws.set_option(message_type(opcode::text)); + ws.auto_fragment(false); + ws.binary(false); c.write(ws, sbuf("Hello")); { // receive echoed message - opcode op; - streambuf db; - c.read(ws, op, db); - BEAST_EXPECT(op == opcode::text); + multi_buffer db; + c.read(ws, db); + BEAST_EXPECT(ws.got_text()); BEAST_EXPECT(to_string(db.data()) == "Hello"); } @@ -1116,116 +1656,116 @@ public: c.close(ws, {close_code::going_away, "Going away"}); restart(error::closed); + bool once; + // send ping and message - bool pong = false; - ws.set_option(ping_callback{ - [&](bool is_pong, ping_data const& payload) + once = false; + ws.control_callback( + [&](frame_type kind, string_view s) { - BEAST_EXPECT(is_pong); - BEAST_EXPECT(! pong); - pong = true; - BEAST_EXPECT(payload == ""); - }}); + BEAST_EXPECT(kind == frame_type::pong); + BEAST_EXPECT(! once); + once = true; + BEAST_EXPECT(s == ""); + }); c.ping(ws, ""); - ws.set_option(message_type(opcode::binary)); + ws.binary(true); c.write(ws, sbuf("Hello")); { // receive echoed message - opcode op; - streambuf db; - c.read(ws, op, db); - BEAST_EXPECT(pong == 1); - BEAST_EXPECT(op == opcode::binary); + multi_buffer db; + c.read(ws, db); + BEAST_EXPECT(once); + BEAST_EXPECT(ws.got_binary()); BEAST_EXPECT(to_string(db.data()) == "Hello"); } - ws.set_option(ping_callback{}); + ws.control_callback({}); // send ping and fragmented message - ws.set_option(ping_callback{ - [&](bool is_pong, ping_data const& payload) + once = false; + ws.control_callback( + [&](frame_type kind, string_view s) { - BEAST_EXPECT(is_pong); - BEAST_EXPECT(payload == "payload"); - }}); + BEAST_EXPECT(kind == frame_type::pong); + BEAST_EXPECT(! once); + once = true; + BEAST_EXPECT(s == "payload"); + }); ws.ping("payload"); c.write_frame(ws, false, sbuf("Hello, ")); c.write_frame(ws, false, sbuf("")); c.write_frame(ws, true, sbuf("World!")); { // receive echoed message - opcode op; - streambuf db; - c.read(ws, op, db); - BEAST_EXPECT(pong == 1); + multi_buffer db; + c.read(ws, db); + BEAST_EXPECT(once); BEAST_EXPECT(to_string(db.data()) == "Hello, World!"); } - ws.set_option(ping_callback{}); + ws.control_callback({}); // send pong c.pong(ws, ""); // send auto fragmented message - ws.set_option(auto_fragment{true}); - ws.set_option(write_buffer_size{8}); + ws.auto_fragment(true); + ws.write_buffer_size(8); c.write(ws, sbuf("Now is the time for all good men")); { // receive echoed message - opcode op; - streambuf sb; - c.read(ws, op, sb); - BEAST_EXPECT(to_string(sb.data()) == "Now is the time for all good men"); + multi_buffer b; + c.read(ws, b); + BEAST_EXPECT(to_string(b.data()) == "Now is the time for all good men"); } - ws.set_option(auto_fragment{false}); - ws.set_option(write_buffer_size{4096}); + ws.auto_fragment(false); + ws.write_buffer_size(4096); // send message with write buffer limit { std::string s(2000, '*'); - ws.set_option(write_buffer_size(1200)); + ws.write_buffer_size(1200); c.write(ws, buffer(s.data(), s.size())); { // receive echoed message - opcode op; - streambuf db; - c.read(ws, op, db); + multi_buffer db; + c.read(ws, db); BEAST_EXPECT(to_string(db.data()) == s); } } // cause ping - ws.set_option(message_type(opcode::binary)); + ws.binary(true); c.write(ws, sbuf("PING")); - ws.set_option(message_type(opcode::text)); + ws.binary(false); c.write(ws, sbuf("Hello")); { // receive echoed message - opcode op; - streambuf db; - c.read(ws, op, db); - BEAST_EXPECT(op == opcode::text); + multi_buffer db; + c.read(ws, db); + BEAST_EXPECT(ws.got_text()); BEAST_EXPECT(to_string(db.data()) == "Hello"); } // cause close - ws.set_option(message_type(opcode::binary)); + ws.binary(true); c.write(ws, sbuf("CLOSE")); restart(error::closed); // send bad utf8 - ws.set_option(message_type(opcode::binary)); + ws.binary(true); c.write(ws, buffer_cat(sbuf("TEXT"), cbuf(0x03, 0xea, 0xf0, 0x28, 0x8c, 0xbc))); restart(error::failed); // cause bad utf8 - ws.set_option(message_type(opcode::binary)); + ws.binary(true); c.write(ws, buffer_cat(sbuf("TEXT"), cbuf(0x03, 0xea, 0xf0, 0x28, 0x8c, 0xbc))); c.write(ws, sbuf("Hello")); restart(error::failed); // cause bad close - ws.set_option(message_type(opcode::binary)); + ws.binary(true); c.write(ws, buffer_cat(sbuf("RAW"), cbuf(0x88, 0x02, 0x03, 0xed))); restart(error::failed); @@ -1261,10 +1801,10 @@ public: restart(error::closed); // message size exceeds max - ws.set_option(read_message_max{1}); + ws.read_message_max(1); c.write(ws, cbuf(0x00, 0x00)); restart(error::failed); - ws.set_option(read_message_max{16*1024*1024}); + ws.read_message_max(16*1024*1024); } } catch(system_error const&) @@ -1276,25 +1816,26 @@ public: BEAST_EXPECT(n < limit); } - void run() override + void + run() override { - static_assert(std::is_constructible< - stream, boost::asio::io_service&>::value, ""); + BOOST_STATIC_ASSERT(std::is_constructible< + stream, boost::asio::io_service&>::value); - static_assert(std::is_move_constructible< - stream>::value, ""); + BOOST_STATIC_ASSERT(std::is_move_constructible< + stream>::value); - static_assert(std::is_move_assignable< - stream>::value, ""); + BOOST_STATIC_ASSERT(std::is_move_assignable< + stream>::value); - static_assert(std::is_constructible< - stream, socket_type&>::value, ""); + BOOST_STATIC_ASSERT(std::is_constructible< + stream, socket_type&>::value); - static_assert(std::is_move_constructible< - stream>::value, ""); + BOOST_STATIC_ASSERT(std::is_move_constructible< + stream>::value); - static_assert(! std::is_move_assignable< - stream>::value, ""); + BOOST_STATIC_ASSERT(! std::is_move_assignable< + stream>::value); log << "sizeof(websocket::stream) == " << sizeof(websocket::stream) << std::endl; @@ -1304,6 +1845,7 @@ public: testOptions(); testAccept(); + testHandshake(); testBadHandshakes(); testBadResponses(); @@ -1318,12 +1860,11 @@ public: server.open(any, ec); BEAST_EXPECTS(! ec, ec.message()); auto const ep = server.local_endpoint(); - testDecorator(ep); - //testInvokable1(ep); - testInvokable2(ep); - testInvokable3(ep); - testInvokable4(ep); - //testInvokable5(ep); + //testPausation1(ep); + testPausation2(ep); + testPausation3(ep); + testPausation4(ep); + //testPausation5(ep); testWriteFrames(ep); testAsyncWriteFrame(ep); } @@ -1376,6 +1917,7 @@ public: pmd.server_enable = false; doClientTests(pmd); + #if ! BEAST_NO_SLOW_TESTS pmd.client_enable = true; pmd.server_enable = true; pmd.client_max_window_bits = 10; @@ -1387,6 +1929,7 @@ public: pmd.client_max_window_bits = 10; pmd.client_no_context_takeover = true; doClientTests(pmd); + #endif } }; diff --git a/src/beast/test/websocket/utf8_checker.cpp b/src/beast/test/websocket/utf8_checker.cpp index 20dc3f37c2..dc01a33258 100644 --- a/src/beast/test/websocket/utf8_checker.cpp +++ b/src/beast/test/websocket/utf8_checker.cpp @@ -9,7 +9,7 @@ #include #include -#include +#include #include #include @@ -49,7 +49,7 @@ public: BEAST_EXPECT(! utf8.write(&(*it), 1)); // Invalid sequence - std::fill(buf.begin(), buf.end(), 0xFF); + std::fill(buf.begin(), buf.end(), '\xff'); BEAST_EXPECT(! utf8.write(&buf.front(), buf.size())); } @@ -379,15 +379,15 @@ public: consuming_buffers< boost::asio::const_buffers_1> cb{ boost::asio::const_buffers_1(s.data(), n)}; - streambuf sb{size}; + multi_buffer b; while(n) { auto const amount = (std::min)(n, size); - sb.commit(buffer_copy(sb.prepare(amount), cb)); + b.commit(buffer_copy(b.prepare(amount), cb)); cb.consume(amount); n -= amount; } - BEAST_EXPECT(utf8.write(sb.data())); + BEAST_EXPECT(utf8.write(b.data())); BEAST_EXPECT(utf8.finish()); } } @@ -403,7 +403,9 @@ public: } }; +#if defined(NDEBUG) && ! BEAST_NO_SLOW_TESTS BEAST_DEFINE_TESTSUITE(utf8_checker,websocket,beast); +#endif } // detail } // websocket diff --git a/src/beast/test/websocket/websocket_async_echo_server.hpp b/src/beast/test/websocket/websocket_async_echo_server.hpp index 4932e35e5f..9057e3cc56 100644 --- a/src/beast/test/websocket/websocket_async_echo_server.hpp +++ b/src/beast/test/websocket/websocket_async_echo_server.hpp @@ -8,8 +8,7 @@ #ifndef BEAST_WEBSOCKET_ASYNC_ECHO_SERVER_HPP #define BEAST_WEBSOCKET_ASYNC_ECHO_SERVER_HPP -#include -#include +#include #include #include #include @@ -38,25 +37,6 @@ public: using endpoint_type = boost::asio::ip::tcp::endpoint; private: - struct identity - { - template - void - operator()(beast::http::message< - true, Body, Fields>& req) const - { - req.fields.replace("User-Agent", "async_echo_client"); - } - - template - void - operator()(beast::http::message< - false, Body, Fields>& resp) const - { - resp.fields.replace("Server", "async_echo_server"); - } - }; - /** A container of type-erased option setters. */ template @@ -159,8 +139,6 @@ public: , acceptor_(ios_) , work_(ios_) { - opts_.set_option( - beast::websocket::decorate(identity{})); thread_.reserve(threads); for(std::size_t i = 0; i < threads; ++i) thread_.emplace_back( @@ -223,7 +201,7 @@ public: return fail("listen", ec); acceptor_.async_accept(sock_, ep_, std::bind(&async_echo_server::on_accept, this, - beast::asio::placeholders::error)); + std::placeholders::_1)); } private: @@ -236,8 +214,7 @@ private: int state = 0; beast::websocket::stream ws; boost::asio::io_service::strand strand; - beast::websocket::opcode op; - beast::streambuf db; + beast::multi_buffer db; std::size_t id; data(async_echo_server& server_, @@ -282,7 +259,13 @@ private: void run() { auto& d = *d_; - d.ws.async_accept(std::move(*this)); + d.ws.async_accept_ex( + [](beast::websocket::response_type& res) + { + res.insert( + "Server", "async_echo_server"); + }, + std::move(*this)); } template @@ -328,7 +311,7 @@ private: d.db.consume(d.db.size()); // read message d.state = 2; - d.ws.async_read(d.op, d.db, + d.ws.async_read(d.db, d.strand.wrap(std::move(*this))); return; @@ -348,9 +331,7 @@ private: else if(match(d.db, "TEXT")) { d.state = 1; - d.ws.set_option( - beast::websocket::message_type{ - beast::websocket::opcode::text}); + d.ws.binary(false); d.ws.async_write( d.db.data(), d.strand.wrap(std::move(*this))); return; @@ -375,8 +356,7 @@ private: } // write message d.state = 1; - d.ws.set_option( - beast::websocket::message_type(d.op)); + d.ws.binary(d.ws.got_binary()); d.ws.async_write(d.db.data(), d.strand.wrap(std::move(*this))); return; @@ -420,7 +400,7 @@ private: peer{*this, ep_, std::move(sock_)}; acceptor_.async_accept(sock_, ep_, std::bind(&async_echo_server::on_accept, this, - beast::asio::placeholders::error)); + std::placeholders::_1)); } }; diff --git a/src/beast/test/websocket/websocket_sync_echo_server.hpp b/src/beast/test/websocket/websocket_sync_echo_server.hpp index 8f1dbf0757..ab4384e9ad 100644 --- a/src/beast/test/websocket/websocket_sync_echo_server.hpp +++ b/src/beast/test/websocket/websocket_sync_echo_server.hpp @@ -8,8 +8,7 @@ #ifndef BEAST_WEBSOCKET_SYNC_ECHO_SERVER_HPP #define BEAST_WEBSOCKET_SYNC_ECHO_SERVER_HPP -#include -#include +#include #include #include #include @@ -38,25 +37,6 @@ public: using socket_type = boost::asio::ip::tcp::socket; private: - struct identity - { - template - void - operator()(beast::http::message< - true, Body, Fields>& req) const - { - req.fields.replace("User-Agent", "sync_echo_client"); - } - - template - void - operator()(beast::http::message< - false, Body, Fields>& resp) const - { - resp.fields.replace("Server", "sync_echo_server"); - } - }; - /** A container of type-erased option setters. */ template @@ -151,8 +131,6 @@ public: , sock_(ios_) , acceptor_(ios_) { - opts_.set_option( - beast::websocket::decorate(identity{})); } /** Destructor. @@ -212,7 +190,7 @@ public: return fail("listen", ec); acceptor_.async_accept(sock_, ep_, std::bind(&sync_echo_server::on_accept, this, - beast::asio::placeholders::error)); + std::placeholders::_1)); thread_ = std::thread{[&]{ ios_.run(); }}; } @@ -231,7 +209,7 @@ private: void fail(std::string what, error_code ec, - int id, endpoint_type const& ep) + std::size_t id, endpoint_type const& ep) { if(log_) if(ec != beast::websocket::error::closed) @@ -280,7 +258,7 @@ private: std::thread{lambda{*this, ep_, std::move(sock_)}}.detach(); acceptor_.async_accept(sock_, ep_, std::bind(&sync_echo_server::on_accept, this, - beast::asio::placeholders::error)); + std::placeholders::_1)); } template @@ -312,7 +290,13 @@ private: socket_type> ws{std::move(sock)}; opts_.set_options(ws); error_code ec; - ws.accept(ec); + ws.accept_ex( + [](beast::websocket::response_type& res) + { + res.insert( + "Server", "sync_echo_server"); + }, + ec); if(ec) { fail("accept", ec, id, ep); @@ -320,42 +304,39 @@ private: } for(;;) { - beast::websocket::opcode op; - beast::streambuf sb; - ws.read(op, sb, ec); + beast::multi_buffer b; + ws.read(b, ec); if(ec) { auto const s = ec.message(); break; } - ws.set_option(beast::websocket::message_type{op}); - if(match(sb, "RAW")) + ws.binary(ws.got_binary()); + if(match(b, "RAW")) { boost::asio::write( - ws.next_layer(), sb.data(), ec); + ws.next_layer(), b.data(), ec); } - else if(match(sb, "TEXT")) + else if(match(b, "TEXT")) { - ws.set_option( - beast::websocket::message_type{ - beast::websocket::opcode::text}); - ws.write(sb.data(), ec); + ws.binary(false); + ws.write(b.data(), ec); } - else if(match(sb, "PING")) + else if(match(b, "PING")) { beast::websocket::ping_data payload; - sb.consume(buffer_copy( + b.consume(buffer_copy( buffer(payload.data(), payload.size()), - sb.data())); + b.data())); ws.ping(payload, ec); } - else if(match(sb, "CLOSE")) + else if(match(b, "CLOSE")) { ws.close({}, ec); } else { - ws.write(sb.data(), ec); + ws.write(b.data(), ec); } if(ec) break; diff --git a/src/beast/test/zlib/CMakeLists.txt b/src/beast/test/zlib/CMakeLists.txt index 4d955e26ad..6d3c6f507e 100644 --- a/src/beast/test/zlib/CMakeLists.txt +++ b/src/beast/test/zlib/CMakeLists.txt @@ -2,8 +2,32 @@ GroupSources(extras/beast extras) GroupSources(include/beast beast) + GroupSources(test/zlib "/") +set(ZLIB_SOURCES + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/crc32.h + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/deflate.h + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/inffast.h + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/inffixed.h + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/inflate.h + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/inftrees.h + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/trees.h + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/zlib.h + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/zutil.h + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/adler32.c + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/compress.c + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/crc32.c + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/deflate.c + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/infback.c + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/inffast.c + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/inflate.c + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/inftrees.c + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/trees.c + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/uncompr.c + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/zutil.c +) + if (MSVC) set_source_files_properties (${ZLIB_SOURCES} PROPERTIES COMPILE_FLAGS "/wd4127 /wd4131 /wd4244") endif() @@ -19,8 +43,8 @@ add_executable (zlib-tests inflate_stream.cpp ) -if (NOT WIN32) - target_link_libraries(zlib-tests ${Boost_LIBRARIES} Threads::Threads) -else() - target_link_libraries(zlib-tests ${Boost_LIBRARIES}) -endif() +target_link_libraries(zlib-tests + Beast + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ) diff --git a/src/beast/test/zlib/Jamfile b/src/beast/test/zlib/Jamfile new file mode 100644 index 0000000000..deea895cc0 --- /dev/null +++ b/src/beast/test/zlib/Jamfile @@ -0,0 +1,24 @@ +# +# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +# + +unit-test zlib-tests : + ../../extras/beast/unit_test/main.cpp + zlib-1.2.8/adler32.c + zlib-1.2.8/compress.c + zlib-1.2.8/crc32.c + zlib-1.2.8/deflate.c + zlib-1.2.8/infback.c + zlib-1.2.8/inffast.c + zlib-1.2.8/inflate.c + zlib-1.2.8/inftrees.c + zlib-1.2.8/trees.c + zlib-1.2.8/uncompr.c + zlib-1.2.8/zutil.c + deflate_stream.cpp + error.cpp + inflate_stream.cpp + ; diff --git a/src/beast/test/zlib/deflate_stream.cpp b/src/beast/test/zlib/deflate_stream.cpp index a827659253..c1b2b38f58 100644 --- a/src/beast/test/zlib/deflate_stream.cpp +++ b/src/beast/test/zlib/deflate_stream.cpp @@ -305,6 +305,7 @@ public: { doMatrix("1.beast ", "Hello, world!", &self::doDeflate1_beast); doMatrix("1.zlib ", "Hello, world!", &self::doDeflate1_zlib); + #if ! BEAST_NO_SLOW_TESTS doMatrix("2.beast ", "Hello, world!", &self::doDeflate2_beast); doMatrix("2.zlib ", "Hello, world!", &self::doDeflate2_zlib); { @@ -317,6 +318,7 @@ public: doMatrix("4.beast ", s, &self::doDeflate1_beast); doMatrix("4.zlib ", s, &self::doDeflate1_zlib); } + #endif } void diff --git a/src/beast/test/zlib/error.cpp b/src/beast/test/zlib/error.cpp index 8623a38edf..eb40185dfb 100644 --- a/src/beast/test/zlib/error.cpp +++ b/src/beast/test/zlib/error.cpp @@ -34,24 +34,24 @@ public: void run() override { - check("zlib", error::need_buffers); - check("zlib", error::end_of_stream); - check("zlib", error::stream_error); + check("beast.zlib", error::need_buffers); + check("beast.zlib", error::end_of_stream); + check("beast.zlib", error::stream_error); - check("zlib", error::invalid_block_type); - check("zlib", error::invalid_stored_length); - check("zlib", error::too_many_symbols); - check("zlib", error::invalid_code_lenths); - check("zlib", error::invalid_bit_length_repeat); - check("zlib", error::missing_eob); - check("zlib", error::invalid_literal_length); - check("zlib", error::invalid_distance_code); - check("zlib", error::invalid_distance); + check("beast.zlib", error::invalid_block_type); + check("beast.zlib", error::invalid_stored_length); + check("beast.zlib", error::too_many_symbols); + check("beast.zlib", error::invalid_code_lenths); + check("beast.zlib", error::invalid_bit_length_repeat); + check("beast.zlib", error::missing_eob); + check("beast.zlib", error::invalid_literal_length); + check("beast.zlib", error::invalid_distance_code); + check("beast.zlib", error::invalid_distance); - check("zlib", error::over_subscribed_length); - check("zlib", error::incomplete_length_set); + check("beast.zlib", error::over_subscribed_length); + check("beast.zlib", error::incomplete_length_set); - check("zlib", error::general); + check("beast.zlib", error::general); } }; diff --git a/src/beast/test/zlib/inflate_stream.cpp b/src/beast/test/zlib/inflate_stream.cpp index 31f14ac582..1af12d7f88 100644 --- a/src/beast/test/zlib/inflate_stream.cpp +++ b/src/beast/test/zlib/inflate_stream.cpp @@ -325,6 +325,8 @@ public: m("1. beast", Beast{half, half}, check); m("1. zlib ", ZLib {half, half}, check); } + + #if ! BEAST_NO_SLOW_TESTS { Matrix m{*this}; auto const check = corpus1(50000); @@ -380,6 +382,7 @@ public: m("8. beast", Beast{full, once, Flush::block}, check); m("8. zlib ", ZLib {full, once, Z_BLOCK}, check); } + #endif // VFALCO Fails, but I'm unsure of what the correct // behavior of Z_TREES/Flush::trees is. diff --git a/src/ripple/app/consensus/RCLConsensus.cpp b/src/ripple/app/consensus/RCLConsensus.cpp index f5b240ebc9..94d4ace251 100644 --- a/src/ripple/app/consensus/RCLConsensus.cpp +++ b/src/ripple/app/consensus/RCLConsensus.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -29,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -47,21 +49,45 @@ RCLConsensus::RCLConsensus( LedgerMaster& ledgerMaster, LocalTxs& localTxs, InboundTransactions& inboundTransactions, - typename Base::clock_type const& clock, + Consensus::clock_type const& clock, + ValidatorKeys const& validatorKeys, beast::Journal journal) - : Base(clock, journal) - , app_(app) - , feeVote_(std::move(feeVote)) - , ledgerMaster_(ledgerMaster) - , localTxs_(localTxs) - , inboundTransactions_{inboundTransactions} + : adaptor_( + app, + std::move(feeVote), + ledgerMaster, + localTxs, + inboundTransactions, + validatorKeys, + journal) + , consensus_(clock, adaptor_, journal) , j_(journal) - , nodeID_{calcNodeID(app.nodeIdentity().first)} + +{ +} + +RCLConsensus::Adaptor::Adaptor( + Application& app, + std::unique_ptr&& feeVote, + LedgerMaster& ledgerMaster, + LocalTxs& localTxs, + InboundTransactions& inboundTransactions, + ValidatorKeys const& validatorKeys, + beast::Journal journal) + : app_(app) + , feeVote_(std::move(feeVote)) + , ledgerMaster_(ledgerMaster) + , localTxs_(localTxs) + , inboundTransactions_{inboundTransactions} + , j_(journal) + , nodeID_{calcNodeID(app.nodeIdentity().first)} + , valPublic_{validatorKeys.publicKey} + , valSecret_{validatorKeys.secretKey} { } boost::optional -RCLConsensus::acquireLedger(LedgerHash const& ledger) +RCLConsensus::Adaptor::acquireLedger(LedgerHash const& ledger) { // we need to switch the ledger we're working from auto buildLCL = ledgerMaster_.getLedgerByHash(ledger); @@ -95,37 +121,9 @@ RCLConsensus::acquireLedger(LedgerHash const& ledger) return RCLCxLedger(buildLCL); } -std::vector -RCLConsensus::proposals(LedgerHash const& prevLedger) -{ - std::vector ret; - { - std::lock_guard _(peerPositionsLock_); - - for (auto const& it : peerPositions_) - for (auto const& pos : it.second) - if (pos->proposal().prevLedger() == prevLedger) - ret.emplace_back(*pos); - } - - return ret; -} void -RCLConsensus::storeProposal(RCLCxPeerPos::ref peerPos, NodeID const& nodeID) -{ - std::lock_guard _(peerPositionsLock_); - - auto& props = peerPositions_[nodeID]; - - if (props.size() >= 10) - props.pop_front(); - - props.push_back(peerPos); -} - -void -RCLConsensus::relay(RCLCxPeerPos const& peerPos) +RCLConsensus::Adaptor::relay(RCLCxPeerPos const& peerPos) { protocol::TMProposeSet prop; @@ -139,17 +137,17 @@ RCLConsensus::relay(RCLCxPeerPos const& peerPos) prop.set_previousledger( proposal.prevLedger().begin(), proposal.position().size()); - auto const pk = peerPos.getPublicKey().slice(); + auto const pk = peerPos.publicKey().slice(); prop.set_nodepubkey(pk.data(), pk.size()); - auto const sig = peerPos.getSignature(); + auto const sig = peerPos.signature(); prop.set_signature(sig.data(), sig.size()); - app_.overlay().relay(prop, peerPos.getSuppressionID()); + app_.overlay().relay(prop, peerPos.suppressionID()); } void -RCLConsensus::relay(RCLCxTx const& tx) +RCLConsensus::Adaptor::relay(RCLCxTx const& tx) { // If we didn't relay this transaction recently, relay it to all peers if (app_.getHashRouter().shouldRelay(tx.id())) @@ -170,7 +168,7 @@ RCLConsensus::relay(RCLCxTx const& tx) } } void -RCLConsensus::propose(RCLCxPeerPos::Proposal const& proposal) +RCLConsensus::Adaptor::propose(RCLCxPeerPos::Proposal const& proposal) { JLOG(j_.trace()) << "We propose: " << (proposal.isBowOut() @@ -203,13 +201,13 @@ RCLConsensus::propose(RCLCxPeerPos::Proposal const& proposal) } void -RCLConsensus::relay(RCLTxSet const& set) +RCLConsensus::Adaptor::relay(RCLTxSet const& set) { inboundTransactions_.giveSet(set.id(), set.map_, false); } boost::optional -RCLConsensus::acquireTxSet(RCLTxSet::ID const& setId) +RCLConsensus::Adaptor::acquireTxSet(RCLTxSet::ID const& setId) { if (auto set = inboundTransactions_.getSet(setId, true)) { @@ -219,77 +217,77 @@ RCLConsensus::acquireTxSet(RCLTxSet::ID const& setId) } bool -RCLConsensus::hasOpenTransactions() const +RCLConsensus::Adaptor::hasOpenTransactions() const { return !app_.openLedger().empty(); } std::size_t -RCLConsensus::proposersValidated(LedgerHash const& h) const +RCLConsensus::Adaptor::proposersValidated(LedgerHash const& h) const { - return app_.getValidations().getTrustedValidationCount(h); + return app_.getValidations().numTrustedForLedger(h); } std::size_t -RCLConsensus::proposersFinished(LedgerHash const& h) const +RCLConsensus::Adaptor::proposersFinished(LedgerHash const& h) const { return app_.getValidations().getNodesAfter(h); } uint256 -RCLConsensus::getPrevLedger( +RCLConsensus::Adaptor::getPrevLedger( uint256 ledgerID, RCLCxLedger const& ledger, - Mode mode) + ConsensusMode mode) { uint256 parentID; // Only set the parent ID if we believe ledger is the right ledger - if (mode != Mode::wrongLedger) + if (mode != ConsensusMode::wrongLedger) parentID = ledger.parentID(); // Get validators that are on our ledger, or "close" to being on // our ledger. - auto vals = app_.getValidations().getCurrentValidations( - ledgerID, parentID, ledgerMaster_.getValidLedgerIndex()); + hash_map ledgerCounts = + app_.getValidations().currentTrustedDistribution( + ledgerID, parentID, ledgerMaster_.getValidLedgerIndex()); uint256 netLgr = ledgerID; int netLgrCount = 0; - for (auto& it : vals) + for (auto const & it : ledgerCounts) { // Switch to ledger supported by more peers // Or stick with ours on a tie - if ((it.second.first > netLgrCount) || - ((it.second.first == netLgrCount) && (it.first == ledgerID))) + if ((it.second > netLgrCount) || + ((it.second == netLgrCount) && (it.first == ledgerID))) { netLgr = it.first; - netLgrCount = it.second.first; + netLgrCount = it.second; } } if (netLgr != ledgerID) { - if (mode != Mode::wrongLedger) + if (mode != ConsensusMode::wrongLedger) app_.getOPs().consensusViewChange(); if (auto stream = j_.debug()) { - for (auto& it : vals) - stream << "V: " << it.first << ", " << it.second.first; - stream << getJson(true); + for (auto const & it : ledgerCounts) + stream << "V: " << it.first << ", " << it.second; } } return netLgr; } -RCLConsensus::Result -RCLConsensus::onClose( +auto +RCLConsensus::Adaptor::onClose( RCLCxLedger const& ledger, NetClock::time_point const& closeTime, - Mode mode) + ConsensusMode mode) -> Result { - const bool wrongLCL = mode == Mode::wrongLedger; - const bool proposing = mode == Mode::proposing; + const bool wrongLCL = mode == ConsensusMode::wrongLedger; + const bool proposing = mode == ConsensusMode::proposing; notify(protocol::neCLOSING_LEDGER, ledger, !wrongLCL); @@ -324,14 +322,10 @@ RCLConsensus::onClose( { // previous ledger was flag ledger, add pseudo-transactions auto const validations = - app_.getValidations().getValidations(prevLedger->info().parentHash); + app_.getValidations().getTrustedForLedger ( + prevLedger->info().parentHash); - std::size_t const count = std::count_if( - validations.begin(), validations.end(), [](auto const& v) { - return v.second->isTrusted(); - }); - - if (count >= app_.validators().quorum()) + if (validations.size() >= app_.validators ().quorum ()) { feeVote_->doVoting(prevLedger, validations, initialSet); app_.getAmendmentTable().doVoting( @@ -355,47 +349,68 @@ RCLConsensus::onClose( } void -RCLConsensus::onForceAccept( +RCLConsensus::Adaptor::onForceAccept( Result const& result, RCLCxLedger const& prevLedger, NetClock::duration const& closeResolution, - CloseTimes const& rawCloseTimes, - Mode const& mode) + ConsensusCloseTimes const& rawCloseTimes, + ConsensusMode const& mode, + Json::Value && consensusJson) { - doAccept(result, prevLedger, closeResolution, rawCloseTimes, mode); + doAccept( + result, + prevLedger, + closeResolution, + rawCloseTimes, + mode, + std::move(consensusJson)); } void -RCLConsensus::onAccept( +RCLConsensus::Adaptor::onAccept( Result const& result, RCLCxLedger const& prevLedger, NetClock::duration const& closeResolution, - CloseTimes const& rawCloseTimes, - Mode const& mode) + ConsensusCloseTimes const& rawCloseTimes, + ConsensusMode const& mode, + Json::Value && consensusJson) { app_.getJobQueue().addJob( - jtACCEPT, "acceptLedger", [&, that = this->shared_from_this() ](auto&) { - // note that no lock is held inside this thread, which - // is fine since once a ledger is accepted, consensus - // will not touch any internal state until startRound is called - that->doAccept( - result, prevLedger, closeResolution, rawCloseTimes, mode); - that->app_.getOPs().endConsensus(); + jtACCEPT, + "acceptLedger", + [&, cj = std::move(consensusJson) ](auto&) mutable { + // Note that no lock is held or acquired during this job. + // This is because generic Consensus guarantees that once a ledger + // is accepted, the consensus results and capture by reference state + // will not change until startRound is called (which happens via + // endConsensus). + this->doAccept( + result, + prevLedger, + closeResolution, + rawCloseTimes, + mode, + std::move(cj)); + this->app_.getOPs().endConsensus(); }); } void -RCLConsensus::doAccept( +RCLConsensus::Adaptor::doAccept( Result const& result, RCLCxLedger const& prevLedger, NetClock::duration closeResolution, - CloseTimes const& rawCloseTimes, - Mode const& mode) + ConsensusCloseTimes const& rawCloseTimes, + ConsensusMode const& mode, + Json::Value && consensusJson) { + prevProposers_ = result.proposers; + prevRoundTime_ = result.roundTime.read(); + bool closeTimeCorrect; - const bool proposing = mode == Mode::proposing; - const bool haveCorrectLCL = mode != Mode::wrongLedger; + const bool proposing = mode == ConsensusMode::proposing; + const bool haveCorrectLCL = mode != ConsensusMode::wrongLedger; const bool consensusFail = result.state == ConsensusState::MovedOn; auto consensusCloseTime = result.position.closeTime(); @@ -456,7 +471,7 @@ RCLConsensus::doAccept( JLOG(j_.info()) << "CNF buildLCL " << newLCLHash; // See if we can accept a ledger as fully-validated - ledgerMaster_.consensusBuilt(sharedLCL.ledger_, getJson(true)); + ledgerMaster_.consensusBuilt(sharedLCL.ledger_, std::move(consensusJson)); //------------------------------------------------------------------------- { @@ -547,7 +562,7 @@ RCLConsensus::doAccept( // we entered the round with the network, // see how close our close time is to other node's // close time reports, and update our clock. - if ((mode == Mode::proposing || mode == Mode::observing) && !consensusFail) + if ((mode == ConsensusMode::proposing || mode == ConsensusMode::observing) && !consensusFail) { auto closeTime = rawCloseTimes.self; @@ -586,7 +601,7 @@ RCLConsensus::doAccept( } void -RCLConsensus::notify( +RCLConsensus::Adaptor::notify( protocol::NodeEvent ne, RCLCxLedger const& ledger, bool haveCorrectLCL) @@ -722,7 +737,7 @@ applyTransactions( } RCLCxLedger -RCLConsensus::buildLCL( +RCLConsensus::Adaptor::buildLCL( RCLCxLedger const& previousLedger, RCLTxSet const& set, NetClock::time_point closeTime, @@ -818,7 +833,7 @@ RCLConsensus::buildLCL( } void -RCLConsensus::validate(RCLCxLedger const& ledger, bool proposing) +RCLConsensus::Adaptor::validate(RCLCxLedger const& ledger, bool proposing) { auto validationTime = app_.timeKeeper().closeTime(); if (validationTime <= lastValidationTime_) @@ -850,7 +865,7 @@ RCLConsensus::validate(RCLCxLedger const& ledger, bool proposing) v->setTrusted(); // suppress it if we receive it - FIXME: wrong suppression app_.getHashRouter().addSuppression(signingHash); - app_.getValidations().addValidation(v, "local"); + handleNewValidation(app_, v, "local"); Blob validation = v->getSerialized(); protocol::TMValidation val; val.set_validation(&validation[0], validation.size()); @@ -861,37 +876,26 @@ RCLConsensus::validate(RCLCxLedger const& ledger, bool proposing) Json::Value RCLConsensus::getJson(bool full) const { - auto ret = Base::getJson(full); - ret["validating"] = validating_; + Json::Value ret; + { + ScopedLockType _{mutex_}; + ret = consensus_.getJson(full); + } + ret["validating"] = adaptor_.validating(); return ret; } -PublicKey const& -RCLConsensus::getValidationPublicKey() const -{ - return valPublic_; -} - -void -RCLConsensus::setValidationKeys( - SecretKey const& valSecret, - PublicKey const& valPublic) -{ - valSecret_ = valSecret; - valPublic_ = valPublic; -} - void RCLConsensus::timerEntry(NetClock::time_point const& now) { try { - Base::timerEntry(now); + ScopedLockType _{mutex_}; + consensus_.timerEntry(now); } catch (SHAMapMissingNode const& mn) { // This should never happen - leaveConsensus(); JLOG(j_.error()) << "Missing node during consensus process " << mn; Rethrow(); } @@ -902,30 +906,46 @@ RCLConsensus::gotTxSet(NetClock::time_point const& now, RCLTxSet const& txSet) { try { - Base::gotTxSet(now, txSet); + ScopedLockType _{mutex_}; + consensus_.gotTxSet(now, txSet); } catch (SHAMapMissingNode const& mn) { // This should never happen - leaveConsensus(); JLOG(j_.error()) << "Missing node during consensus process " << mn; Rethrow(); } } -void -RCLConsensus::startRound( - NetClock::time_point const& now, - RCLCxLedger::ID const& prevLgrId, - RCLCxLedger const& prevLgr) -{ - // We have a key, and we have some idea what the ledger is - validating_ = - !app_.getOPs().isNeedNetworkLedger() && (valPublic_.size() != 0); - // propose only if we're in sync with the network (and validating) - bool proposing = - validating_ && (app_.getOPs().getOperatingMode() == NetworkOPs::omFULL); +//! @see Consensus::simulate + +void +RCLConsensus::simulate( + NetClock::time_point const& now, + boost::optional consensusDelay) +{ + ScopedLockType _{mutex_}; + consensus_.simulate(now, consensusDelay); +} + +bool +RCLConsensus::peerProposal( + NetClock::time_point const& now, + RCLCxPeerPos const& newProposal) +{ + ScopedLockType _{mutex_}; + return consensus_.peerProposal(now, newProposal); +} + +bool +RCLConsensus::Adaptor::preStartRound(RCLCxLedger const & prevLgr) +{ + // We have a key and do not want out of sync validations after a restart, + // and are not amendment blocked. + validating_ = valPublic_.size() != 0 && + prevLgr.seq() >= app_.getMaxDisallowedLedger() && + !app_.getOPs().isAmendmentBlocked(); if (validating_) { @@ -940,6 +960,19 @@ RCLConsensus::startRound( // Notify inbOund ledgers that we are starting a new round inboundTransactions_.newRound(prevLgr.seq()); - Base::startRound(now, prevLgrId, prevLgr, proposing); + // propose only if we're in sync with the network (and validating) + return validating_ && + (app_.getOPs().getOperatingMode() == NetworkOPs::omFULL); +} + +void +RCLConsensus::startRound( + NetClock::time_point const& now, + RCLCxLedger::ID const& prevLgrId, + RCLCxLedger const& prevLgr) +{ + ScopedLockType _{mutex_}; + consensus_.startRound( + now, prevLgrId, prevLgr, adaptor_.preStartRound(prevLgr)); } } diff --git a/src/ripple/app/consensus/RCLConsensus.h b/src/ripple/app/consensus/RCLConsensus.h index 26478faee6..5522b8d4e8 100644 --- a/src/ripple/app/consensus/RCLConsensus.h +++ b/src/ripple/app/consensus/RCLConsensus.h @@ -34,34 +34,332 @@ #include #include #include +#include +#include namespace ripple { class InboundTransactions; class LocalTxs; class LedgerMaster; +class ValidatorKeys; -//! Types used to adapt consensus for RCL -struct RCLCxTraits -{ - //! Ledger type presented to Consensus - using Ledger_t = RCLCxLedger; - //! Peer identifier type used in Consensus - using NodeID_t = NodeID; - //! TxSet type presented to Consensus - using TxSet_t = RCLTxSet; -}; - -/** Adapts the generic Consensus algorithm for use by RCL. - - @note The enabled_shared_from_this base allows the application to properly - create a shared instance of RCLConsensus for use in the accept logic.. +/** Manages the generic consensus algorithm for use by the RCL. */ -class RCLConsensus final : public Consensus, - public std::enable_shared_from_this, - public CountedObject +class RCLConsensus { - using Base = Consensus; + // Implements the Adaptor template interface required by Consensus. + class Adaptor + { + Application& app_; + std::unique_ptr feeVote_; + LedgerMaster& ledgerMaster_; + LocalTxs& localTxs_; + InboundTransactions& inboundTransactions_; + beast::Journal j_; + + NodeID const nodeID_; + PublicKey const valPublic_; + SecretKey const valSecret_; + + // Ledger we most recently needed to acquire + LedgerHash acquiringLedger_; + ConsensusParms parms_; + + // The timestamp of the last validation we used + NetClock::time_point lastValidationTime_; + + // These members are queried via public accesors and are atomic for + // thread safety. + std::atomic validating_{false}; + std::atomic prevProposers_{0}; + std::atomic prevRoundTime_{ + std::chrono::milliseconds{0}}; + std::atomic mode_{ConsensusMode::observing}; + + public: + using Ledger_t = RCLCxLedger; + using NodeID_t = NodeID; + using TxSet_t = RCLTxSet; + using PeerPosition_t = RCLCxPeerPos; + + using Result = ConsensusResult; + + Adaptor( + Application& app, + std::unique_ptr&& feeVote, + LedgerMaster& ledgerMaster, + LocalTxs& localTxs, + InboundTransactions& inboundTransactions, + ValidatorKeys const & validatorKeys, + beast::Journal journal); + + bool + validating() const + { + return validating_; + } + + std::size_t + prevProposers() const + { + return prevProposers_; + } + + std::chrono::milliseconds + prevRoundTime() const + { + return prevRoundTime_; + } + + ConsensusMode + mode() const + { + return mode_; + } + + /** Called before kicking off a new consensus round. + + @param prevLedger Ledger that will be prior ledger for next round + @return Whether we enter the round proposing + */ + bool + preStartRound(RCLCxLedger const & prevLedger); + + /** Consensus simulation parameters + */ + ConsensusParms const& + parms() const + { + return parms_; + } + + private: + //--------------------------------------------------------------------- + // The following members implement the generic Consensus requirements + // and are marked private to indicate ONLY Consensus will call + // them (via friendship). Since they are callled only from Consenus + // methods and since RCLConsensus::consensus_ should only be accessed + // under lock, these will only be called under lock. + // + // In general, the idea is that there is only ONE thread that is running + // consensus code at anytime. The only special case is the dispatched + // onAccept call, which does not take a lock and relies on Consensus not + // changing state until a future call to startRound. + friend class Consensus; + + /** Attempt to acquire a specific ledger. + + If not available, asynchronously acquires from the network. + + @param ledger The ID/hash of the ledger acquire + @return Optional ledger, will be seated if we locally had the ledger + */ + boost::optional + acquireLedger(LedgerHash const& ledger); + + /** Relay the given proposal to all peers + + @param peerPos The peer position to relay. + */ + void + relay(RCLCxPeerPos const& peerPos); + + /** Relay disputed transacction to peers. + + Only relay if the provided transaction hasn't been shared recently. + + @param tx The disputed transaction to relay. + */ + void + relay(RCLCxTx const& tx); + + /** Acquire the transaction set associated with a proposal. + + If the transaction set is not available locally, will attempt + acquire it from the network. + + @param setId The transaction set ID associated with the proposal + @return Optional set of transactions, seated if available. + */ + boost::optional + acquireTxSet(RCLTxSet::ID const& setId); + + /** Whether the open ledger has any transactions + */ + bool + hasOpenTransactions() const; + + /** Number of proposers that have vallidated the given ledger + + @param h The hash of the ledger of interest + @return the number of proposers that validated a ledger + */ + std::size_t + proposersValidated(LedgerHash const& h) const; + + /** Number of proposers that have validated a ledger descended from + requested ledger. + + @param h The hash of the ledger of interest. + @return The number of validating peers that have validated a ledger + succeeding the one provided. + */ + std::size_t + proposersFinished(LedgerHash const& h) const; + + /** Propose the given position to my peers. + + @param proposal Our proposed position + */ + void + propose(RCLCxPeerPos::Proposal const& proposal); + + /** Relay the given tx set to peers. + + @param set The TxSet to share. + */ + void + relay(RCLTxSet const& set); + + /** Get the ID of the previous ledger/last closed ledger(LCL) on the + network + + @param ledgerID ID of previous ledger used by consensus + @param ledger Previous ledger consensus has available + @param mode Current consensus mode + @return The id of the last closed network + + @note ledgerID may not match ledger.id() if we haven't acquired + the ledger matching ledgerID from the network + */ + uint256 + getPrevLedger( + uint256 ledgerID, + RCLCxLedger const& ledger, + ConsensusMode mode); + + void + onModeChange(ConsensusMode before, ConsensusMode after) + { + mode_ = after; + } + + /** Close the open ledger and return initial consensus position. + + @param ledger the ledger we are changing to + @param closeTime When consensus closed the ledger + @param mode Current consensus mode + @return Tentative consensus result + */ + Result + onClose( + RCLCxLedger const& ledger, + NetClock::time_point const& closeTime, + ConsensusMode mode); + + /** Process the accepted ledger. + + @param result The result of consensus + @param prevLedger The closed ledger consensus worked from + @param closeResolution The resolution used in agreeing on an + effective closeTime + @param rawCloseTimes The unrounded closetimes of ourself and our + peers + @param mode Our participating mode at the time consensus was + declared + @param consensusJson Json representation of consensus state + */ + void + onAccept( + Result const& result, + RCLCxLedger const& prevLedger, + NetClock::duration const& closeResolution, + ConsensusCloseTimes const& rawCloseTimes, + ConsensusMode const& mode, + Json::Value&& consensusJson); + + /** Process the accepted ledger that was a result of simulation/force + accept. + + @ref onAccept + */ + void + onForceAccept( + Result const& result, + RCLCxLedger const& prevLedger, + NetClock::duration const& closeResolution, + ConsensusCloseTimes const& rawCloseTimes, + ConsensusMode const& mode, + Json::Value&& consensusJson); + + /** Notify peers of a consensus state change + + @param ne Event type for notification + @param ledger The ledger at the time of the state change + @param haveCorrectLCL Whether we believ we have the correct LCL. + */ + void + notify( + protocol::NodeEvent ne, + RCLCxLedger const& ledger, + bool haveCorrectLCL); + + /** Accept a new ledger based on the given transactions. + + @ref onAccept + */ + void + doAccept( + Result const& result, + RCLCxLedger const& prevLedger, + NetClock::duration closeResolution, + ConsensusCloseTimes const& rawCloseTimes, + ConsensusMode const& mode, + Json::Value&& consensusJson); + + /** Build the new last closed ledger. + + Accept the given the provided set of consensus transactions and + build the last closed ledger. Since consensus just agrees on which + transactions to apply, but not whether they make it into the closed + ledger, this function also populates retriableTxs with those that + can be retried in the next round. + + @param previousLedger Prior ledger building upon + @param set The set of transactions to apply to the ledger + @param closeTime The the ledger closed + @param closeTimeCorrect Whether consensus agreed on close time + @param closeResolution Resolution used to determine consensus close + time + @param roundTime Duration of this consensus rorund + @param retriableTxs Populate with transactions to retry in next + round + @return The newly built ledger + */ + RCLCxLedger + buildLCL( + RCLCxLedger const& previousLedger, + RCLTxSet const& set, + NetClock::time_point closeTime, + bool closeTimeCorrect, + NetClock::duration closeResolution, + std::chrono::milliseconds roundTime, + CanonicalTXSet& retriableTxs); + + /** Validate the given ledger and share with peers as necessary + + @param ledger The ledger to validate + @param proposing Whether we were proposing transactions while + generating this ledger. If we are not proposing, + a validation can still be sent to inform peers that + we know we aren't fully participating in consensus + but are still around and trying to catch up. + */ + void + validate(RCLCxLedger const& ledger, bool proposing); + + }; public: //! Constructor @@ -71,7 +369,8 @@ public: LedgerMaster& ledgerMaster, LocalTxs& localTxs, InboundTransactions& inboundTransactions, - typename Base::clock_type const& clock, + Consensus::clock_type const& clock, + ValidatorKeys const & validatorKeys, beast::Journal journal); RCLConsensus(RCLConsensus const&) = delete; @@ -79,310 +378,96 @@ public: RCLConsensus& operator=(RCLConsensus const&) = delete; - static char const* - getCountedObjectName() - { - return "Consensus"; - } - - /** Save the given consensus proposed by a peer with nodeID for later - use in consensus. - - @param peerPos Proposed peer position - @param nodeID ID of peer - */ - void - storeProposal(RCLCxPeerPos::ref peerPos, NodeID const& nodeID); - //! Whether we are validating consensus ledgers. bool validating() const { - return validating_; + return adaptor_.validating(); } - bool - haveCorrectLCL() const + //! Get the number of proposing peers that participated in the previous + //! round. + std::size_t + prevProposers() const { - return mode() != Mode::wrongLedger; + return adaptor_.prevProposers(); } - bool - proposing() const - { - return mode() == Mode::proposing; - } + /** Get duration of the previous round. - /** Get the Json state of the consensus process. + The duration of the round is the establish phase, measured from closing + the open ledger to accepting the consensus result. - Called by the consensus_info RPC. - - @param full True if verbose response desired. - @return The Json state. + @return Last round duration in milliseconds */ + std::chrono::milliseconds + prevRoundTime() const + { + return adaptor_.prevRoundTime(); + } + + //! @see Consensus::mode + ConsensusMode + mode() const + { + return adaptor_.mode(); + } + + //! @see Consensus::getJson Json::Value getJson(bool full) const; - //! See Consensus::startRound + //! @see Consensus::startRound void startRound( NetClock::time_point const& now, RCLCxLedger::ID const& prevLgrId, RCLCxLedger const& prevLgr); - //! See Consensus::timerEntry + //! @see Consensus::timerEntry void timerEntry(NetClock::time_point const& now); - //! See Consensus::gotTxSet + //! @see Consensus::gotTxSet void gotTxSet(NetClock::time_point const& now, RCLTxSet const& txSet); - /** Returns validation public key */ - PublicKey const& - getValidationPublicKey() const; + // @see Consensus::prevLedgerID + RCLCxLedger::ID + prevLedgerID() const + { + ScopedLockType _{mutex_}; + return consensus_.prevLedgerID(); + } - /** Set validation private and public key pair. */ + //! @see Consensus::simulate void - setValidationKeys(SecretKey const& valSecret, PublicKey const& valPublic); + simulate( + NetClock::time_point const& now, + boost::optional consensusDelay); + + //! @see Consensus::proposal + bool + peerProposal( + NetClock::time_point const& now, + RCLCxPeerPos const& newProposal); + + ConsensusParms const & + parms() const + { + return adaptor_.parms(); + } private: - friend class Consensus; + // Since Consensus does not provide intrinsic thread-safety, this mutex + // guards all calls to consensus_. adaptor_ uses atomics internally + // to allow concurrent access of its data members that have getters. + mutable std::recursive_mutex mutex_; + using ScopedLockType = std::lock_guard ; - //------------------------------------------------------------------------- - // Consensus type requirements. - - /** Attempt to acquire a specific ledger. - - If not available, asynchronously acquires from the network. - - @param ledger The ID/hash of the ledger acquire - @return Optional ledger, will be seated if we locally had the ledger - */ - boost::optional - acquireLedger(LedgerHash const& ledger); - - /** Get peers' proposed positions. - @param prevLedger The base ledger which proposals are based on - @return The set of proposals - */ - std::vector - proposals(LedgerHash const& prevLedger); - - /** Relay the given proposal to all peers - - @param peerPos The peer position to relay. - */ - void - relay(RCLCxPeerPos const& peerPos); - - /** Relay disputed transacction to peers. - - Only relay if the provided transaction hasn't been shared recently. - - @param tx The disputed transaction to relay. - */ - void - relay(RCLCxTx const& tx); - - /** Acquire the transaction set associated with a proposal. - - If the transaction set is not available locally, will attempt acquire it - from the network. - - @param setId The transaction set ID associated with the proposal - @return Optional set of transactions, seated if available. - */ - boost::optional - acquireTxSet(RCLTxSet::ID const& setId); - - /** Whether the open ledger has any transactions - */ - bool - hasOpenTransactions() const; - - /** Number of proposers that have vallidated the given ledger - - @param h The hash of the ledger of interest - @return the number of proposers that validated a ledger - */ - std::size_t - proposersValidated(LedgerHash const& h) const; - - /** Number of proposers that have validated a ledger descended from - requested ledger. - - @param h The hash of the ledger of interest. - @return The number of validating peers that have validated a ledger - succeeding the one provided. - */ - std::size_t - proposersFinished(LedgerHash const& h) const; - - /** Propose the given position to my peers. - - @param proposal Our proposed position - */ - void - propose(RCLCxPeerPos::Proposal const& proposal); - - /** Relay the given tx set to peers. - - @param set The TxSet to share. - */ - void - relay(RCLTxSet const& set); - - /** Get the ID of the previous ledger/last closed ledger(LCL) on the network - - @param ledgerID ID of previous ledger used by consensus - @param ledger Previous ledger consensus has available - @param mode Current consensus mode - @return The id of the last closed network - - @note ledgerID may not match ledger.id() if we haven't acquired - the ledger matching ledgerID from the network - */ - uint256 - getPrevLedger( - uint256 ledgerID, - RCLCxLedger const& ledger, - Mode mode); - - /** Close the open ledger and return initial consensus position. - - @param ledger the ledger we are changing to - @param closeTime When consensus closed the ledger - @param mode Current consensus mode - @return Tentative consensus result - */ - Result - onClose( - RCLCxLedger const& ledger, - NetClock::time_point const& closeTime, - Mode mode); - - /** Process the accepted ledger. - - Accepting a ledger may be expensive, so this function can dispatch - that call to another thread if desired. - - @param result The result of consensus - @param prevLedger The closed ledger consensus worked from - @param closeResolution The resolution used in agreeing on an effective - closeTiem - @param rawCloseTimes The unrounded closetimes of ourself and our peers - @param mode Our participating mode at the time consensus was declared - */ - void - onAccept( - Result const& result, - RCLCxLedger const& prevLedger, - NetClock::duration const & closeResolution, - CloseTimes const& rawCloseTimes, - Mode const& mode); - - /** Process the accepted ledger that was a result of simulation/force - accept. - - @ref onAccept - */ - void - onForceAccept( - Result const& result, - RCLCxLedger const& prevLedger, - NetClock::duration const &closeResolution, - CloseTimes const& rawCloseTimes, - Mode const& mode); - - //!------------------------------------------------------------------------- - // Additional members (not directly required by Consensus interface) - /** Notify peers of a consensus state change - - @param ne Event type for notification - @param ledger The ledger at the time of the state change - @param haveCorrectLCL Whether we believ we have the correct LCL. - */ - void - notify( - protocol::NodeEvent ne, - RCLCxLedger const& ledger, - bool haveCorrectLCL); - - /** Accept a new ledger based on the given transactions. - - @ref onAccept - */ - void - doAccept( - Result const& result, - RCLCxLedger const& prevLedger, - NetClock::duration closeResolution, - CloseTimes const& rawCloseTimes, - Mode const& mode); - - /** Build the new last closed ledger. - - Accept the given the provided set of consensus transactions and build - the last closed ledger. Since consensus just agrees on which - transactions to apply, but not whether they make it into the closed - ledger, this function also populates retriableTxs with those that can - be retried in the next round. - - @param previousLedger Prior ledger building upon - @param set The set of transactions to apply to the ledger - @param closeTime The the ledger closed - @param closeTimeCorrect Whether consensus agreed on close time - @param closeResolution Resolution used to determine consensus close time - @param roundTime Duration of this consensus rorund - @param retriableTxs Populate with transactions to retry in next round - @return The newly built ledger - */ - RCLCxLedger - buildLCL( - RCLCxLedger const& previousLedger, - RCLTxSet const& set, - NetClock::time_point closeTime, - bool closeTimeCorrect, - NetClock::duration closeResolution, - std::chrono::milliseconds roundTime, - CanonicalTXSet& retriableTxs); - - /** Validate the given ledger and share with peers as necessary - - @param ledger The ledger to validate - @param proposing Whether we were proposing transactions while generating - this ledger. If we are not proposing, a validation - can still be sent to inform peers that we know we - aren't fully participating in consensus but are still - around and trying to catch up. - */ - void - validate(RCLCxLedger const& ledger, bool proposing); - - //!------------------------------------------------------------------------- - Application& app_; - std::unique_ptr feeVote_; - LedgerMaster& ledgerMaster_; - LocalTxs& localTxs_; - InboundTransactions& inboundTransactions_; + Adaptor adaptor_; + Consensus consensus_; beast::Journal j_; - - NodeID nodeID_; - PublicKey valPublic_; - SecretKey valSecret_; - LedgerHash acquiringLedger_; - - // The timestamp of the last validation we used, in network time. This is - // only used for our own validations. - NetClock::time_point lastValidationTime_; - - using PeerPositions = hash_map>; - PeerPositions peerPositions_; - std::mutex peerPositionsLock_; - - bool validating_ = false; - bool simulating_ = false; }; } diff --git a/src/ripple/app/consensus/RCLCxPeerPos.cpp b/src/ripple/app/consensus/RCLCxPeerPos.cpp index 73f278fe7c..3e92376ea1 100644 --- a/src/ripple/app/consensus/RCLCxPeerPos.cpp +++ b/src/ripple/app/consensus/RCLCxPeerPos.cpp @@ -33,14 +33,16 @@ RCLCxPeerPos::RCLCxPeerPos( Slice const& signature, uint256 const& suppression, Proposal&& proposal) - : proposal_{std::move(proposal)} - , mSuppression{suppression} - , publicKey_{publicKey} - , signature_{signature} + : data_{std::make_shared( + publicKey, + signature, + suppression, + std::move(proposal))} { } + uint256 -RCLCxPeerPos::getSigningHash() const +RCLCxPeerPos::signingHash() const { return sha512Half( HashPrefix::proposal, @@ -53,16 +55,18 @@ RCLCxPeerPos::getSigningHash() const bool RCLCxPeerPos::checkSign() const { - return verifyDigest(publicKey_, getSigningHash(), signature_, false); + return verifyDigest( + publicKey(), signingHash(), signature(), false); } + Json::Value RCLCxPeerPos::getJson() const { auto ret = proposal().getJson(); - if (publicKey_.size()) - ret[jss::peer_id] = toBase58(TokenType::TOKEN_NODE_PUBLIC, publicKey_); + if (publicKey().size()) + ret[jss::peer_id] = toBase58(TokenType::TOKEN_NODE_PUBLIC, publicKey()); return ret; } @@ -87,4 +91,16 @@ proposalUniqueId( return s.getSHA512Half(); } +RCLCxPeerPos::Data::Data( + PublicKey const& publicKey, + Slice const& signature, + uint256 const& suppress, + Proposal&& proposal) + : publicKey_{publicKey} + , signature_{signature} + , supression_{suppress} + , proposal_{std::move(proposal)} +{ +} + } // ripple diff --git a/src/ripple/app/consensus/RCLCxPeerPos.h b/src/ripple/app/consensus/RCLCxPeerPos.h index b358d4606f..04bcd7bb70 100644 --- a/src/ripple/app/consensus/RCLCxPeerPos.h +++ b/src/ripple/app/consensus/RCLCxPeerPos.h @@ -36,19 +36,12 @@ namespace ripple { /** A peer's signed, proposed position for use in RCLConsensus. - Carries a ConsensusProposal signed by a peer. + Carries a ConsensusProposal signed by a peer. Provides value semantics + but manages shared storage of the peer position internally. */ -class RCLCxPeerPos : public CountedObject +class RCLCxPeerPos { public: - static char const* - getCountedObjectName() - { - return "RCLCxPeerPos"; - } - using pointer = std::shared_ptr; - using ref = const pointer&; - //< The type of the proposed position using Proposal = ConsensusProposal; @@ -70,7 +63,7 @@ public: //! Create the signing hash for the proposal uint256 - getSigningHash() const; + signingHash() const; //! Verify the signing hash of the proposal bool @@ -78,45 +71,59 @@ public: //! Signature of the proposal (not necessarily verified) Slice - getSignature() const + signature() const { - return signature_; + return data_->signature_; } //! Public key of peer that sent the proposal PublicKey const& - getPublicKey() const + publicKey() const { - return publicKey_; + return data_->publicKey_; } //! ????? uint256 const& - getSuppressionID() const + suppressionID() const { - return mSuppression; + return data_->supression_; } - //! The consensus proposal - Proposal const& + Proposal const & proposal() const { - return proposal_; + return data_->proposal_; } - /// @cond Ignore - //! Add a conversion operator to conform to the Consensus interface - operator Proposal const&() const - { - return proposal_; - } - /// @endcond - //! JSON representation of proposal Json::Value getJson() const; private: + + struct Data : public CountedObject + { + PublicKey publicKey_; + Buffer signature_; + uint256 supression_; + Proposal proposal_; + + Data( + PublicKey const& publicKey, + Slice const& signature, + uint256 const& suppress, + Proposal&& proposal); + + static char const* + getCountedObjectName() + { + return "RCLCxPeerPos::Data"; + } + }; + + std::shared_ptr data_; + template void hash_append(Hasher& h) const @@ -129,10 +136,6 @@ private: hash_append(h, proposal().position()); } - Proposal proposal_; - uint256 mSuppression; - PublicKey publicKey_; - Buffer signature_; }; /** Calculate a unique identifier for a signed proposal. diff --git a/src/ripple/app/consensus/RCLValidations.cpp b/src/ripple/app/consensus/RCLValidations.cpp new file mode 100644 index 0000000000..14c352fc2f --- /dev/null +++ b/src/ripple/app/consensus/RCLValidations.cpp @@ -0,0 +1,280 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012-2017 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { + +RCLValidationsPolicy::RCLValidationsPolicy(Application& app) : app_(app) +{ + staleValidations_.reserve(512); +} + +NetClock::time_point +RCLValidationsPolicy::now() const +{ + return app_.timeKeeper().closeTime(); +} + +void +RCLValidationsPolicy::onStale(RCLValidation&& v) +{ + // Store the newly stale validation; do not do significant work in this + // function since this is a callback from Validations, which may be + // doing other work. + + ScopedLockType sl(staleLock_); + staleValidations_.emplace_back(std::move(v)); + if (staleWriting_) + return; + + // addJob() may return false (Job not added) at shutdown. + staleWriting_ = app_.getJobQueue().addJob( + jtWRITE, "Validations::doStaleWrite", [this](Job&) { + auto event = + app_.getJobQueue().makeLoadEvent(jtDISK, "ValidationWrite"); + ScopedLockType sl(staleLock_); + doStaleWrite(sl); + }); +} + +void +RCLValidationsPolicy::flush(hash_map&& remaining) +{ + bool anyNew = false; + { + ScopedLockType sl(staleLock_); + + for (auto const& keyVal : remaining) + { + staleValidations_.emplace_back(std::move(keyVal.second)); + anyNew = true; + } + + // If we have new validations to write and there isn't a write in + // progress already, then write to the database synchronously. + if (anyNew && !staleWriting_) + { + staleWriting_ = true; + doStaleWrite(sl); + } + + // In the case when a prior asynchronous doStaleWrite was scheduled, + // this loop will block until all validations have been flushed. + // This ensures that all validations are written upon return from + // this function. + + while (staleWriting_) + { + ScopedUnlockType sul(staleLock_); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + } +} + +// NOTE: doStaleWrite() must be called with staleLock_ *locked*. The passed +// ScopedLockType& acts as a reminder to future maintainers. +void +RCLValidationsPolicy::doStaleWrite(ScopedLockType&) +{ + static const std::string insVal( + "INSERT INTO Validations " + "(InitialSeq, LedgerSeq, LedgerHash,NodePubKey,SignTime,RawData) " + "VALUES (:initialSeq, :ledgerSeq, " + ":ledgerHash,:nodePubKey,:signTime,:rawData);"); + static const std::string findSeq( + "SELECT LedgerSeq FROM Ledgers WHERE Ledgerhash=:ledgerHash;"); + + assert(staleWriting_); + + while (!staleValidations_.empty()) + { + std::vector currentStale; + currentStale.reserve(512); + staleValidations_.swap(currentStale); + + { + ScopedUnlockType sul(staleLock_); + { + auto db = app_.getLedgerDB().checkoutDb(); + + Serializer s(1024); + soci::transaction tr(*db); + for (auto const& rclValidation : currentStale) + { + s.erase(); + STValidation::pointer const& val = rclValidation.unwrap(); + val->add(s); + + auto const ledgerHash = to_string(val->getLedgerHash()); + + boost::optional ledgerSeq; + *db << findSeq, soci::use(ledgerHash), + soci::into(ledgerSeq); + + auto const initialSeq = ledgerSeq.value_or( + app_.getLedgerMaster().getCurrentLedgerIndex()); + auto const nodePubKey = toBase58( + TokenType::TOKEN_NODE_PUBLIC, val->getSignerPublic()); + auto const signTime = + val->getSignTime().time_since_epoch().count(); + + soci::blob rawData(*db); + rawData.append( + reinterpret_cast(s.peekData().data()), + s.peekData().size()); + assert(rawData.get_len() == s.peekData().size()); + + *db << insVal, soci::use(initialSeq), soci::use(ledgerSeq), + soci::use(ledgerHash), soci::use(nodePubKey), + soci::use(signTime), soci::use(rawData); + } + + tr.commit(); + } + } + } + + staleWriting_ = false; +} + +bool +handleNewValidation(Application& app, + STValidation::ref val, + std::string const& source) +{ + PublicKey const& signer = val->getSignerPublic(); + uint256 const& hash = val->getLedgerHash(); + + // Ensure validation is marked as trusted if signer currently trusted + boost::optional pubKey = app.validators().getTrustedKey(signer); + if (!val->isTrusted() && pubKey) + val->setTrusted(); + RCLValidations& validations = app.getValidations(); + + beast::Journal j = validations.journal(); + + // Do not process partial validations. + if (!val->isFull()) + { + const bool current = isCurrent( + validations.parms(), + app.timeKeeper().closeTime(), + val->getSignTime(), + val->getSeenTime()); + + JLOG(j.debug()) << "Val (partial) for " << hash << " from " + << toBase58(TokenType::TOKEN_NODE_PUBLIC, signer) + << " ignored " + << (val->isTrusted() ? "trusted/" : "UNtrusted/") + << (current ? "current" : "stale"); + + // Only forward if current and trusted + return current && val->isTrusted(); + } + + if (!val->isTrusted()) + { + JLOG(j.trace()) << "Node " + << toBase58(TokenType::TOKEN_NODE_PUBLIC, signer) + << " not in UNL st=" + << val->getSignTime().time_since_epoch().count() + << ", hash=" << hash + << ", shash=" << val->getSigningHash() + << " src=" << source; + } + + // If not currently trusted, see if signer is currently listed + if (!pubKey) + pubKey = app.validators().getListedKey(signer); + + bool shouldRelay = false; + + // only add trusted or listed + if (pubKey) + { + using AddOutcome = RCLValidations::AddOutcome; + + AddOutcome const res = validations.add(*pubKey, val); + + // This is a duplicate validation + if (res == AddOutcome::repeat) + return false; + + // This validation replaced a prior one with the same sequence number + if (res == AddOutcome::sameSeq) + { + auto const seq = val->getFieldU32(sfLedgerSequence); + JLOG(j.warn()) << "Trusted node " + << toBase58(TokenType::TOKEN_NODE_PUBLIC, *pubKey) + << " published multiple validations for ledger " + << seq; + } + + JLOG(j.debug()) << "Val for " << hash << " from " + << toBase58(TokenType::TOKEN_NODE_PUBLIC, signer) + << " added " + << (val->isTrusted() ? "trusted/" : "UNtrusted/") + << ((res == AddOutcome::current) ? "current" : "stale"); + + // Trusted current validations should be checked and relayed. + // Trusted validations with sameSeq replaced an older validation + // with that sequence number, so should still be checked and relayed. + if (val->isTrusted() && + (res == AddOutcome::current || res == AddOutcome::sameSeq)) + { + app.getLedgerMaster().checkAccept( + hash, val->getFieldU32(sfLedgerSequence)); + + shouldRelay = true; + } + } + else + { + JLOG(j.debug()) << "Val for " << hash << " from " + << toBase58(TokenType::TOKEN_NODE_PUBLIC, signer) + << " not added UNtrusted/"; + } + + // This currently never forwards untrusted validations, though we may + // reconsider in the future. From @JoelKatz: + // The idea was that we would have a certain number of validation slots with + // priority going to validators we trusted. Remaining slots might be + // allocated to validators that were listed by publishers we trusted but + // that we didn't choose to trust. The shorter term plan was just to forward + // untrusted validations if peers wanted them or if we had the + // ability/bandwidth to. None of that was implemented. + return shouldRelay; +} +} // namespace ripple diff --git a/src/ripple/app/consensus/RCLValidations.h b/src/ripple/app/consensus/RCLValidations.h new file mode 100644 index 0000000000..cb8dbff057 --- /dev/null +++ b/src/ripple/app/consensus/RCLValidations.h @@ -0,0 +1,193 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_APP_CONSENSUSS_VALIDATIONS_H_INCLUDED +#define RIPPLE_APP_CONSENSUSS_VALIDATIONS_H_INCLUDED + +#include +#include +#include +#include +#include + +namespace ripple { + +class Application; + +/** Wrapper over STValidation for generic Validation code + + Wraps an STValidation::pointer for compatibility with the generic validation + code. +*/ +class RCLValidation +{ + STValidation::pointer val_; +public: + + /** Constructor + + @param v The validation to wrap. + */ + RCLValidation(STValidation::pointer const& v) : val_{v} + { + } + + /// Validated ledger's hash + uint256 + ledgerID() const + { + return val_->getLedgerHash(); + } + + /// Validated ledger's sequence number (0 if none) + std::uint32_t + seq() const + { + if(auto res = (*val_)[~sfLedgerSequence]) + return *res; + return 0; + } + + /// Validation's signing time + NetClock::time_point + signTime() const + { + return val_->getSignTime(); + } + + /// Validated ledger's first seen time + NetClock::time_point + seenTime() const + { + return val_->getSeenTime(); + } + + /// Public key of validator that published the validation + PublicKey + key() const + { + return val_->getSignerPublic(); + } + + /// NodeID of validator that published the validation + NodeID + nodeID() const + { + return val_->getNodeID(); + } + + /// Whether the validation is considered trusted. + bool + trusted() const + { + return val_->isTrusted(); + } + + /// Get the load fee of the validation if it exists + boost::optional + loadFee() const + { + return ~(*val_)[~sfLoadFee]; + } + + /// Extract the underlying STValidation being wrapped + STValidation::pointer + unwrap() const + { + return val_; + } + +}; + +/** Implements the StalePolicy policy class for adapting Validations in the RCL + + Manages storing and writing stale RCLValidations to the sqlite DB. +*/ +class RCLValidationsPolicy +{ + using LockType = std::mutex; + using ScopedLockType = std::lock_guard; + using ScopedUnlockType = GenericScopedUnlock; + + Application& app_; + + // Lock for managing staleValidations_ and writing_ + std::mutex staleLock_; + std::vector staleValidations_; + bool staleWriting_ = false; + + // Write the stale validations to sqlite DB, the scoped lock argument + // is used to remind callers that the staleLock_ must be *locked* prior + // to making the call + void + doStaleWrite(ScopedLockType&); + +public: + + RCLValidationsPolicy(Application & app); + + /** Current time used to determine if validations are stale. + */ + NetClock::time_point + now() const; + + /** Handle a newly stale validation. + + @param v The newly stale validation + + @warning This should do minimal work, as it is expected to be called + by the generic Validations code while it may be holding an + internal lock + */ + void + onStale(RCLValidation&& v); + + /** Flush current validations to disk before shutdown. + + @param remaining The remaining validations to flush + */ + void + flush(hash_map && remaining); +}; + + +/// Alias for RCL-specific instantiation of generic Validations +using RCLValidations = + Validations; + +/** Handle a new validation + + 1. Set the trust status of a validation based on the validating node's + public key and this node's current UNL. + 2. Add the validation to the set of validations if current. + 3. If new and trusted, send the validation to the ledgerMaster. + + @param app Application object containing validations and ledgerMaster + @param val The validation to add + @param source Name associated with validation used in logging + + @return Whether the validation should be relayed +*/ +bool +handleNewValidation(Application & app, STValidation::ref val, std::string const& source); + + +} // namespace ripple + +#endif diff --git a/src/ripple/app/ledger/Ledger.cpp b/src/ripple/app/ledger/Ledger.cpp index 2881a67fe6..61543b9b9e 100644 --- a/src/ripple/app/ledger/Ledger.cpp +++ b/src/ripple/app/ledger/Ledger.cpp @@ -1048,25 +1048,22 @@ bool pendSaveValidated ( return true; } - if (isSynchronous) - return saveValidatedLedger(app, ledger, isCurrent); + JobType const jobType {isCurrent ? jtPUBLEDGER : jtPUBOLDLEDGER}; + char const* const jobName { + isCurrent ? "Ledger::pendSave" : "Ledger::pendOldSave"}; - auto job = [ledger, &app, isCurrent] (Job&) { - saveValidatedLedger(app, ledger, isCurrent); - }; - - if (isCurrent) + // See if we can use the JobQueue. + if (!isSynchronous && + app.getJobQueue().addJob (jobType, jobName, + [&app, ledger, isCurrent] (Job&) { + saveValidatedLedger(app, ledger, isCurrent); + })) { - app.getJobQueue().addJob( - jtPUBLEDGER, "Ledger::pendSave", job); - } - else - { - app.getJobQueue ().addJob( - jtPUBOLDLEDGER, "Ledger::pendOldSave", job); + return true; } - return true; + // The JobQueue won't do the Job. Do the save synchronously. + return saveValidatedLedger(app, ledger, isCurrent); } void diff --git a/src/ripple/app/ledger/LedgerMaster.h b/src/ripple/app/ledger/LedgerMaster.h index d3217073c0..8be9bc0bdf 100644 --- a/src/ripple/app/ledger/LedgerMaster.h +++ b/src/ripple/app/ledger/LedgerMaster.h @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -203,9 +204,9 @@ public: void setBuildingLedger (LedgerIndex index); void tryAdvance (); - void newPathRequest (); + bool newPathRequest (); // Returns true if path request successfully placed. bool isNewPathRequest (); - void newOrderBookDB (); + bool newOrderBookDB (); // Returns true if able to fulfill request. bool fixIndex ( LedgerIndex ledgerIndex, LedgerHash const& ledgerHash); @@ -242,6 +243,9 @@ public: std::size_t getFetchPackCacheSize () const; private: + using ScopedLockType = std::lock_guard ; + using ScopedUnlockType = GenericScopedUnlock ; + void setValidLedger( std::shared_ptr const& l); void setPubLedger( @@ -255,8 +259,9 @@ private: boost::optional getLedgerHashForHistory(LedgerIndex index); std::size_t getNeededValidations(); void advanceThread(); - // Try to publish ledgers, acquire missing ledgers - void doAdvance(); + // Try to publish ledgers, acquire missing ledgers. Always called with + // m_mutex locked. The passed ScopedLockType is a reminder to callers. + void doAdvance(ScopedLockType&); bool shouldFetchPack(std::uint32_t seq) const; bool shouldAcquire( std::uint32_t const currentLedger, @@ -268,12 +273,12 @@ private: findNewLedgersToPublish(); void updatePaths(Job& job); - void newPFWork(const char *name); + + // Returns true if work started. Always called with m_mutex locked. + // The passed ScopedLockType is a reminder to callers. + bool newPFWork(const char *name, ScopedLockType&); private: - using ScopedLockType = std::lock_guard ; - using ScopedUnlockType = GenericScopedUnlock ; - Application& app_; beast::Journal m_journal; @@ -295,38 +300,38 @@ private: std::shared_ptr mHistLedger; // Fully validated ledger, whether or not we have the ledger resident. - std::pair mLastValidLedger; + std::pair mLastValidLedger {uint256(), 0}; LedgerHistory mLedgerHistory; - CanonicalTXSet mHeldTransactions; + CanonicalTXSet mHeldTransactions {uint256()}; // A set of transactions to replay during the next close std::unique_ptr replayData; std::recursive_mutex mCompleteLock; - RangeSet mCompleteLedgers; + RangeSet mCompleteLedgers; std::unique_ptr mLedgerCleaner; uint256 mLastValidateHash; - std::uint32_t mLastValidateSeq; + std::uint32_t mLastValidateSeq {0}; // Publish thread is running. - bool mAdvanceThread; + bool mAdvanceThread {false}; // Publish thread has work to do. - bool mAdvanceWork; - int mFillInProgress; + bool mAdvanceWork {false}; + int mFillInProgress {0}; - int mPathFindThread; // Pathfinder jobs dispatched - bool mPathFindNewRequest; + int mPathFindThread {0}; // Pathfinder jobs dispatched + bool mPathFindNewRequest {false}; - std::atomic mPubLedgerClose; - std::atomic mPubLedgerSeq; - std::atomic mValidLedgerSign; - std::atomic mValidLedgerSeq; - std::atomic mBuildingLedgerSeq; + std::atomic mPubLedgerClose {0}; + std::atomic mPubLedgerSeq {0}; + std::atomic mValidLedgerSign {0}; + std::atomic mValidLedgerSeq {0}; + std::atomic mBuildingLedgerSeq {0}; // The server is in standalone mode bool const standalone_; @@ -341,7 +346,11 @@ private: TaggedCache fetch_packs_; - std::uint32_t fetch_seq_; + std::uint32_t fetch_seq_ {0}; + + // Try to keep a validator from switching from test to live network + // without first wiping the database. + LedgerIndex const max_ledger_difference_ {1000000}; }; diff --git a/src/ripple/app/ledger/OrderBookDB.cpp b/src/ripple/app/ledger/OrderBookDB.cpp index f28c0afb0b..183007d9f6 100644 --- a/src/ripple/app/ledger/OrderBookDB.cpp +++ b/src/ripple/app/ledger/OrderBookDB.cpp @@ -101,6 +101,15 @@ void OrderBookDB::update( { for(auto& sle : ledger->sles) { + if (isStopping()) + { + JLOG (j_.info()) + << "OrderBookDB::update exiting due to isStopping"; + std::lock_guard sl (mLock); + mSeq = 0; + return; + } + if (sle->getType () == ltDIR_NODE && sle->isFieldPresent (sfExchangeRate) && sle->getFieldH256 (sfRootIndex) == sle->key()) diff --git a/src/ripple/app/ledger/impl/LedgerMaster.cpp b/src/ripple/app/ledger/impl/LedgerMaster.cpp index 6814c1d2e7..68733671ea 100644 --- a/src/ripple/app/ledger/impl/LedgerMaster.cpp +++ b/src/ripple/app/ledger/impl/LedgerMaster.cpp @@ -32,7 +32,7 @@ #include #include #include -#include +#include #include #include #include @@ -66,22 +66,9 @@ LedgerMaster::LedgerMaster (Application& app, Stopwatch& stopwatch, : Stoppable ("LedgerMaster", parent) , app_ (app) , m_journal (journal) - , mLastValidLedger (std::make_pair (uint256(), 0)) , mLedgerHistory (collector, app) - , mHeldTransactions (uint256 ()) , mLedgerCleaner (detail::make_LedgerCleaner ( app, *this, app_.journal("LedgerCleaner"))) - , mLastValidateSeq (0) - , mAdvanceThread (false) - , mAdvanceWork (false) - , mFillInProgress (0) - , mPathFindThread (0) - , mPathFindNewRequest (false) - , mPubLedgerClose (0) - , mPubLedgerSeq (0) - , mValidLedgerSign (0) - , mValidLedgerSeq (0) - , mBuildingLedgerSeq (0) , standalone_ (app_.config().standalone()) , fetch_depth_ (app_.getSHAMapStore ().clampFetchDepth ( app_.config().FETCH_DEPTH)) @@ -89,7 +76,6 @@ LedgerMaster::LedgerMaster (Application& app, Stopwatch& stopwatch, , ledger_fetch_size_ (app_.config().getSize (siLedgerFetch)) , fetch_packs_ ("FetchPack", 65536, 45, stopwatch, app_.journal("TaggedCache")) - , fetch_seq_ (0) { } @@ -200,7 +186,7 @@ LedgerMaster::setValidLedger( if (! standalone_) { - times = app_.getValidations().getValidationTimes( + times = app_.getValidations().getTrustedValidationTimes( l->info().hash); } @@ -221,12 +207,24 @@ LedgerMaster::setValidLedger( mValidLedger.set (l); mValidLedgerSign = signTime.time_since_epoch().count(); + assert (mValidLedgerSeq || + !app_.getMaxDisallowedLedger() || + l->info().seq + max_ledger_difference_ > + app_.getMaxDisallowedLedger()); + (void) max_ledger_difference_; mValidLedgerSeq = l->info().seq; app_.getOPs().updateLocalTx (*l); app_.getSHAMapStore().onLedgerClosed (getValidatedLedger()); mLedgerHistory.validatedLedger (l); app_.getAmendmentTable().doValidatedLedger (l); + if (!app_.getOPs().isAmendmentBlocked() && + app_.getAmendmentTable().hasUnsupportedEnabled ()) + { + JLOG (m_journal.error()) << + "One or more unsupported amendments activated: server blocked."; + app_.getOPs().setAmendmentBlocked(); + } } void @@ -344,14 +342,14 @@ bool LedgerMaster::haveLedger (std::uint32_t seq) { ScopedLockType sl (mCompleteLock); - return mCompleteLedgers.hasValue (seq); + return boost::icl::contains(mCompleteLedgers, seq); } void LedgerMaster::clearLedger (std::uint32_t seq) { ScopedLockType sl (mCompleteLock); - return mCompleteLedgers.clearValue (seq); + mCompleteLedgers.erase (seq); } // returns Ledgers we have all the nodes for @@ -365,15 +363,16 @@ LedgerMaster::getFullValidatedRange (std::uint32_t& minVal, std::uint32_t& maxVa if (!maxVal) return false; + boost::optional maybeMin; { ScopedLockType sl (mCompleteLock); - minVal = mCompleteLedgers.prevMissing (maxVal); + maybeMin = prevMissing(mCompleteLedgers, maxVal); } - if (minVal == RangeSet::absent) + if (maybeMin == boost::none) minVal = maxVal; else - ++minVal; + minVal = 1 + *maybeMin; return true; } @@ -382,23 +381,9 @@ LedgerMaster::getFullValidatedRange (std::uint32_t& minVal, std::uint32_t& maxVa bool LedgerMaster::getValidatedRange (std::uint32_t& minVal, std::uint32_t& maxVal) { - // Validated ledger is likely not stored in the DB yet so we use the - // published ledger which is. - maxVal = mPubLedgerSeq.load(); - - if (!maxVal) + if (!getFullValidatedRange(minVal, maxVal)) return false; - { - ScopedLockType sl (mCompleteLock); - minVal = mCompleteLedgers.prevMissing (maxVal); - } - - if (minVal == RangeSet::absent) - minVal = maxVal; - else - ++minVal; - // Remove from the validated range any ledger sequences that may not be // fully updated in the database yet @@ -481,8 +466,8 @@ LedgerMaster::tryFill ( return; { - ScopedLockType ml (mCompleteLock); - mCompleteLedgers.setRange (minHas, maxHas); + ScopedLockType ml(mCompleteLock); + mCompleteLedgers.insert(range(minHas, maxHas)); } maxHas = minHas; ledgerHashes = getHashesByIndex ((seq < 500) @@ -502,7 +487,7 @@ LedgerMaster::tryFill ( { ScopedLockType ml (mCompleteLock); - mCompleteLedgers.setRange (minHas, maxHas); + mCompleteLedgers.insert(range(minHas, maxHas)); } { ScopedLockType ml (m_mutex); @@ -647,7 +632,7 @@ LedgerMaster::setFullLedger ( { ScopedLockType ml (mCompleteLock); - mCompleteLedgers.setValue (ledger->info().seq); + mCompleteLedgers.insert (ledger->info().seq); } { @@ -700,7 +685,7 @@ LedgerMaster::checkAccept (uint256 const& hash, std::uint32_t seq) return; valCount = - app_.getValidations().getTrustedValidationCount (hash); + app_.getValidations().numTrustedForLedger (hash); if (valCount >= app_.validators ().quorum ()) { @@ -764,8 +749,7 @@ LedgerMaster::checkAccept ( return; auto const minVal = getNeededValidations(); - auto const tvc = app_.getValidations().getTrustedValidationCount( - ledger->info().hash); + auto const tvc = app_.getValidations().numTrustedForLedger(ledger->info().hash); if (tvc < minVal) // nothing we can do { JLOG (m_journal.trace()) << @@ -791,7 +775,7 @@ LedgerMaster::checkAccept ( app_.getOrderBookDB().setup(ledger); } - std::uint64_t const base = app_.getFeeTrack().getLoadBase(); + std::uint32_t const base = app_.getFeeTrack().getLoadBase(); auto fees = app_.getValidations().fees (ledger->info().hash, base); { auto fees2 = app_.getValidations().fees ( @@ -799,7 +783,7 @@ LedgerMaster::checkAccept ( fees.reserve (fees.size() + fees2.size()); std::copy (fees2.begin(), fees2.end(), std::back_inserter(fees)); } - std::uint64_t fee; + std::uint32_t fee; if (! fees.empty()) { std::sort (fees.begin(), fees.end()); @@ -854,7 +838,7 @@ LedgerMaster::consensusBuilt( // maybe we saved up validations for some other ledger that can be auto const val = - app_.getValidations().getCurrentTrustedValidations(); + app_.getValidations().currentTrusted(); // Track validation counts with sequence numbers class valSeq @@ -926,7 +910,7 @@ LedgerMaster::advanceThread() try { - doAdvance(); + doAdvance(sl); } catch (std::exception const&) { @@ -1166,6 +1150,7 @@ LedgerMaster::updatePaths (Job& job) { JLOG (m_journal.debug()) << "Published ledger too old for updating paths"; + ScopedLockType ml (m_mutex); --mPathFindThread; return; } @@ -1200,48 +1185,51 @@ LedgerMaster::updatePaths (Job& job) } } -void +bool LedgerMaster::newPathRequest () { ScopedLockType ml (m_mutex); - mPathFindNewRequest = true; - - newPFWork("pf:newRequest"); + mPathFindNewRequest = newPFWork("pf:newRequest", ml); + return mPathFindNewRequest; } bool LedgerMaster::isNewPathRequest () { ScopedLockType ml (m_mutex); - if (!mPathFindNewRequest) - return false; + bool const ret = mPathFindNewRequest; mPathFindNewRequest = false; - return true; + return ret; } // If the order book is radically updated, we need to reprocess all // pathfinding requests. -void +bool LedgerMaster::newOrderBookDB () { ScopedLockType ml (m_mutex); mPathLedger.reset(); - newPFWork("pf:newOBDB"); + return newPFWork("pf:newOBDB", ml); } /** A thread needs to be dispatched to handle pathfinding work of some kind. */ -void -LedgerMaster::newPFWork (const char *name) +bool +LedgerMaster::newPFWork (const char *name, ScopedLockType&) { if (mPathFindThread < 2) { - ++mPathFindThread; - app_.getJobQueue().addJob ( + if (app_.getJobQueue().addJob ( jtUPDATE_PF, name, - [this] (Job& j) { updatePaths(j); }); + [this] (Job& j) { updatePaths(j); })) + { + ++mPathFindThread; + } } + // If we're stopping don't give callers the expectation that their + // request will be fulfilled, even if it may be serviced. + return mPathFindThread > 0 && !isStopping(); } std::recursive_mutex& @@ -1283,7 +1271,7 @@ std::string LedgerMaster::getCompleteLedgers () { ScopedLockType sl (mCompleteLock); - return mCompleteLedgers.toString (); + return to_string(mCompleteLedgers); } boost::optional @@ -1447,7 +1435,7 @@ void LedgerMaster::setLedgerRangePresent (std::uint32_t minV, std::uint32_t maxV) { ScopedLockType sl (mCompleteLock); - mCompleteLedgers.setRange (minV, maxV); + mCompleteLedgers.insert(range(minV, maxV)); } void @@ -1478,12 +1466,9 @@ LedgerMaster::getPropertySource () void LedgerMaster::clearPriorLedgers (LedgerIndex seq) { - ScopedLockType sl (mCompleteLock); - for (LedgerIndex i = mCompleteLedgers.getFirst(); i < seq; ++i) - { - if (haveLedger (i)) - clearLedger (i); - } + ScopedLockType sl(mCompleteLock); + if (seq > 0) + mCompleteLedgers.erase(range(0u, seq - 1)); } void @@ -1530,7 +1515,7 @@ LedgerMaster::shouldAcquire ( } // Try to publish ledgers, acquire missing ledgers -void LedgerMaster::doAdvance () +void LedgerMaster::doAdvance (ScopedLockType& sl) { // TODO NIKB: simplify and unindent this a bit! @@ -1547,137 +1532,141 @@ void LedgerMaster::doAdvance () (mValidLedgerSeq == mPubLedgerSeq) && (getValidatedLedgerAge() < MAX_LEDGER_AGE_ACQUIRE)) { // We are in sync, so can acquire - std::uint32_t missing; + boost::optional maybeMissing; { ScopedLockType sl (mCompleteLock); - missing = mCompleteLedgers.prevMissing( - mPubLedger->info().seq); + maybeMissing = + prevMissing(mCompleteLedgers, mPubLedger->info().seq); } - JLOG (m_journal.trace()) - << "tryAdvance discovered missing " << missing; - if ((missing != RangeSet::absent) && (missing > 0) && - shouldAcquire (mValidLedgerSeq, ledger_history_, - app_.getSHAMapStore ().getCanDelete (), missing) && - ((mFillInProgress == 0) || (missing > mFillInProgress))) + if (maybeMissing) { - JLOG (m_journal.trace()) - << "advanceThread should acquire"; + std::uint32_t missing = *maybeMissing; + JLOG(m_journal.trace()) + << "tryAdvance discovered missing " << missing; + if ((missing > 0) && + shouldAcquire(mValidLedgerSeq, ledger_history_, + app_.getSHAMapStore().getCanDelete(), missing) && + ((mFillInProgress == 0) || (missing > mFillInProgress))) { - ScopedUnlockType sl(m_mutex); - auto hash = getLedgerHashForHistory (missing); - if (hash) + JLOG(m_journal.trace()) + << "advanceThread should acquire"; { - assert(hash->isNonZero()); - auto ledger = getLedgerByHash (*hash); - if (!ledger) + ScopedUnlockType sl(m_mutex); + auto hash = getLedgerHashForHistory(missing); + if (hash) { - if (!app_.getInboundLedgers().isFailure ( - *hash)) + assert(hash->isNonZero()); + auto ledger = getLedgerByHash(*hash); + if (!ledger) { - ledger = - app_.getInboundLedgers().acquire( - *hash, missing, - InboundLedger::fcHISTORY); - if (! ledger && (missing > 32600) && - shouldFetchPack (missing)) + if (!app_.getInboundLedgers().isFailure( + *hash)) { - JLOG (m_journal.trace()) << - "tryAdvance want fetch pack " << - missing; - fetch_seq_ = missing; - getFetchPack(*hash, missing); + ledger = + app_.getInboundLedgers().acquire( + *hash, missing, + InboundLedger::fcHISTORY); + if (!ledger && (missing > 32600) && + shouldFetchPack(missing)) + { + JLOG(m_journal.trace()) << + "tryAdvance want fetch pack " << + missing; + fetch_seq_ = missing; + getFetchPack(*hash, missing); + } + else + JLOG(m_journal.trace()) << + "tryAdvance no fetch pack for " << + missing; } else - JLOG (m_journal.trace()) << - "tryAdvance no fetch pack for " << - missing; + JLOG(m_journal.debug()) << + "tryAdvance found failed acquire"; } - else - JLOG (m_journal.debug()) << - "tryAdvance found failed acquire"; - } - if (ledger) - { - auto seq = ledger->info().seq; - assert(seq == missing); - JLOG (m_journal.trace()) + if (ledger) + { + auto seq = ledger->info().seq; + assert(seq == missing); + JLOG(m_journal.trace()) << "tryAdvance acquired " << ledger->info().seq; - setFullLedger( - ledger, - false, - false); - auto const& parent = ledger->info().parentHash; + setFullLedger( + ledger, + false, + false); + auto const& parent = ledger->info().parentHash; - int fillInProgress; - { - ScopedLockType lock(m_mutex); - mHistLedger = ledger; - fillInProgress = mFillInProgress; - } - - if (fillInProgress == 0 && - getHashByIndex(seq - 1, app_) == parent) - { + int fillInProgress; { - // Previous ledger is in DB ScopedLockType lock(m_mutex); - mFillInProgress = ledger->info().seq; + mHistLedger = ledger; + fillInProgress = mFillInProgress; } - app_.getJobQueue().addJob( - jtADVANCE, "tryFill", - [this, ledger] (Job& j) { + if (fillInProgress == 0 && + getHashByIndex(seq - 1, app_) == parent) + { + { + // Previous ledger is in DB + ScopedLockType lock(m_mutex); + mFillInProgress = ledger->info().seq; + } + + app_.getJobQueue().addJob( + jtADVANCE, "tryFill", + [this, ledger](Job& j) { tryFill(j, ledger); }); - } + } - progress = true; + progress = true; + } + else + { + try + { + for (int i = 0; i < ledger_fetch_size_; ++i) + { + std::uint32_t seq = missing - i; + auto hash2 = + getLedgerHashForHistory(seq); + if (hash2) + { + assert(hash2->isNonZero()); + app_.getInboundLedgers().acquire + (*hash2, seq, + InboundLedger::fcHISTORY); + } + } + } + catch (std::exception const&) + { + JLOG(m_journal.warn()) << + "Threw while prefetching"; + } + } } else { - try - { - for (int i = 0; i < ledger_fetch_size_; ++i) - { - std::uint32_t seq = missing - i; - auto hash2 = - getLedgerHashForHistory(seq); - if (hash2) - { - assert(hash2->isNonZero()); - app_.getInboundLedgers().acquire - (*hash2, seq, - InboundLedger::fcHISTORY); - } - } - } - catch (std::exception const&) - { - JLOG (m_journal.warn()) << - "Threw while prefetching"; - } + JLOG(m_journal.fatal()) << + "Can't find ledger following prevMissing " << + missing; + JLOG(m_journal.fatal()) << "Pub:" << + mPubLedgerSeq << " Val:" << mValidLedgerSeq; + JLOG(m_journal.fatal()) << "Ledgers: " << + app_.getLedgerMaster().getCompleteLedgers(); + clearLedger(missing + 1); + progress = true; } } - else + if (mValidLedgerSeq != mPubLedgerSeq) { - JLOG (m_journal.fatal()) << - "Can't find ledger following prevMissing " << - missing; - JLOG (m_journal.fatal()) << "Pub:" << - mPubLedgerSeq << " Val:" << mValidLedgerSeq; - JLOG (m_journal.fatal()) << "Ledgers: " << - app_.getLedgerMaster().getCompleteLedgers(); - clearLedger (missing + 1); + JLOG(m_journal.debug()) << + "tryAdvance found last valid changed"; progress = true; } } - if (mValidLedgerSeq != mPubLedgerSeq) - { - JLOG (m_journal.debug()) << - "tryAdvance found last valid changed"; - progress = true; - } } } else @@ -1713,9 +1702,8 @@ void LedgerMaster::doAdvance () } } - progress = true; app_.getOPs().clearNeedNetworkLedger(); - newPFWork ("pf:newLedger"); + progress = newPFWork ("pf:newLedger", sl); } if (progress) mAdvanceWork = true; diff --git a/src/ripple/app/ledger/impl/TransactionAcquire.cpp b/src/ripple/app/ledger/impl/TransactionAcquire.cpp index 23da237b59..f846a92a2b 100644 --- a/src/ripple/app/ledger/impl/TransactionAcquire.cpp +++ b/src/ripple/app/ledger/impl/TransactionAcquire.cpp @@ -82,6 +82,11 @@ void TransactionAcquire::done () uint256 const& hash (mHash); std::shared_ptr const& map (mMap); auto const pap = &app_; + // Note that, when we're in the process of shutting down, addJob() + // may reject the request. If that happens then giveSet() will + // not be called. That's fine. According to David the giveSet() call + // just updates the consensus and related structures when we acquire + // a transaction set. No need to update them if we're shutting down. app_.getJobQueue().addJob (jtTXN_DATA, "completeAcquire", [pap, hash, map](Job&) { diff --git a/src/ripple/app/main/Amendments.cpp b/src/ripple/app/main/Amendments.cpp index e42add8d05..7e8c5ac4c8 100644 --- a/src/ripple/app/main/Amendments.cpp +++ b/src/ripple/app/main/Amendments.cpp @@ -44,7 +44,8 @@ supportedAmendments () { "C1B8D934087225F509BEB5A8EC24447854713EE447D277F69545ABFA0E0FD490 Tickets" }, { "6781F8368C4771B83E8B821D88F580202BCB4228075297B19E4FDC5233F1EFDC TrustSetAuth" }, { "42426C4D4F1009EE67080A9B7965B44656D7714D104A72F9B4369F97ABF044EE FeeEscalation" }, - { "9178256A980A86CF3D70D0260A7DA6402AAFE43632FDBCB88037978404188871 OwnerPaysFee" }, + // The following will be supported in a future release (and uncommented at that time) + //{ "9178256A980A86CF3D70D0260A7DA6402AAFE43632FDBCB88037978404188871 OwnerPaysFee" }, { "08DE7D96082187F6E6578530258C77FAABABE4C20474BDB82F04B021F1A68647 PayChan" }, { "740352F2412A9909880C23A559FCECEDA3BE2126FED62FC7660D628A06927F11 Flow" }, { "1562511F573A19AE9BD103B5D6B9E01B3B46805AEC5D3C4805C902B514399146 CryptoConditions" }, @@ -54,7 +55,10 @@ supportedAmendments () { "86E83A7D2ECE3AD5FA87AB2195AE015C950469ABF0B72EAACED318F74886AE90 CryptoConditionsSuite" }, { "42EEA5E28A97824821D4EF97081FE36A54E9593C6E4F20CBAE098C69D2E072DC fix1373" }, { "DC9CA96AEA1DCF83E527D1AFC916EFAF5D27388ECA4060A88817C1238CAEE0BF EnforceInvariants" }, - { "3012E8230864E95A58C60FD61430D7E1B4D3353195F2981DC12B0C7C0950FFAC FlowCross" } + { "3012E8230864E95A58C60FD61430D7E1B4D3353195F2981DC12B0C7C0950FFAC FlowCross" }, + { "CC5ABAE4F3EC92E94A59B1908C2BE82D2228B6485C00AFF8F22DF930D89C194E SortedDirectories" }, + { "B4D44CC3111ADD964E846FC57760C8B50FFCD5A82C86A72756F6B058DDDF96AD fix1201" }, + { "6C92211186613F9647A89DFFBAB8F94C99D4C7E956D495270789128569177DA1 fix1512" } }; } diff --git a/src/ripple/app/main/Application.cpp b/src/ripple/app/main/Application.cpp index 73308950fc..946374dccf 100644 --- a/src/ripple/app/main/Application.cpp +++ b/src/ripple/app/main/Application.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -41,20 +42,24 @@ #include #include #include +#include #include #include #include #include #include -#include #include #include #include #include +#include #include #include #include +#include #include +#include +#include namespace ripple { @@ -75,7 +80,7 @@ private: beast::Journal j_; // missing node handler - std::uint32_t maxSeq = 0; + LedgerIndex maxSeq = 0; std::mutex maxSeqLock; void acquire ( @@ -216,7 +221,6 @@ supportedAmendments (); class ApplicationImp : public Application , public RootStoppable - , public DeadlineTimer::Listener , public BasicApp { private: @@ -304,6 +308,7 @@ public: std::unique_ptr m_collectorManager; CachedSLEs cachedSLEs_; std::pair nodeIdentity_; + ValidatorKeys const validatorKeys_; std::unique_ptr m_resourceManager; @@ -328,11 +333,12 @@ public: std::unique_ptr m_amendmentTable; std::unique_ptr mFeeTrack; std::unique_ptr mHashRouter; - std::unique_ptr mValidations; + RCLValidations mValidations; std::unique_ptr m_loadManager; std::unique_ptr txQ_; - DeadlineTimer m_sweepTimer; - DeadlineTimer m_entropyTimer; + ClosureCounter waitHandlerCounter_; + boost::asio::steady_timer sweepTimer_; + boost::asio::steady_timer entropyTimer_; bool startTimers_; std::unique_ptr mTxnDB; @@ -393,8 +399,8 @@ public: , m_collectorManager (CollectorManager::New ( config_->section (SECTION_INSIGHT), logs_->journal("Collector"))) - , cachedSLEs_ (std::chrono::minutes(1), stopwatch()) + , validatorKeys_(*config_, m_journal) , m_resourceManager (Resource::make_Manager ( m_collectorManager->collector(), logs_->journal("Resource"))) @@ -444,8 +450,8 @@ public: , m_networkOPs (make_NetworkOPs (*this, stopwatch(), config_->standalone(), config_->NETWORK_QUORUM, config_->START_VALID, - *m_jobQueue, *m_ledgerMaster, *m_jobQueue, - logs_->journal("NetworkOPs"))) + *m_jobQueue, *m_ledgerMaster, *m_jobQueue, validatorKeys_, + get_io_service(), logs_->journal("NetworkOPs"))) , cluster_ (std::make_unique ( logs_->journal("Overlay"))) @@ -472,15 +478,16 @@ public: stopwatch(), HashRouter::getDefaultHoldTime (), HashRouter::getDefaultRecoverLimit ())) - , mValidations (make_Validations (*this)) + , mValidations (ValidationParms(),stopwatch(), logs_->journal("Validations"), + *this) , m_loadManager (make_LoadManager (*this, *this, logs_->journal("LoadManager"))) , txQ_(make_TxQ(setup_TxQ(*config_), logs_->journal("TxQ"))) - , m_sweepTimer (this) + , sweepTimer_ (get_io_service()) - , m_entropyTimer (this) + , entropyTimer_ (get_io_service()) , startTimers_ (false) @@ -569,6 +576,13 @@ public: return nodeIdentity_; } + virtual + PublicKey const & + getValidationPublicKey() const override + { + return validatorKeys_.publicKey; + } + NetworkOPs& getOPs () override { return *m_networkOPs; @@ -671,9 +685,9 @@ public: return *mHashRouter; } - Validations& getValidations () override + RCLValidations& getValidations () override { - return *mValidations; + return mValidations; } ValidatorList& validators () override @@ -817,8 +831,8 @@ public: using namespace std::chrono_literals; if(startTimers_) { - m_sweepTimer.setExpiration (10s); - m_entropyTimer.setRecurringExpiration (5min); + setSweepTimer(); + setEntropyTimer(); } m_io_latency_sampler.start(); @@ -848,13 +862,30 @@ public: // things will happen. m_resolver->stop (); - if(startTimers_) { - m_sweepTimer.cancel (); - m_entropyTimer.cancel (); - } + boost::system::error_code ec; + sweepTimer_.cancel (ec); + if (ec) + { + JLOG (m_journal.error()) + << "Application: sweepTimer cancel error: " + << ec.message(); + } - mValidations->flush (); + ec.clear(); + entropyTimer_.cancel (ec); + if (ec) + { + JLOG (m_journal.error()) + << "Application: entropyTimer cancel error: " + << ec.message(); + } + } + // Make sure that any waitHandlers pending in our timers are done + // before we declare ourselves stopped. + waitHandlerCounter_.join("Application", 1s, m_journal); + + mValidations.flush (); validatorSites_->stop (); @@ -874,7 +905,7 @@ public: stopped (); } - //------------------------------------------------------------------------------ + //-------------------------------------------------------------------------- // // PropertyStream // @@ -883,41 +914,83 @@ public: { } - //------------------------------------------------------------------------------ + //-------------------------------------------------------------------------- - void onDeadlineTimer (DeadlineTimer& timer) override + void setSweepTimer () { - if (timer == m_entropyTimer) - { - crypto_prng().mix_entropy (); - return; - } - - if (timer == m_sweepTimer) - { - // VFALCO TODO Move all this into doSweep - - if (! config_->standalone()) + // Only start the timer if waitHandlerCounter_ is not yet joined. + if (auto optionalCountedHandler = waitHandlerCounter_.wrap ( + [this] (boost::system::error_code const& e) { - boost::filesystem::space_info space = - boost::filesystem::space (config_->legacy ("database_path")); - - // VFALCO TODO Give this magic constant a name and move it into a well documented header - // - if (space.available < (512 * 1024 * 1024)) + if ((e.value() == boost::system::errc::success) && + (! m_jobQueue->isStopped())) { - JLOG(m_journal.fatal()) - << "Remaining free disk space is less than 512MB"; - signalStop (); + m_jobQueue->addJob( + jtSWEEP, "sweep", [this] (Job&) { doSweep(); }); } - } + // Recover as best we can if an unexpected error occurs. + if (e.value() != boost::system::errc::success && + e.value() != boost::asio::error::operation_aborted) + { + // Try again later and hope for the best. + JLOG (m_journal.error()) + << "Sweep timer got error '" << e.message() + << "'. Restarting timer."; + setSweepTimer(); + } + })) + { + sweepTimer_.expires_from_now ( + std::chrono::seconds {config_->getSize (siSweepInterval)}); + sweepTimer_.async_wait (std::move (*optionalCountedHandler)); + } + } - m_jobQueue->addJob(jtSWEEP, "sweep", [this] (Job&) { doSweep(); }); + void setEntropyTimer () + { + // Only start the timer if waitHandlerCounter_ is not yet joined. + if (auto optionalCountedHandler = waitHandlerCounter_.wrap ( + [this] (boost::system::error_code const& e) + { + if (e.value() == boost::system::errc::success) + { + crypto_prng().mix_entropy(); + setEntropyTimer(); + } + // Recover as best we can if an unexpected error occurs. + if (e.value() != boost::system::errc::success && + e.value() != boost::asio::error::operation_aborted) + { + // Try again later and hope for the best. + JLOG (m_journal.error()) + << "Entropy timer got error '" << e.message() + << "'. Restarting timer."; + setEntropyTimer(); + } + })) + { + using namespace std::chrono_literals; + entropyTimer_.expires_from_now (5min); + entropyTimer_.async_wait (std::move (*optionalCountedHandler)); } } void doSweep () { + if (! config_->standalone()) + { + boost::filesystem::space_info space = + boost::filesystem::space (config_->legacy ("database_path")); + + constexpr std::uintmax_t bytes512M = 512 * 1024 * 1024; + if (space.available < (bytes512M)) + { + JLOG(m_journal.fatal()) + << "Remaining free disk space is less than 512MB"; + signalStop (); + } + } + // VFALCO NOTE Does the order of calls matter? // VFALCO TODO fix the dependency inversion using an observer, // have listeners register for "onSweep ()" notification. @@ -927,19 +1000,27 @@ public: getNodeStore().sweep(); getLedgerMaster().sweep(); getTempNodeCache().sweep(); - getValidations().sweep(); + getValidations().expire(); getInboundLedgers().sweep(); m_acceptedLedgerCache.sweep(); family().treecache().sweep(); cachedSLEs_.expire(); - // VFALCO NOTE does the call to sweep() happen on another thread? - m_sweepTimer.setExpiration ( - std::chrono::seconds {config_->getSize (siSweepInterval)}); + // Set timer to do another sweep later. + setSweepTimer(); + } + + LedgerIndex getMaxDisallowedLedger() override + { + return maxDisallowedLedger_; } private: + // For a newly-started validator, this is the greatest persisted ledger + // and new validations must be greater than this. + std::atomic maxDisallowedLedger_ {0}; + void addTxnSeqField(); void addValidationSeqFields(); bool updateTables (); @@ -956,6 +1037,8 @@ private: std::string const& ledgerID, bool replay, bool isFilename); + + void setMaxDisallowedLedger(); }; //------------------------------------------------------------------------------ @@ -1005,6 +1088,9 @@ bool ApplicationImp::setup() return false; } + if (validatorKeys_.publicKey.size()) + setMaxDisallowedLedger(); + getLedgerDB ().getSession () << boost::str (boost::format ("PRAGMA cache_size=-%d;") % (config_->getSize (siLgrDBCache) * 1024)); @@ -1085,38 +1171,11 @@ bool ApplicationImp::setup() } { - PublicKey valPublic; - SecretKey valSecret; - std::string manifest; - if (config().exists (SECTION_VALIDATOR_TOKEN)) - { - if (auto const token = ValidatorToken::make_ValidatorToken ( - config().section (SECTION_VALIDATOR_TOKEN).lines ())) - { - valSecret = token->validationSecret; - valPublic = derivePublicKey (KeyType::secp256k1, valSecret); - manifest = std::move(token->manifest); - } - else - { - JLOG(m_journal.fatal()) << - "Invalid entry in validator token configuration."; - return false; - } - } - else if (config().exists (SECTION_VALIDATION_SEED)) - { - auto const seed = parseBase58( - config().section (SECTION_VALIDATION_SEED).lines ().front()); - if (!seed) - Throw ( - "Invalid seed specified in [" SECTION_VALIDATION_SEED "]"); - valSecret = generateSecretKey (KeyType::secp256k1, *seed); - valPublic = derivePublicKey (KeyType::secp256k1, valSecret); - } + if(validatorKeys_.configInvalid()) + return false; if (!validatorManifests_->load ( - getWalletDB (), "ValidatorManifests", manifest, + getWalletDB (), "ValidatorManifests", validatorKeys_.manifest, config().section (SECTION_VALIDATOR_KEY_REVOCATION).values ())) { JLOG(m_journal.fatal()) << "Invalid configured validator manifest."; @@ -1126,11 +1185,9 @@ bool ApplicationImp::setup() publisherManifests_->load ( getWalletDB (), "PublisherManifests"); - m_networkOPs->setValidationKeys (valSecret, valPublic); - // Setup trusted validators if (!validators_->load ( - valPublic, + validatorKeys_.publicKey, config().section (SECTION_VALIDATORS).values (), config().section (SECTION_VALIDATOR_LIST_KEYS).values ())) { @@ -1572,7 +1629,7 @@ bool ApplicationImp::loadOldLedger ( } } } - else if (ledgerID.empty () || beast::detail::ci_equal(ledgerID, "latest")) + else if (ledgerID.empty () || beast::detail::iequals(ledgerID, "latest")) { loadLedger = getLastFullLedger (); } @@ -1949,6 +2006,21 @@ bool ApplicationImp::updateTables () return true; } +void ApplicationImp::setMaxDisallowedLedger() +{ + boost::optional seq; + { + auto db = getLedgerDB().checkoutDb(); + *db << "SELECT MAX(LedgerSeq) FROM Ledgers;", soci::into(seq); + } + if (seq) + maxDisallowedLedger_ = *seq; + + JLOG (m_journal.trace()) << "Max persisted ledger is " + << maxDisallowedLedger_; +} + + //------------------------------------------------------------------------------ Application::Application () diff --git a/src/ripple/app/main/Application.h b/src/ripple/app/main/Application.h index a6033636cf..2769be22d4 100644 --- a/src/ripple/app/main/Application.h +++ b/src/ripple/app/main/Application.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -64,7 +65,7 @@ class STLedgerEntry; class TimeKeeper; class TransactionMaster; class TxQ; -class Validations; + class ValidatorList; class ValidatorSite; class Cluster; @@ -74,6 +75,13 @@ class SHAMapStore; using NodeCache = TaggedCache ; +template +class Validations; +class RCLValidation; +class RCLValidationsPolicy; +using RCLValidations = + Validations; + class Application : public beast::PropertyStream::Source { public: @@ -128,7 +136,7 @@ public: virtual ManifestCache& validatorManifests () = 0; virtual ManifestCache& publisherManifests () = 0; virtual Cluster& cluster () = 0; - virtual Validations& getValidations () = 0; + virtual RCLValidations& getValidations () = 0; virtual NodeStore::Database& getNodeStore () = 0; virtual InboundLedgers& getInboundLedgers () = 0; virtual InboundTransactions& getInboundTransactions () = 0; @@ -143,6 +151,10 @@ public: std::pair const& nodeIdentity () = 0; + virtual + PublicKey const & + getValidationPublicKey() const = 0; + virtual Resource::Manager& getResourceManager () = 0; virtual PathRequests& getPathRequests () = 0; virtual SHAMapStore& getSHAMapStore () = 0; @@ -164,6 +176,10 @@ public: /** Retrieve the "wallet database" */ virtual DatabaseCon& getWalletDB () = 0; + + /** Ensure that a newly-started validator does not sign proposals older + * than the last ledger it persisted. */ + virtual LedgerIndex getMaxDisallowedLedger() = 0; }; std::unique_ptr diff --git a/src/ripple/app/main/Main.cpp b/src/ripple/app/main/Main.cpp index 6d2ae686a3..78b1779917 100644 --- a/src/ripple/app/main/Main.cpp +++ b/src/ripple/app/main/Main.cpp @@ -49,6 +49,7 @@ #include #include #include +#include #if defined(BEAST_LINUX) || defined(BEAST_MAC) || defined(BEAST_BSD) @@ -79,7 +80,7 @@ adjustDescriptorLimit(int needed, beast::Journal j) if (getrlimit(RLIMIT_NOFILE, &rl) == 0) { - // If the limit is infnite, then we are good. + // If the limit is infinite, then we are good. if (rl.rlim_cur == RLIM_INFINITY) available = needed; else @@ -379,12 +380,11 @@ int run (int argc, char** argv) vm["rpc_port"].as()); if (*config->rpc_port == 0) - Throw (""); + throw std::domain_error("0"); } - catch(std::exception const&) + catch(std::exception const& e) { - std::cerr << "Invalid rpc_port = " << - vm["rpc_port"].as() << std::endl; + std::cerr << "Invalid rpc_port = " << e.what() << "\n"; return -1; } } @@ -394,11 +394,15 @@ int run (int argc, char** argv) try { config->VALIDATION_QUORUM = vm["quorum"].as (); + if (config->VALIDATION_QUORUM == std::size_t{}) + { + throw std::domain_error("0"); + } } - catch(std::exception const&) + catch(std::exception const& e) { - std::cerr << "Invalid quorum = " << - vm["quorum"].as () << std::endl; + std::cerr << "Invalid value specified for --quorum (" + << e.what() << ")\n"; return -1; } } diff --git a/src/ripple/app/main/NodeStoreScheduler.cpp b/src/ripple/app/main/NodeStoreScheduler.cpp index 54351c4712..dfe0121dfd 100644 --- a/src/ripple/app/main/NodeStoreScheduler.cpp +++ b/src/ripple/app/main/NodeStoreScheduler.cpp @@ -25,8 +25,6 @@ namespace ripple { NodeStoreScheduler::NodeStoreScheduler (Stoppable& parent) : Stoppable ("NodeStoreScheduler", parent) - , m_jobQueue (nullptr) - , m_taskCount (0) { } @@ -48,16 +46,29 @@ void NodeStoreScheduler::onChildrenStopped () void NodeStoreScheduler::scheduleTask (NodeStore::Task& task) { ++m_taskCount; - m_jobQueue->addJob ( + if (!m_jobQueue->addJob ( jtWRITE, "NodeObject::store", - [this, &task] (Job&) { doTask(task); }); + [this, &task] (Job&) { doTask(task); })) + { + // Job not added, presumably because we're shutting down. + // Recover by executing the task synchronously. + doTask (task); + } } void NodeStoreScheduler::doTask (NodeStore::Task& task) { task.performScheduledTask (); - if ((--m_taskCount == 0) && isStopping()) + + // NOTE: It feels a bit off that there are two different methods that + // call stopped(): onChildrenStopped() and doTask(). There's a + // suspicion that, as long as the Stoppable tree is configured + // correctly, this call to stopped() in doTask() can never occur. + // + // However, until we increase our confidence that the suspicion is + // correct, we will leave this code in place. + if ((--m_taskCount == 0) && isStopping() && areChildrenStopped()) stopped(); } diff --git a/src/ripple/app/main/NodeStoreScheduler.h b/src/ripple/app/main/NodeStoreScheduler.h index e6689011bf..657fe6499e 100644 --- a/src/ripple/app/main/NodeStoreScheduler.h +++ b/src/ripple/app/main/NodeStoreScheduler.h @@ -49,8 +49,8 @@ public: private: void doTask (NodeStore::Task& task); - JobQueue* m_jobQueue; - std::atomic m_taskCount; + JobQueue* m_jobQueue {nullptr}; + std::atomic m_taskCount {0}; }; } // ripple diff --git a/src/ripple/app/misc/AmendmentTable.h b/src/ripple/app/misc/AmendmentTable.h index 50f722e280..674fd57bbc 100644 --- a/src/ripple/app/misc/AmendmentTable.h +++ b/src/ripple/app/misc/AmendmentTable.h @@ -21,7 +21,7 @@ #define RIPPLE_APP_MISC_AMENDMENTTABLE_H_INCLUDED #include -#include +#include #include #include @@ -47,6 +47,14 @@ public: virtual bool isEnabled (uint256 const& amendment) = 0; virtual bool isSupported (uint256 const& amendment) = 0; + /** + * @brief returns true if one or more amendments on the network + * have been enabled that this server does not support + * + * @return true if an unsupported feature is enabled on the network + */ + virtual bool hasUnsupportedEnabled () = 0; + virtual Json::Value getJson (int) = 0; /** Returns a Json::objectValue. */ @@ -78,7 +86,7 @@ public: NetClock::time_point closeTime, std::set const& enabledAmendments, majorityAmendments_t const& majorityAmendments, - ValidationSet const& valSet) = 0; + std::vector const& valSet) = 0; // Called by the consensus code when we need to // add feature entries to a validation @@ -112,7 +120,7 @@ public: void doVoting ( std::shared_ptr const& lastClosedLedger, - ValidationSet const& parentValidations, + std::vector const& parentValidations, std::shared_ptr const& initialPosition) { // Ask implementation what to do diff --git a/src/ripple/app/misc/FeeVote.h b/src/ripple/app/misc/FeeVote.h index ea271545e7..4e8377303d 100644 --- a/src/ripple/app/misc/FeeVote.h +++ b/src/ripple/app/misc/FeeVote.h @@ -22,7 +22,7 @@ #include #include -#include +#include #include #include @@ -72,7 +72,7 @@ public: virtual void doVoting (std::shared_ptr const& lastClosedLedger, - ValidationSet const& parentValidations, + std::vector const& parentValidations, std::shared_ptr const& initialPosition) = 0; }; diff --git a/src/ripple/app/misc/FeeVoteImpl.cpp b/src/ripple/app/misc/FeeVoteImpl.cpp index 221d43df69..9a24aca597 100644 --- a/src/ripple/app/misc/FeeVoteImpl.cpp +++ b/src/ripple/app/misc/FeeVoteImpl.cpp @@ -21,7 +21,7 @@ #include #include #include -#include +#include #include #include @@ -103,7 +103,7 @@ public: void doVoting (std::shared_ptr const& lastClosedLedger, - ValidationSet const& parentValidations, + std::vector const& parentValidations, std::shared_ptr const& initialPosition) override; }; @@ -149,8 +149,8 @@ FeeVoteImpl::doValidation( void FeeVoteImpl::doVoting( std::shared_ptr const& lastClosedLedger, - ValidationSet const& set, - std::shared_ptr const& initialPosition) + std::vector const& set, + std::shared_ptr const& initialPosition) { // LCL must be flag ledger assert ((lastClosedLedger->info().seq % 256) == 0); @@ -164,33 +164,31 @@ FeeVoteImpl::doVoting( detail::VotableInteger incReserveVote ( lastClosedLedger->fees().increment, target_.owner_reserve); - for (auto const& e : set) + for (auto const& val : set) { - STValidation const& val = *e.second; - - if (val.isTrusted ()) + if (val->isTrusted ()) { - if (val.isFieldPresent (sfBaseFee)) + if (val->isFieldPresent (sfBaseFee)) { - baseFeeVote.addVote (val.getFieldU64 (sfBaseFee)); + baseFeeVote.addVote (val->getFieldU64 (sfBaseFee)); } else { baseFeeVote.noVote (); } - if (val.isFieldPresent (sfReserveBase)) + if (val->isFieldPresent (sfReserveBase)) { - baseReserveVote.addVote (val.getFieldU32 (sfReserveBase)); + baseReserveVote.addVote (val->getFieldU32 (sfReserveBase)); } else { baseReserveVote.noVote (); } - if (val.isFieldPresent (sfReserveIncrement)) + if (val->isFieldPresent (sfReserveIncrement)) { - incReserveVote.addVote (val.getFieldU32 (sfReserveIncrement)); + incReserveVote.addVote (val->getFieldU32 (sfReserveIncrement)); } else { diff --git a/src/ripple/app/misc/NetworkOPs.cpp b/src/ripple/app/misc/NetworkOPs.cpp index 6af2675abe..1743034b11 100644 --- a/src/ripple/app/misc/NetworkOPs.cpp +++ b/src/ripple/app/misc/NetworkOPs.cpp @@ -21,10 +21,11 @@ #include #include #include +#include #include #include #include -#include +#include #include #include #include @@ -35,14 +36,13 @@ #include #include #include +#include #include #include #include #include #include #include -#include -#include #include #include #include @@ -57,12 +57,12 @@ #include #include #include +#include namespace ripple { class NetworkOPsImp final : public NetworkOPs - , public DeadlineTimer::Listener { /** * Transaction with input flags and results to be applied in batches. @@ -182,40 +182,41 @@ class NetworkOPsImp final public: // VFALCO TODO Make LedgerMaster a SharedPtr or a reference. // - NetworkOPsImp ( - Application& app, clock_type& clock, bool standalone, - std::size_t network_quorum, bool start_valid, JobQueue& job_queue, - LedgerMaster& ledgerMaster, Stoppable& parent, - beast::Journal journal) + NetworkOPsImp (Application& app, NetworkOPs::clock_type& clock, + bool standalone, std::size_t network_quorum, bool start_valid, + JobQueue& job_queue, LedgerMaster& ledgerMaster, Stoppable& parent, + ValidatorKeys const & validatorKeys, boost::asio::io_service& io_svc, + beast::Journal journal) : NetworkOPs (parent) , app_ (app) , m_clock (clock) , m_journal (journal) , m_localTX (make_LocalTxs ()) , mMode (start_valid ? omFULL : omDISCONNECTED) - , mNeedNetworkLedger (false) - , m_amendmentBlocked (false) - , m_heartbeatTimer (this) - , m_clusterTimer (this) - , mConsensus (std::make_shared(app, + , heartbeatTimer_ (io_svc) + , clusterTimer_ (io_svc) + , mConsensus (app, make_FeeVote(setup_FeeVote (app_.config().section ("voting")), app_.logs().journal("FeeVote")), ledgerMaster, *m_localTX, app.getInboundTransactions(), stopwatch(), - app_.logs().journal("LedgerConsensus"))) + validatorKeys, + app_.logs().journal("LedgerConsensus")) , m_ledgerMaster (ledgerMaster) , m_job_queue (job_queue) , m_standalone (standalone) , m_network_quorum (start_valid ? 0 : network_quorum) - , accounting_ () { } ~NetworkOPsImp() override { - jobCounter_.join(); + // This clear() is necessary to ensure the shared_ptrs in this map get + // destroyed NOW because the objects in this map invoke methods on this + // class when they are destroyed + mRpcSubMap.clear(); } public: @@ -291,7 +292,7 @@ public: // Ledger proposal/close functions. void processTrustedProposal ( - RCLCxPeerPos::pointer proposal, + RCLCxPeerPos proposal, std::shared_ptr set, NodeID const &node) override; @@ -318,7 +319,6 @@ private: std::shared_ptr const& newLCL); bool checkLastClosedLedger ( const Overlay::PeerSequence&, uint256& networkClosed); - void tryStartConsensus (); public: bool beginConsensus (uint256 const& networkClosed) override; @@ -335,35 +335,27 @@ public: void needNetworkLedger () override { - mNeedNetworkLedger = true; + needNetworkLedger_ = true; } void clearNeedNetworkLedger () override { - mNeedNetworkLedger = false; + needNetworkLedger_ = false; } bool isNeedNetworkLedger () override { - return mNeedNetworkLedger; + return needNetworkLedger_; } bool isFull () override { - return !mNeedNetworkLedger && (mMode == omFULL); + return !needNetworkLedger_ && (mMode == omFULL); } bool isAmendmentBlocked () override { - return m_amendmentBlocked; + return amendmentBlocked_; } void setAmendmentBlocked () override; void consensusViewChange () override; - PublicKey const& getValidationPublicKey () const override - { - return mConsensus->getValidationPublicKey (); - } - void setValidationKeys ( - SecretKey const& valSecret, PublicKey const& valPublic) override - { - mConsensus->setValidationKeys (valSecret, valPublic); - } + Json::Value getConsensusInfo () override; Json::Value getServerInfo (bool human, bool admin) override; void clearLedgerFetch () override; @@ -476,6 +468,7 @@ public: InfoSub::pointer findRpcSub (std::string const& strUrl) override; InfoSub::pointer addRpcSub ( std::string const& strUrl, InfoSub::ref) override; + bool tryRemoveRpcSub (std::string const& strUrl) override; //-------------------------------------------------------------------------- // @@ -484,19 +477,35 @@ public: void onStop () override { mAcquiringLedger.reset(); - m_heartbeatTimer.cancel(); - m_clusterTimer.cancel(); - - // Wait until all our in-flight Jobs are completed. - jobCounter_.join(); + { + boost::system::error_code ec; + heartbeatTimer_.cancel (ec); + if (ec) + { + JLOG (m_journal.error()) + << "NetworkOPs: heartbeatTimer cancel error: " + << ec.message(); + } + ec.clear(); + clusterTimer_.cancel (ec); + if (ec) + { + JLOG (m_journal.error()) + << "NetworkOPs: clusterTimer cancel error: " + << ec.message(); + } + } + // Make sure that any waitHandlers pending in our timers are done + // before we declare ourselves stopped. + using namespace std::chrono_literals; + waitHandlerCounter_.join("NetworkOPs", 1s, m_journal); stopped (); } private: void setHeartbeatTimer (); void setClusterTimer (); - void onDeadlineTimer (DeadlineTimer& timer) override; void processHeartbeatTimer (); void processClusterTimer (); @@ -536,14 +545,14 @@ private: std::atomic mMode; - std::atomic mNeedNetworkLedger; - bool m_amendmentBlocked; + std::atomic needNetworkLedger_ {false}; + std::atomic amendmentBlocked_ {false}; - DeadlineTimer m_heartbeatTimer; - DeadlineTimer m_clusterTimer; - JobCounter jobCounter_; + ClosureCounter waitHandlerCounter_; + boost::asio::steady_timer heartbeatTimer_; + boost::asio::steady_timer clusterTimer_; - std::shared_ptr mConsensus; + RCLConsensus mConsensus; LedgerMaster& m_ledgerMaster; std::shared_ptr mAcquiringLedger; @@ -553,13 +562,20 @@ private: subRpcMapType mRpcSubMap; - SubMapType mSubLedger; // Accepted ledgers. - SubMapType mSubManifests; // Received validator manifests. - SubMapType mSubServer; // When server changes connectivity state. - SubMapType mSubTransactions; // All accepted transactions. - SubMapType mSubRTTransactions; // All proposed and accepted transactions. - SubMapType mSubValidations; // Received validations. - SubMapType mSubPeerStatus; // peer status changes + enum SubTypes + { + sLedger, // Accepted ledgers. + sManifests, // Received validator manifests. + sServer, // When server changes connectivity state. + sTransactions, // All accepted transactions. + sRTTransactions, // All proposed and accepted transactions. + sValidations, // Received validations. + sPeerStatus, // Peer status changes. + + sLastEntry = sPeerStatus // as this name implies, any new entry must + // be ADDED ABOVE this one + }; + std::array mStreamMaps; ServerFeeSummary mLastFeeSummary; @@ -578,7 +594,7 @@ private: DispatchState mDispatchState = DispatchState::none; std::vector mTransactions; - StateAccounting accounting_; + StateAccounting accounting_ {}; }; //------------------------------------------------------------------------------ @@ -638,28 +654,61 @@ void NetworkOPsImp::setStateTimer () void NetworkOPsImp::setHeartbeatTimer () { - m_heartbeatTimer.setExpiration (LEDGER_GRANULARITY); + // Only start the timer if waitHandlerCounter_ is not yet joined. + if (auto optionalCountedHandler = waitHandlerCounter_.wrap ( + [this] (boost::system::error_code const& e) + { + if ((e.value() == boost::system::errc::success) && + (! m_job_queue.isStopped())) + { + m_job_queue.addJob (jtNETOP_TIMER, "NetOPs.heartbeat", + [this] (Job&) { processHeartbeatTimer(); }); + } + // Recover as best we can if an unexpected error occurs. + if (e.value() != boost::system::errc::success && + e.value() != boost::asio::error::operation_aborted) + { + // Try again later and hope for the best. + JLOG (m_journal.error()) + << "Heartbeat timer got error '" << e.message() + << "'. Restarting timer."; + setHeartbeatTimer(); + } + })) + { + heartbeatTimer_.expires_from_now ( + mConsensus.parms().ledgerGRANULARITY); + heartbeatTimer_.async_wait (std::move (*optionalCountedHandler)); + } } void NetworkOPsImp::setClusterTimer () { - using namespace std::chrono_literals; - m_clusterTimer.setExpiration (10s); -} - -void NetworkOPsImp::onDeadlineTimer (DeadlineTimer& timer) -{ - if (timer == m_heartbeatTimer) + // Only start the timer if waitHandlerCounter_ is not yet joined. + if (auto optionalCountedHandler = waitHandlerCounter_.wrap ( + [this] (boost::system::error_code const& e) + { + if ((e.value() == boost::system::errc::success) && + (! m_job_queue.isStopped())) + { + m_job_queue.addJob (jtNETOP_CLUSTER, "NetOPs.cluster", + [this] (Job&) { processClusterTimer(); }); + } + // Recover as best we can if an unexpected error occurs. + if (e.value() != boost::system::errc::success && + e.value() != boost::asio::error::operation_aborted) + { + // Try again later and hope for the best. + JLOG (m_journal.error()) + << "Cluster timer got error '" << e.message() + << "'. Restarting timer."; + setClusterTimer(); + } + })) { - m_job_queue.addCountedJob ( - jtNETOP_TIMER, "NetOPs.heartbeat", jobCounter_, - [this] (Job&) { processHeartbeatTimer(); }); - } - else if (timer == m_clusterTimer) - { - m_job_queue.addCountedJob ( - jtNETOP_CLUSTER, "NetOPs.cluster", jobCounter_, - [this] (Job&) { processClusterTimer(); }); + using namespace std::chrono_literals; + clusterTimer_.expires_from_now (10s); + clusterTimer_.async_wait (std::move (*optionalCountedHandler)); } } @@ -684,7 +733,7 @@ void NetworkOPsImp::processHeartbeatTimer () << "Node count (" << numPeers << ") " << "has fallen below quorum (" << m_network_quorum << ")."; } - // We do not call mConsensus->timerEntry until there + // We do not call mConsensus.timerEntry until there // are enough peers providing meaningful inputs to consensus setHeartbeatTimer (); @@ -707,7 +756,7 @@ void NetworkOPsImp::processHeartbeatTimer () } - mConsensus->timerEntry (app_.timeKeeper().closeTime()); + mConsensus.timerEntry (app_.timeKeeper().closeTime()); setHeartbeatTimer (); } @@ -762,13 +811,17 @@ void NetworkOPsImp::processClusterTimer () std::string NetworkOPsImp::strOperatingMode () const { - if (mMode == omFULL && mConsensus->haveCorrectLCL()) + if (mMode == omFULL) { - if (mConsensus->proposing ()) - return "proposing"; + auto const mode = mConsensus.mode(); + if (mode != ConsensusMode::wrongLedger) + { + if (mode == ConsensusMode::proposing) + return "proposing"; - if (mConsensus->validating ()) - return "validating"; + if (mConsensus.validating()) + return "validating"; + } } return states_[mMode]; @@ -821,8 +874,8 @@ void NetworkOPsImp::submitTransaction (std::shared_ptr const& iTrans auto tx = std::make_shared ( trans, reason, app_); - m_job_queue.addCountedJob ( - jtTRANSACTION, "submitTxn", jobCounter_, + m_job_queue.addJob ( + jtTRANSACTION, "submitTxn", [this, tx] (Job&) { auto t = tx; processTransaction(t, false, false, FailHard::no); @@ -888,8 +941,8 @@ void NetworkOPsImp::doTransactionAsync (std::shared_ptr transaction if (mDispatchState == DispatchState::none) { - if (m_job_queue.addCountedJob ( - jtBATCH, "transactionBatch", jobCounter_, + if (m_job_queue.addJob ( + jtBATCH, "transactionBatch", [this] (Job&) { transactionBatch(); })) { mDispatchState = DispatchState::scheduled; @@ -923,8 +976,8 @@ void NetworkOPsImp::doTransactionSync (std::shared_ptr transaction, if (mTransactions.size()) { // More transactions need to be applied, but by another job. - if (m_job_queue.addCountedJob ( - jtBATCH, "transactionBatch", jobCounter_, + if (m_job_queue.addJob ( + jtBATCH, "transactionBatch", [this] (Job&) { transactionBatch(); })) { mDispatchState = DispatchState::scheduled; @@ -1196,83 +1249,10 @@ Json::Value NetworkOPsImp::getOwnerInfo ( void NetworkOPsImp::setAmendmentBlocked () { - m_amendmentBlocked = true; + amendmentBlocked_ = true; setMode (omTRACKING); } -class ValidationCount -{ -public: - int trustedValidations, nodesUsing; - NodeID highNodeUsing, highValidation; - - ValidationCount () : trustedValidations (0), nodesUsing (0) - { - } - - bool operator> (const ValidationCount& v) const - { - if (trustedValidations > v.trustedValidations) - return true; - - if (trustedValidations < v.trustedValidations) - return false; - - if (trustedValidations == 0) - { - if (nodesUsing > v.nodesUsing) - return true; - - if (nodesUsing < v.nodesUsing) - return false; - - return highNodeUsing > v.highNodeUsing; - } - - return highValidation > v.highValidation; - } -}; - -void NetworkOPsImp::tryStartConsensus () -{ - uint256 networkClosed; - bool ledgerChange = checkLastClosedLedger ( - app_.overlay ().getActivePeers (), networkClosed); - - if (networkClosed.isZero ()) - return; - - // WRITEME: Unless we are in omFULL and in the process of doing a consensus, - // we must count how many nodes share our LCL, how many nodes disagree with - // our LCL, and how many validations our LCL has. We also want to check - // timing to make sure there shouldn't be a newer LCL. We need this - // information to do the next three tests. - - if (((mMode == omCONNECTED) || (mMode == omSYNCING)) && !ledgerChange) - { - // Count number of peers that agree with us and UNL nodes whose - // validations we have for LCL. If the ledger is good enough, go to - // omTRACKING - TODO - if (!mNeedNetworkLedger) - setMode (omTRACKING); - } - - if (((mMode == omCONNECTED) || (mMode == omTRACKING)) && !ledgerChange) - { - // check if the ledger is good enough to go to omFULL - // Note: Do not go to omFULL if we don't have the previous ledger - // check if the ledger is bad enough to go to omCONNECTED -- TODO - auto current = m_ledgerMaster.getCurrentLedger(); - if (app_.timeKeeper().now() < - (current->info().parentCloseTime + 2* current->info().closeTimeResolution)) - { - setMode (omFULL); - } - } - - beginConsensus (networkClosed); -} - bool NetworkOPsImp::checkLastClosedLedger ( const Overlay::PeerSequence& peerList, uint256& networkClosed) { @@ -1293,20 +1273,43 @@ bool NetworkOPsImp::checkLastClosedLedger ( JLOG(m_journal.trace()) << "OurClosed: " << closedLedger; JLOG(m_journal.trace()) << "PrevClosed: " << prevClosedLedger; + struct ValidationCount + { + int trustedValidations, nodesUsing; + + ValidationCount() : trustedValidations(0), nodesUsing(0) + { + } + + auto + asTie() const + { + return std::tie(trustedValidations, nodesUsing); + } + + bool + operator>(const ValidationCount& v) const + { + return asTie() > v.asTie(); + } + + bool + operator==(const ValidationCount& v) const + { + return asTie() == v.asTie(); + } + }; + hash_map ledgers; { - auto current = app_.getValidations ().getCurrentValidations ( - closedLedger, prevClosedLedger, - m_ledgerMaster.getValidLedgerIndex()); + hash_map current = + app_.getValidations().currentTrustedDistribution( + closedLedger, + prevClosedLedger, + m_ledgerMaster.getValidLedgerIndex()); for (auto& it: current) - { - auto& vc = ledgers[it.first]; - vc.trustedValidations += it.second.first; - - if (it.second.second > vc.highValidation) - vc.highValidation = it.second.second; - } + ledgers[it.first].trustedValidations += it.second; } auto& ourVC = ledgers[closedLedger]; @@ -1314,10 +1317,6 @@ bool NetworkOPsImp::checkLastClosedLedger ( if (mMode >= omTRACKING) { ++ourVC.nodesUsing; - auto const ourNodeID = calcNodeID( - app_.nodeIdentity().first); - if (ourNodeID > ourVC.highNodeUsing) - ourVC.highNodeUsing = ourNodeID; } for (auto& peer: peerList) @@ -1325,23 +1324,7 @@ bool NetworkOPsImp::checkLastClosedLedger ( uint256 peerLedger = peer->getClosedLedgerHash (); if (peerLedger.isNonZero ()) - { - try - { - auto& vc = ledgers[peerLedger]; - auto const nodeId = calcNodeID(peer->getNodePublic ()); - if (vc.nodesUsing == 0 || nodeId > vc.highNodeUsing) - { - vc.highNodeUsing = nodeId; - } - - ++vc.nodesUsing; - } - catch (std::exception const&) - { - // Peer is likely not connected anymore - } - } + ++ledgers[peerLedger].nodesUsing; } auto bestVC = ledgers[closedLedger]; @@ -1359,17 +1342,20 @@ bool NetworkOPsImp::checkLastClosedLedger ( // Temporary logging to make sure tiebreaking isn't broken if (it.second.trustedValidations > 0) JLOG(m_journal.trace()) - << " TieBreakTV: " << it.second.highValidation; + << " TieBreakTV: " << it.first; else { if (it.second.nodesUsing > 0) { JLOG(m_journal.trace()) - << " TieBreakNU: " << it.second.highNodeUsing; + << " TieBreakNU: " << it.first; } } - if (it.second > bestVC) + // Switch to a ledger with more support + // or the one with higher hash if they have the same support + if (it.second > bestVC || + (it.second == bestVC && it.first > closedLedger)) { bestVC = it.second; closedLedger = it.first; @@ -1508,7 +1494,7 @@ bool NetworkOPsImp::beginConsensus (uint256 const& networkClosed) app_.validators().onConsensusStart ( app_.getValidations().getCurrentPublicKeys ()); - mConsensus->startRound ( + mConsensus.startRound ( app_.timeKeeper().closeTime(), networkClosed, prevLedger); @@ -1519,19 +1505,19 @@ bool NetworkOPsImp::beginConsensus (uint256 const& networkClosed) uint256 NetworkOPsImp::getConsensusLCL () { - return mConsensus->prevLedgerID (); + return mConsensus.prevLedgerID (); } void NetworkOPsImp::processTrustedProposal ( - RCLCxPeerPos::pointer peerPos, + RCLCxPeerPos peerPos, std::shared_ptr set, NodeID const& node) { - mConsensus->storeProposal (peerPos, node); - - if (mConsensus->peerProposal ( - app_.timeKeeper().closeTime(), peerPos->proposal())) - app_.overlay().relay(*set, peerPos->getSuppressionID()); + if (mConsensus.peerProposal( + app_.timeKeeper().closeTime(), peerPos)) + { + app_.overlay().relay(*set, peerPos.suppressionID()); + } else JLOG(m_journal.info()) << "Not relaying trusted proposal"; } @@ -1554,7 +1540,7 @@ NetworkOPsImp::mapComplete ( // We acquired it because consensus asked us to if (fromAcquire) - mConsensus->gotTxSet ( + mConsensus.gotTxSet ( app_.timeKeeper().closeTime(), RCLTxSet{map}); } @@ -1572,7 +1558,42 @@ void NetworkOPsImp::endConsensus () } } - tryStartConsensus(); + uint256 networkClosed; + bool ledgerChange = checkLastClosedLedger ( + app_.overlay ().getActivePeers (), networkClosed); + + if (networkClosed.isZero ()) + return; + + // WRITEME: Unless we are in omFULL and in the process of doing a consensus, + // we must count how many nodes share our LCL, how many nodes disagree with + // our LCL, and how many validations our LCL has. We also want to check + // timing to make sure there shouldn't be a newer LCL. We need this + // information to do the next three tests. + + if (((mMode == omCONNECTED) || (mMode == omSYNCING)) && !ledgerChange) + { + // Count number of peers that agree with us and UNL nodes whose + // validations we have for LCL. If the ledger is good enough, go to + // omTRACKING - TODO + if (!needNetworkLedger_) + setMode (omTRACKING); + } + + if (((mMode == omCONNECTED) || (mMode == omTRACKING)) && !ledgerChange) + { + // check if the ledger is good enough to go to omFULL + // Note: Do not go to omFULL if we don't have the previous ledger + // check if the ledger is bad enough to go to omCONNECTED -- TODO + auto current = m_ledgerMaster.getCurrentLedger(); + if (app_.timeKeeper().now() < + (current->info().parentCloseTime + 2* current->info().closeTimeResolution)) + { + setMode (omFULL); + } + } + + beginConsensus (networkClosed); } void NetworkOPsImp::consensusViewChange () @@ -1586,7 +1607,7 @@ void NetworkOPsImp::pubManifest (Manifest const& mo) // VFALCO consider std::shared_mutex ScopedLockType sl (mSubLock); - if (!mSubManifests.empty ()) + if (!mStreamMaps[sManifests].empty ()) { Json::Value jvObj (Json::objectValue); @@ -1599,7 +1620,8 @@ void NetworkOPsImp::pubManifest (Manifest const& mo) jvObj [jss::signature] = strHex (mo.getSignature ()); jvObj [jss::master_signature] = strHex (mo.getMasterSignature ()); - for (auto i = mSubManifests.begin (); i != mSubManifests.end (); ) + for (auto i = mStreamMaps[sManifests].begin (); + i != mStreamMaps[sManifests].end (); ) { if (auto p = i->second.lock()) { @@ -1608,7 +1630,7 @@ void NetworkOPsImp::pubManifest (Manifest const& mo) } else { - i = mSubManifests.erase (i); + i = mStreamMaps[sManifests].erase (i); } } } @@ -1654,7 +1676,7 @@ void NetworkOPsImp::pubServer () // ScopedLockType sl (mSubLock); - if (!mSubServer.empty ()) + if (!mStreamMaps[sServer].empty ()) { Json::Value jvObj (Json::objectValue); @@ -1697,7 +1719,8 @@ void NetworkOPsImp::pubServer () mLastFeeSummary = f; - for (auto i = mSubServer.begin (); i != mSubServer.end (); ) + for (auto i = mStreamMaps[sServer].begin (); + i != mStreamMaps[sServer].end (); ) { InfoSub::pointer p = i->second.lock (); @@ -1711,7 +1734,7 @@ void NetworkOPsImp::pubServer () } else { - i = mSubServer.erase (i); + i = mStreamMaps[sServer].erase (i); } } } @@ -1723,7 +1746,7 @@ void NetworkOPsImp::pubValidation (STValidation::ref val) // VFALCO consider std::shared_mutex ScopedLockType sl (mSubLock); - if (!mSubValidations.empty ()) + if (!mStreamMaps[sValidations].empty ()) { Json::Value jvObj (Json::objectValue); @@ -1762,7 +1785,8 @@ void NetworkOPsImp::pubValidation (STValidation::ref val) if (auto const reserveInc = (*val)[~sfReserveIncrement]) jvObj [jss::reserve_inc] = *reserveInc; - for (auto i = mSubValidations.begin (); i != mSubValidations.end (); ) + for (auto i = mStreamMaps[sValidations].begin (); + i != mStreamMaps[sValidations].end (); ) { if (auto p = i->second.lock()) { @@ -1771,7 +1795,7 @@ void NetworkOPsImp::pubValidation (STValidation::ref val) } else { - i = mSubValidations.erase (i); + i = mStreamMaps[sValidations].erase (i); } } } @@ -1782,13 +1806,14 @@ void NetworkOPsImp::pubPeerStatus ( { ScopedLockType sl (mSubLock); - if (!mSubPeerStatus.empty ()) + if (!mStreamMaps[sPeerStatus].empty ()) { Json::Value jvObj (func()); jvObj [jss::type] = "peerStatusChange"; - for (auto i = mSubPeerStatus.begin (); i != mSubPeerStatus.end (); ) + for (auto i = mStreamMaps[sPeerStatus].begin (); + i != mStreamMaps[sPeerStatus].end (); ) { InfoSub::pointer p = i->second.lock (); @@ -1799,7 +1824,7 @@ void NetworkOPsImp::pubPeerStatus ( } else { - i = mSubValidations.erase (i); + i = mStreamMaps[sPeerStatus].erase (i); } } } @@ -1818,7 +1843,7 @@ void NetworkOPsImp::setMode (OperatingMode om) om = omCONNECTED; } - if ((om > omTRACKING) && m_amendmentBlocked) + if ((om > omTRACKING) && amendmentBlocked_) om = omTRACKING; if (mMode == om) @@ -2097,12 +2122,12 @@ bool NetworkOPsImp::recvValidation ( JLOG(m_journal.debug()) << "recvValidation " << val->getLedgerHash () << " from " << source; pubValidation (val); - return app_.getValidations ().addValidation (val, source); + return handleNewValidation(app_, val, source); } Json::Value NetworkOPsImp::getConsensusInfo () { - return mConsensus->getJson (true); + return mConsensus.getJson (true); } Json::Value NetworkOPsImp::getServerInfo (bool human, bool admin) @@ -2117,7 +2142,7 @@ Json::Value NetworkOPsImp::getServerInfo (bool human, bool admin) info [jss::server_state] = strOperatingMode (); - if (mNeedNetworkLedger) + if (needNetworkLedger_) info[jss::network_ledger] = "waiting"; info[jss::validation_quorum] = static_cast( @@ -2128,7 +2153,7 @@ Json::Value NetworkOPsImp::getServerInfo (bool human, bool admin) if (admin) { - if (getValidationPublicKey().size ()) + if (!app_.getValidationPublicKey().empty()) { info[jss::pubkey_validator] = toBase58 ( TokenType::TOKEN_NODE_PUBLIC, @@ -2147,7 +2172,7 @@ Json::Value NetworkOPsImp::getServerInfo (bool human, bool admin) info[jss::complete_ledgers] = app_.getLedgerMaster ().getCompleteLedgers (); - if (m_amendmentBlocked) + if (amendmentBlocked_) info[jss::amendment_blocked] = true; auto const fp = m_ledgerMaster.getFetchPackCacheSize (); @@ -2158,23 +2183,23 @@ Json::Value NetworkOPsImp::getServerInfo (bool human, bool admin) info[jss::peers] = Json::UInt (app_.overlay ().size ()); Json::Value lastClose = Json::objectValue; - lastClose[jss::proposers] = Json::UInt(mConsensus->prevProposers()); + lastClose[jss::proposers] = Json::UInt(mConsensus.prevProposers()); if (human) { lastClose[jss::converge_time_s] = std::chrono::duration{ - mConsensus->prevRoundTime()}.count(); + mConsensus.prevRoundTime()}.count(); } else { lastClose[jss::converge_time] = - Json::Int (mConsensus->prevRoundTime().count()); + Json::Int (mConsensus.prevRoundTime().count()); } info[jss::last_close] = lastClose; - // info[jss::consensus] = mConsensus->getJson(); + // info[jss::consensus] = mConsensus.getJson(); if (admin) info[jss::load] = m_job_queue.getJson (); @@ -2354,8 +2379,8 @@ void NetworkOPsImp::pubProposedTransaction ( { ScopedLockType sl (mSubLock); - auto it = mSubRTTransactions.begin (); - while (it != mSubRTTransactions.end ()) + auto it = mStreamMaps[sRTTransactions].begin (); + while (it != mStreamMaps[sRTTransactions].end ()) { InfoSub::pointer p = it->second.lock (); @@ -2366,7 +2391,7 @@ void NetworkOPsImp::pubProposedTransaction ( } else { - it = mSubRTTransactions.erase (it); + it = mStreamMaps[sRTTransactions].erase (it); } } } @@ -2395,7 +2420,7 @@ void NetworkOPsImp::pubLedger ( { ScopedLockType sl (mSubLock); - if (!mSubLedger.empty ()) + if (!mStreamMaps[sLedger].empty ()) { Json::Value jvObj (Json::objectValue); @@ -2419,8 +2444,8 @@ void NetworkOPsImp::pubLedger ( = app_.getLedgerMaster ().getCompleteLedgers (); } - auto it = mSubLedger.begin (); - while (it != mSubLedger.end ()) + auto it = mStreamMaps[sLedger].begin (); + while (it != mStreamMaps[sLedger].end ()) { InfoSub::pointer p = it->second.lock (); if (p) @@ -2429,7 +2454,7 @@ void NetworkOPsImp::pubLedger ( ++it; } else - it = mSubLedger.erase (it); + it = mStreamMaps[sLedger].erase (it); } } } @@ -2451,8 +2476,8 @@ void NetworkOPsImp::reportFeeChange () // only schedule the job if something has changed if (f != mLastFeeSummary) { - m_job_queue.addCountedJob ( - jtCLIENT, "reportFeeChange->pubServer", jobCounter_, + m_job_queue.addJob ( + jtCLIENT, "reportFeeChange->pubServer", [this] (Job&) { pubServer(); }); } } @@ -2522,8 +2547,8 @@ void NetworkOPsImp::pubValidatedTransaction ( { ScopedLockType sl (mSubLock); - auto it = mSubTransactions.begin (); - while (it != mSubTransactions.end ()) + auto it = mStreamMaps[sTransactions].begin (); + while (it != mStreamMaps[sTransactions].end ()) { InfoSub::pointer p = it->second.lock (); @@ -2533,12 +2558,12 @@ void NetworkOPsImp::pubValidatedTransaction ( ++it; } else - it = mSubTransactions.erase (it); + it = mStreamMaps[sTransactions].erase (it); } - it = mSubRTTransactions.begin (); + it = mStreamMaps[sRTTransactions].begin (); - while (it != mSubRTTransactions.end ()) + while (it != mStreamMaps[sRTTransactions].end ()) { InfoSub::pointer p = it->second.lock (); @@ -2548,7 +2573,7 @@ void NetworkOPsImp::pubValidatedTransaction ( ++it; } else - it = mSubRTTransactions.erase (it); + it = mStreamMaps[sRTTransactions].erase (it); } } app_.getOrderBookDB ().processTxn (alAccepted, alTx, jvObj); @@ -2748,7 +2773,7 @@ std::uint32_t NetworkOPsImp::acceptLedger ( // FIXME Could we improve on this and remove the need for a specialized // API in Consensus? beginConsensus (m_ledgerMaster.getClosedLedger()->info().hash); - mConsensus->simulate (app_.timeKeeper().closeTime(), consensusDelay); + mConsensus.simulate (app_.timeKeeper().closeTime(), consensusDelay); return m_ledgerMaster.getCurrentLedger ()->info().seq; } @@ -2775,28 +2800,30 @@ bool NetworkOPsImp::subLedger (InfoSub::ref isrListener, Json::Value& jvResult) } ScopedLockType sl (mSubLock); - return mSubLedger.emplace (isrListener->getSeq (), isrListener).second; + return mStreamMaps[sLedger].emplace ( + isrListener->getSeq (), isrListener).second; } // <-- bool: true=erased, false=was not there bool NetworkOPsImp::unsubLedger (std::uint64_t uSeq) { ScopedLockType sl (mSubLock); - return mSubLedger.erase (uSeq); + return mStreamMaps[sLedger].erase (uSeq); } // <-- bool: true=added, false=already there bool NetworkOPsImp::subManifests (InfoSub::ref isrListener) { ScopedLockType sl (mSubLock); - return mSubManifests.emplace (isrListener->getSeq (), isrListener).second; + return mStreamMaps[sManifests].emplace ( + isrListener->getSeq (), isrListener).second; } // <-- bool: true=erased, false=was not there bool NetworkOPsImp::unsubManifests (std::uint64_t uSeq) { ScopedLockType sl (mSubLock); - return mSubManifests.erase (uSeq); + return mStreamMaps[sManifests].erase (uSeq); } // <-- bool: true=added, false=already there @@ -2825,21 +2852,22 @@ bool NetworkOPsImp::subServer (InfoSub::ref isrListener, Json::Value& jvResult, app_.nodeIdentity().first); ScopedLockType sl (mSubLock); - return mSubServer.emplace (isrListener->getSeq (), isrListener).second; + return mStreamMaps[sServer].emplace ( + isrListener->getSeq (), isrListener).second; } // <-- bool: true=erased, false=was not there bool NetworkOPsImp::unsubServer (std::uint64_t uSeq) { ScopedLockType sl (mSubLock); - return mSubServer.erase (uSeq); + return mStreamMaps[sServer].erase (uSeq); } // <-- bool: true=added, false=already there bool NetworkOPsImp::subTransactions (InfoSub::ref isrListener) { ScopedLockType sl (mSubLock); - return mSubTransactions.emplace ( + return mStreamMaps[sTransactions].emplace ( isrListener->getSeq (), isrListener).second; } @@ -2847,14 +2875,14 @@ bool NetworkOPsImp::subTransactions (InfoSub::ref isrListener) bool NetworkOPsImp::unsubTransactions (std::uint64_t uSeq) { ScopedLockType sl (mSubLock); - return mSubTransactions.erase (uSeq); + return mStreamMaps[sTransactions].erase (uSeq); } // <-- bool: true=added, false=already there bool NetworkOPsImp::subRTTransactions (InfoSub::ref isrListener) { ScopedLockType sl (mSubLock); - return mSubRTTransactions.emplace ( + return mStreamMaps[sRTTransactions].emplace ( isrListener->getSeq (), isrListener).second; } @@ -2862,35 +2890,37 @@ bool NetworkOPsImp::subRTTransactions (InfoSub::ref isrListener) bool NetworkOPsImp::unsubRTTransactions (std::uint64_t uSeq) { ScopedLockType sl (mSubLock); - return mSubRTTransactions.erase (uSeq); + return mStreamMaps[sRTTransactions].erase (uSeq); } // <-- bool: true=added, false=already there bool NetworkOPsImp::subValidations (InfoSub::ref isrListener) { ScopedLockType sl (mSubLock); - return mSubValidations.emplace (isrListener->getSeq (), isrListener).second; + return mStreamMaps[sValidations].emplace ( + isrListener->getSeq (), isrListener).second; } // <-- bool: true=erased, false=was not there bool NetworkOPsImp::unsubValidations (std::uint64_t uSeq) { ScopedLockType sl (mSubLock); - return mSubValidations.erase (uSeq); + return mStreamMaps[sValidations].erase (uSeq); } // <-- bool: true=added, false=already there bool NetworkOPsImp::subPeerStatus (InfoSub::ref isrListener) { ScopedLockType sl (mSubLock); - return mSubPeerStatus.emplace (isrListener->getSeq (), isrListener).second; + return mStreamMaps[sPeerStatus].emplace ( + isrListener->getSeq (), isrListener).second; } // <-- bool: true=erased, false=was not there bool NetworkOPsImp::unsubPeerStatus (std::uint64_t uSeq) { ScopedLockType sl (mSubLock); - return mSubPeerStatus.erase (uSeq); + return mStreamMaps[sPeerStatus].erase (uSeq); } InfoSub::pointer NetworkOPsImp::findRpcSub (std::string const& strUrl) @@ -2915,6 +2945,25 @@ InfoSub::pointer NetworkOPsImp::addRpcSub ( return rspEntry; } +bool NetworkOPsImp::tryRemoveRpcSub (std::string const& strUrl) +{ + ScopedLockType sl (mSubLock); + auto pInfo = findRpcSub(strUrl); + + if (!pInfo) + return false; + + // check to see if any of the stream maps still hold a weak reference to + // this entry before removing + for (SubMapType const& map : mStreamMaps) + { + if (map.find(pInfo->getSeq()) != map.end()) + return false; + } + mRpcSubMap.erase(strUrl); + return true; +} + #ifndef USE_NEW_BOOK_PAGE // NIKB FIXME this should be looked at. There's no reason why this shouldn't @@ -3268,10 +3317,6 @@ NetworkOPs::NetworkOPs (Stoppable& parent) { } -NetworkOPs::~NetworkOPs () -{ -} - //------------------------------------------------------------------------------ @@ -3318,13 +3363,15 @@ Json::Value NetworkOPsImp::StateAccounting::json() const //------------------------------------------------------------------------------ std::unique_ptr -make_NetworkOPs (Application& app, NetworkOPs::clock_type& clock, bool standalone, - std::size_t network_quorum, bool startvalid, - JobQueue& job_queue, LedgerMaster& ledgerMaster, - Stoppable& parent, beast::Journal journal) +make_NetworkOPs (Application& app, NetworkOPs::clock_type& clock, + bool standalone, std::size_t network_quorum, bool startvalid, + JobQueue& job_queue, LedgerMaster& ledgerMaster, Stoppable& parent, + ValidatorKeys const & validatorKeys, boost::asio::io_service& io_svc, + beast::Journal journal) { - return std::make_unique (app, clock, standalone, network_quorum, - startvalid, job_queue, ledgerMaster, parent, journal); + return std::make_unique (app, clock, standalone, + network_quorum, startvalid, job_queue, ledgerMaster, parent, + validatorKeys, io_svc, journal); } } // ripple diff --git a/src/ripple/app/misc/NetworkOPs.h b/src/ripple/app/misc/NetworkOPs.h index 4fee5292e9..dd86629937 100644 --- a/src/ripple/app/misc/NetworkOPs.h +++ b/src/ripple/app/misc/NetworkOPs.h @@ -20,14 +20,14 @@ #ifndef RIPPLE_APP_MISC_NETWORKOPS_H_INCLUDED #define RIPPLE_APP_MISC_NETWORKOPS_H_INCLUDED -#include -#include #include #include +#include +#include #include #include +#include #include -#include #include #include @@ -41,6 +41,7 @@ namespace ripple { class Peer; class LedgerMaster; class Transaction; +class ValidatorKeys; // This is the primary interface into the "client" portion of the program. // Code that wants to do normal operations on the network such as @@ -95,7 +96,7 @@ public: } public: - virtual ~NetworkOPs () = 0; + ~NetworkOPs () override = default; //-------------------------------------------------------------------------- // @@ -150,7 +151,7 @@ public: //-------------------------------------------------------------------------- // ledger proposal/close functions - virtual void processTrustedProposal (RCLCxPeerPos::pointer peerPos, + virtual void processTrustedProposal (RCLCxPeerPos peerPos, std::shared_ptr set, NodeID const& node) = 0; @@ -174,9 +175,6 @@ public: virtual bool isAmendmentBlocked () = 0; virtual void setAmendmentBlocked () = 0; virtual void consensusViewChange () = 0; - virtual PublicKey const& getValidationPublicKey () const = 0; - virtual void setValidationKeys ( - SecretKey const& valSecret, PublicKey const& valPublic) = 0; virtual Json::Value getConsensusInfo () = 0; virtual Json::Value getServerInfo (bool human, bool admin) = 0; @@ -239,10 +237,11 @@ public: //------------------------------------------------------------------------------ std::unique_ptr -make_NetworkOPs (Application& app, NetworkOPs::clock_type& clock, bool standalone, - std::size_t network_quorum, bool start_valid, - JobQueue& job_queue, LedgerMaster& ledgerMaster, - Stoppable& parent, beast::Journal journal); +make_NetworkOPs (Application& app, NetworkOPs::clock_type& clock, + bool standalone, std::size_t network_quorum, bool start_valid, + JobQueue& job_queue, LedgerMaster& ledgerMaster, Stoppable& parent, + ValidatorKeys const & validatorKeys, boost::asio::io_service& io_svc, + beast::Journal journal); } // ripple diff --git a/src/ripple/app/misc/Validations.cpp b/src/ripple/app/misc/Validations.cpp deleted file mode 100644 index 9f11aae5c4..0000000000 --- a/src/ripple/app/misc/Validations.cpp +++ /dev/null @@ -1,557 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2013 Ripple Labs Inc. - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ -//============================================================================== - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace ripple { - -class ValidationsImp : public Validations -{ -private: - using LockType = std::mutex; - using ScopedLockType = std::lock_guard ; - using ScopedUnlockType = GenericScopedUnlock ; - - Application& app_; - std::mutex mutable mLock; - - TaggedCache mValidations; - ValidationSet mCurrentValidations; - std::vector mStaleValidations; - - bool mWriting; - beast::Journal j_; - -private: - std::shared_ptr findCreateSet (uint256 const& ledgerHash) - { - auto j = mValidations.fetch (ledgerHash); - - if (!j) - { - j = std::make_shared (); - mValidations.canonicalize (ledgerHash, j); - } - - return j; - } - - std::shared_ptr findSet (uint256 const& ledgerHash) - { - return mValidations.fetch (ledgerHash); - } - -public: - explicit - ValidationsImp (Application& app) - : app_ (app) - , mValidations ("Validations", 4096, 600, stopwatch(), - app.journal("TaggedCache")) - , mWriting (false) - , j_ (app.journal ("Validations")) - { - mStaleValidations.reserve (512); - } - -private: - bool addValidation (STValidation::ref val, std::string const& source) override - { - auto signer = val->getSignerPublic (); - auto hash = val->getLedgerHash (); - bool isCurrent = current (val); - - auto pubKey = app_.validators ().getTrustedKey (signer); - - if (!val->isTrusted() && pubKey) - val->setTrusted(); - - // Do not process partial validations. - if(!val->isFull()) - { - JLOG (j_.debug()) << - "Val (partial) for " << hash << - " from " << toBase58 (TokenType::TOKEN_NODE_PUBLIC, signer) << - " ignored " << (val->isTrusted () ? "trusted/" : "UNtrusted/") << - (isCurrent ? "current" : "stale"); - - // Only forward if current - return isCurrent && val->isTrusted(); - } - - if (!val->isTrusted ()) - { - JLOG (j_.trace()) << - "Node " << toBase58 (TokenType::TOKEN_NODE_PUBLIC, signer) << - " not in UNL st=" << val->getSignTime().time_since_epoch().count() << - ", hash=" << hash << - ", shash=" << val->getSigningHash () << - " src=" << source; - } - - if (! pubKey) - pubKey = app_.validators ().getListedKey (signer); - - if (isCurrent && - (val->isTrusted () || pubKey)) - { - ScopedLockType sl (mLock); - - if (!findCreateSet (hash)->insert ( - std::make_pair (*pubKey, val)).second) - return false; - - auto it = mCurrentValidations.find (*pubKey); - - if (it == mCurrentValidations.end ()) - { - // No previous validation from this validator - mCurrentValidations.emplace (*pubKey, val); - } - else if (!it->second) - { - // Previous validation has expired - it->second = val; - } - else - { - auto const oldSeq = (*it->second)[~sfLedgerSequence]; - auto const newSeq = (*val)[~sfLedgerSequence]; - - if (oldSeq && newSeq && *oldSeq == *newSeq) - { - JLOG (j_.warn()) << - "Trusted node " << - toBase58 (TokenType::TOKEN_NODE_PUBLIC, *pubKey) << - " published multiple validations for ledger " << - *oldSeq; - - // Remove current validation for the revoked signing key - if (signer != it->second->getSignerPublic()) - { - auto set = findSet (it->second->getLedgerHash ()); - if (set) - set->erase (*pubKey); - } - } - - if (val->getSignTime () > it->second->getSignTime () || - signer != it->second->getSignerPublic()) - { - // This is either a newer validation or a new signing key - val->setPreviousHash (it->second->getLedgerHash ()); - mStaleValidations.push_back (it->second); - it->second = val; - condWrite (); - } - else - { - // We already have a newer validation from this source - isCurrent = false; - } - } - } - - JLOG (j_.debug()) << - "Val for " << hash << - " from " << toBase58 (TokenType::TOKEN_NODE_PUBLIC, signer) << - " added " << (val->isTrusted () ? "trusted/" : "UNtrusted/") << - (isCurrent ? "current" : "stale"); - - if (val->isTrusted () && isCurrent) - { - app_.getLedgerMaster ().checkAccept (hash, val->getFieldU32 (sfLedgerSequence)); - return true; - } - - // FIXME: This never forwards untrusted validations - return false; - } - - ValidationSet getValidations (uint256 const& ledger) override - { - { - ScopedLockType sl (mLock); - auto set = findSet (ledger); - - if (set) - return *set; - } - return ValidationSet (); - } - - bool current (STValidation::ref val) override - { - // Because this can be called on untrusted, possibly - // malicious validations, we do our math in a way - // that avoids any chance of overflowing or underflowing - // the signing time. - - auto const now = app_.timeKeeper().closeTime(); - auto const signTime = val->getSignTime(); - - return - (signTime > (now - VALIDATION_VALID_EARLY)) && - (signTime < (now + VALIDATION_VALID_WALL)) && - ((val->getSeenTime() == NetClock::time_point{}) || - (val->getSeenTime() < (now + VALIDATION_VALID_LOCAL))); - } - - std::size_t - getTrustedValidationCount (uint256 const& ledger) override - { - std::size_t trusted = 0; - ScopedLockType sl (mLock); - auto set = findSet (ledger); - - if (set) - { - for (auto& it: *set) - { - if (it.second->isTrusted ()) - ++trusted; - } - } - - return trusted; - } - - std::vector - fees (uint256 const& ledger, std::uint64_t base) override - { - std::vector result; - std::lock_guard lock (mLock); - auto const set = findSet (ledger); - if (set) - { - for (auto const& v : *set) - { - if (v.second->isTrusted()) - { - if (v.second->isFieldPresent(sfLoadFee)) - result.push_back(v.second->getFieldU32(sfLoadFee)); - else - result.push_back(base); - } - } - } - - return result; - } - - int getNodesAfter (uint256 const& ledger) override - { - // Number of trusted nodes that have moved past this ledger - int count = 0; - ScopedLockType sl (mLock); - for (auto& it: mCurrentValidations) - { - if (it.second->isTrusted () && it.second->isPreviousHash (ledger)) - ++count; - } - return count; - } - - int getLoadRatio (bool overLoaded) override - { - // how many trusted nodes are able to keep up, higher is better - int goodNodes = overLoaded ? 1 : 0; - int badNodes = overLoaded ? 0 : 1; - { - ScopedLockType sl (mLock); - for (auto& it: mCurrentValidations) - { - if (it.second->isTrusted ()) - { - if (it.second->isFull ()) - ++goodNodes; - else - ++badNodes; - } - } - } - return (goodNodes * 100) / (goodNodes + badNodes); - } - - std::list getCurrentTrustedValidations () override - { - std::list ret; - - ScopedLockType sl (mLock); - auto it = mCurrentValidations.begin (); - - while (it != mCurrentValidations.end ()) - { - if (!it->second) // contains no record - it = mCurrentValidations.erase (it); - else if (! current (it->second)) - { - // contains a stale record - mStaleValidations.push_back (it->second); - it->second.reset (); - condWrite (); - it = mCurrentValidations.erase (it); - } - else - { - // contains a live record - if (it->second->isTrusted ()) - ret.push_back (it->second); - - ++it; - } - } - - return ret; - } - - hash_set getCurrentPublicKeys () override - { - hash_set ret; - - ScopedLockType sl (mLock); - auto it = mCurrentValidations.begin (); - - while (it != mCurrentValidations.end ()) - { - if (!it->second) // contains no record - it = mCurrentValidations.erase (it); - else if (! current (it->second)) - { - // contains a stale record - mStaleValidations.push_back (it->second); - it->second.reset (); - condWrite (); - it = mCurrentValidations.erase (it); - } - else - { - // contains a live record - ret.insert (it->first); - - ++it; - } - } - - return ret; - } - - LedgerToValidationCounter getCurrentValidations ( - uint256 currentLedger, - uint256 priorLedger, - LedgerIndex cutoffBefore) override - { - bool valCurrentLedger = currentLedger.isNonZero (); - bool valPriorLedger = priorLedger.isNonZero (); - - LedgerToValidationCounter ret; - - ScopedLockType sl (mLock); - auto it = mCurrentValidations.begin (); - - while (it != mCurrentValidations.end ()) - { - if (!it->second) // contains no record - it = mCurrentValidations.erase (it); - else if (! current (it->second)) - { - // contains a stale record - mStaleValidations.push_back (it->second); - it->second.reset (); - condWrite (); - it = mCurrentValidations.erase (it); - } - else if (! it->second->isTrusted()) - ++it; - else if (! it->second->isFieldPresent (sfLedgerSequence) || - (it->second->getFieldU32 (sfLedgerSequence) >= cutoffBefore)) - { - // contains a live record - bool countPreferred = valCurrentLedger && (it->second->getLedgerHash () == currentLedger); - - if (!countPreferred && // allow up to one ledger slip in either direction - ((valCurrentLedger && it->second->isPreviousHash (currentLedger)) || - (valPriorLedger && (it->second->getLedgerHash () == priorLedger)))) - { - countPreferred = true; - JLOG (j_.trace()) << "Counting for " << currentLedger << " not " << it->second->getLedgerHash (); - } - - ValidationCounter& p = countPreferred ? ret[currentLedger] : ret[it->second->getLedgerHash ()]; - ++ (p.first); - auto ni = it->second->getNodeID (); - - if (ni > p.second) - p.second = ni; - - ++it; - } - else - { - ++it; - } - } - - return ret; - } - - std::vector - getValidationTimes (uint256 const& hash) override - { - std::vector times; - ScopedLockType sl (mLock); - if (auto j = findSet (hash)) - for (auto& it : *j) - if (it.second->isTrusted()) - times.push_back (it.second->getSignTime()); - return times; - } - - void flush () override - { - bool anyNew = false; - - JLOG (j_.info()) << "Flushing validations"; - ScopedLockType sl (mLock); - for (auto& it: mCurrentValidations) - { - if (it.second) - mStaleValidations.push_back (it.second); - - anyNew = true; - } - mCurrentValidations.clear (); - - if (anyNew) - condWrite (); - - while (mWriting) - { - ScopedUnlockType sul (mLock); - std::this_thread::sleep_for (std::chrono::milliseconds (100)); - } - - JLOG (j_.debug()) << "Validations flushed"; - } - - void condWrite () - { - if (mWriting) - return; - - mWriting = true; - app_.getJobQueue ().addJob ( - jtWRITE, "Validations::doWrite", - [this] (Job&) { doWrite(); }); - } - - void doWrite () - { - auto event = app_.getJobQueue ().makeLoadEvent (jtDISK, "ValidationWrite"); - - std::string insVal ("INSERT INTO Validations " - "(InitialSeq, LedgerSeq, LedgerHash,NodePubKey,SignTime,RawData) " - "VALUES (:initialSeq, :ledgerSeq, :ledgerHash,:nodePubKey,:signTime,:rawData);"); - std::string findSeq("SELECT LedgerSeq FROM Ledgers WHERE Ledgerhash=:ledgerHash;"); - - ScopedLockType sl (mLock); - assert (mWriting); - - while (!mStaleValidations.empty ()) - { - std::vector vector; - vector.reserve (512); - mStaleValidations.swap (vector); - - { - ScopedUnlockType sul (mLock); - { - auto db = app_.getLedgerDB ().checkoutDb (); - - Serializer s (1024); - soci::transaction tr(*db); - for (auto it: vector) - { - s.erase (); - it->add (s); - - auto const ledgerHash = to_string(it->getLedgerHash()); - - boost::optional ledgerSeq; - *db << findSeq, soci::use(ledgerHash), - soci::into(ledgerSeq); - - auto const initialSeq = ledgerSeq.value_or( - app_.getLedgerMaster().getCurrentLedgerIndex()); - auto const nodePubKey = toBase58( - TokenType::TOKEN_NODE_PUBLIC, - it->getSignerPublic()); - auto const signTime = - it->getSignTime().time_since_epoch().count(); - - soci::blob rawData(*db); - rawData.append(reinterpret_cast( - s.peekData().data()), s.peekData().size()); - assert(rawData.get_len() == s.peekData().size()); - - *db << - insVal, - soci::use(initialSeq), - soci::use(ledgerSeq), - soci::use(ledgerHash), - soci::use(nodePubKey), - soci::use(signTime), - soci::use(rawData); - } - - tr.commit (); - } - } - } - - mWriting = false; - } - - void sweep () override - { - ScopedLockType sl (mLock); - mValidations.sweep (); - } -}; - -std::unique_ptr make_Validations (Application& app) -{ - return std::make_unique (app); -} - -} // ripple diff --git a/src/ripple/app/misc/Validations.h b/src/ripple/app/misc/Validations.h deleted file mode 100644 index af6285d310..0000000000 --- a/src/ripple/app/misc/Validations.h +++ /dev/null @@ -1,85 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2013 Ripple Labs Inc. - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ -//============================================================================== - -#ifndef RIPPLE_APP_MISC_VALIDATIONS_H_INCLUDED -#define RIPPLE_APP_MISC_VALIDATIONS_H_INCLUDED - -#include -#include -#include -#include -#include - -namespace ripple { - -// VFALCO TODO rename and move these type aliases into the Validations interface - -// nodes validating and highest node ID validating -using ValidationSet = hash_map; - -using ValidationCounter = std::pair; -using LedgerToValidationCounter = hash_map; - -class Validations -{ -public: - virtual ~Validations() = default; - - virtual bool addValidation (STValidation::ref, std::string const& source) = 0; - - virtual bool current (STValidation::ref) = 0; - - virtual ValidationSet getValidations (uint256 const& ledger) = 0; - - virtual std::size_t getTrustedValidationCount (uint256 const& ledger) = 0; - - /** Returns fees reported by trusted validators in the given ledger. */ - virtual - std::vector - fees (uint256 const& ledger, std::uint64_t base) = 0; - - virtual int getNodesAfter (uint256 const& ledger) = 0; - virtual int getLoadRatio (bool overLoaded) = 0; - - virtual hash_set getCurrentPublicKeys () = 0; - - // VFALCO TODO make a type alias for this ugly return value! - virtual LedgerToValidationCounter getCurrentValidations ( - uint256 currentLedger, uint256 previousLedger, - LedgerIndex cutoffBefore) = 0; - - /** Return the times of all validations for a particular ledger hash. */ - virtual std::vector getValidationTimes ( - uint256 const& ledger) = 0; - - virtual std::list - getCurrentTrustedValidations () = 0; - - virtual void flush () = 0; - - virtual void sweep () = 0; -}; - -extern -std::unique_ptr -make_Validations(Application& app); - -} // ripple - -#endif diff --git a/src/ripple/app/misc/ValidatorKeys.h b/src/ripple/app/misc/ValidatorKeys.h new file mode 100644 index 0000000000..702f502c9f --- /dev/null +++ b/src/ripple/app/misc/ValidatorKeys.h @@ -0,0 +1,54 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012-2017 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_APP_MISC_VALIDATOR_KEYS_H_INCLUDED +#define RIPPLE_APP_MISC_VALIDATOR_KEYS_H_INCLUDED + +#include +#include +#include +#include + +namespace ripple { + +class Config; + +/** Validator keys and manifest as set in configuration file. Values will be + empty if not configured as a validator or not configured with a manifest. +*/ +class ValidatorKeys +{ +public: + PublicKey publicKey; + SecretKey secretKey; + std::string manifest; + ValidatorKeys(Config const& config, beast::Journal j); + + bool configInvalid() const + { + return configInvalid_; + } + +private: + bool configInvalid_ = false; //< Set to true if config was invalid +}; + +} // namespace ripple + +#endif diff --git a/src/ripple/app/misc/ValidatorList.h b/src/ripple/app/misc/ValidatorList.h index 4b6012f2eb..0f1a206855 100644 --- a/src/ripple/app/misc/ValidatorList.h +++ b/src/ripple/app/misc/ValidatorList.h @@ -125,6 +125,15 @@ class ValidatorList PublicKey localPubKey_; + // The minimum number of listed validators required to allow removing + // non-communicative validators from the trusted set. In other words, if the + // number of listed validators is less, then use all of them in the + // trusted set. + std::size_t const MINIMUM_RESIZEABLE_UNL {25}; + // The maximum size of a trusted set for which greater than Byzantine fault + // tolerance isn't needed. + std::size_t const BYZANTINE_THRESHOLD {32}; + public: ValidatorList ( ManifestCache& validatorManifests, @@ -308,9 +317,6 @@ public: for_each_listed ( std::function func) const; - static std::size_t - calculateQuorum (std::size_t nTrustedKeys); - private: /** Check response for trusted valid published list @@ -341,6 +347,15 @@ private: bool removePublisherList (PublicKey const& publisherKey); + /** Return safe minimum quorum for listed validator set + + @param nListedKeys Number of list validator keys + + @param unListedLocal Whether the local node is an unlisted validator + */ + static std::size_t + calculateMinimumQuorum ( + std::size_t nListedKeys, bool unlistedLocal=false); }; //------------------------------------------------------------------------------ @@ -389,25 +404,23 @@ ValidatorList::onConsensusStart ( std::pair( std::numeric_limits::max(), localPubKey_)); } - // If no validations are being received, use all validators. - // Otherwise, do not use validators whose validations aren't being received - else if (seenValidators.empty() || - seenValidators.find (val->first) != seenValidators.end ()) + // If the total number of validators is too small, or + // no validations are being received, use all validators. + // Otherwise, do not use validators whose validations aren't + // being received. + else if (keyListings_.size() < MINIMUM_RESIZEABLE_UNL || + seenValidators.empty() || + seenValidators.find (val->first) != seenValidators.end ()) { rankedKeys.insert ( std::pair(val->second, val->first)); } } - // This quorum guarantees sufficient overlap with the trusted sets of other - // nodes using the same set of published lists. - std::size_t quorum = keyListings_.size()/2 + 1; - - // Increment the quorum to prevent two unlisted validators using the same - // even number of listed validators from forking. - if (localPubKey_.size() && ! localKeyListed && - rankedKeys.size () > 1 && keyListings_.size () % 2 != 0) - ++quorum; + // This minimum quorum guarantees safe overlap with the trusted sets of + // other nodes using the same set of published lists. + std::size_t quorum = calculateMinimumQuorum (keyListings_.size(), + localPubKey_.size() && !localKeyListed); JLOG (j_.debug()) << rankedKeys.size() << " of " << keyListings_.size() << @@ -415,25 +428,32 @@ ValidatorList::onConsensusStart ( auto size = rankedKeys.size(); - // Use all eligible keys if there is less than 10 listed validators or - // only one trusted list - if (size < 10 || publisherLists_.size() == 1) + // Require 80% quorum if there are lots of validators. + if (rankedKeys.size() > BYZANTINE_THRESHOLD) { - // Try to raise the quorum toward or above 80% of the trusted set - std::size_t const targetQuorum = ValidatorList::calculateQuorum (size); - if (targetQuorum > quorum) - quorum = targetQuorum; - } - else - { - // reduce the trusted set size so that the quorum represents - // at least 80% - size = quorum * 1.25; + // Use all eligible keys if there is only one trusted list + if (publisherLists_.size() == 1 || + keyListings_.size() < MINIMUM_RESIZEABLE_UNL) + { + // Try to raise the quorum to at least 80% of the trusted set + quorum = std::max(quorum, size - size / 5); + } + else + { + // Reduce the trusted set size so that the quorum represents + // at least 80% + size = quorum * 1.25; + } } - if (minimumQuorum_ && (seenValidators.empty() || - rankedKeys.size() < quorum)) + if (minimumQuorum_ && seenValidators.size() < quorum) + { quorum = *minimumQuorum_; + JLOG (j_.warn()) + << "Using unsafe quorum of " + << quorum_ + << " as specified in the command line"; + } // Do not use achievable quorum until lists from all configured // publishers are available diff --git a/src/ripple/app/misc/detail/WorkBase.h b/src/ripple/app/misc/detail/WorkBase.h index 49cfcd45a7..1c59ab664e 100644 --- a/src/ripple/app/misc/detail/WorkBase.h +++ b/src/ripple/app/misc/detail/WorkBase.h @@ -22,8 +22,7 @@ #include #include -#include -#include +#include #include #include #include @@ -61,7 +60,7 @@ protected: socket_type socket_; request_type req_; response_type res_; - beast::streambuf read_buf_; + beast::multi_buffer read_buf_; public: WorkBase( @@ -132,8 +131,8 @@ WorkBase::run() resolver_.async_resolve( query_type{host_, port_}, strand_.wrap (std::bind(&WorkBase::onResolve, impl().shared_from_this(), - beast::asio::placeholders::error, - beast::asio::placeholders::iterator))); + std::placeholders::_1, + std::placeholders::_2))); } template @@ -171,24 +170,23 @@ WorkBase::onResolve(error_code const& ec, resolver_type::iterator it) socket_.async_connect(*it, strand_.wrap (std::bind(&Impl::onConnect, impl().shared_from_this(), - beast::asio::placeholders::error))); + std::placeholders::_1))); } template void WorkBase::onStart() { - req_.method = "GET"; - req_.url = path_.empty() ? "/" : path_; + req_.method(beast::http::verb::get); + req_.target(path_.empty() ? "/" : path_); req_.version = 11; - req_.fields.replace ( + req_.set ( "Host", host_ + ":" + port_); - req_.fields.replace ("User-Agent", BuildInfo::getFullVersionString()); - beast::http::prepare (req_); - + req_.set ("User-Agent", BuildInfo::getFullVersionString()); + req_.prepare_payload(); beast::http::async_write(impl().stream(), req_, strand_.wrap (std::bind (&WorkBase::onRequest, - impl().shared_from_this(), beast::asio::placeholders::error))); + impl().shared_from_this(), std::placeholders::_1))); } template @@ -200,7 +198,7 @@ WorkBase::onRequest(error_code const& ec) beast::http::async_read (impl().stream(), read_buf_, res_, strand_.wrap (std::bind (&WorkBase::onResponse, - impl().shared_from_this(), beast::asio::placeholders::error))); + impl().shared_from_this(), std::placeholders::_1))); } template diff --git a/src/ripple/app/misc/impl/AmendmentTable.cpp b/src/ripple/app/misc/impl/AmendmentTable.cpp index 10820e23ba..bb6d3ba1d0 100644 --- a/src/ripple/app/misc/impl/AmendmentTable.cpp +++ b/src/ripple/app/misc/impl/AmendmentTable.cpp @@ -20,7 +20,7 @@ #include #include #include -#include +#include #include #include #include @@ -157,6 +157,9 @@ protected: // we haven't participated in one yet. std::unique_ptr lastVote_; + // True if an unsupported amendment is enabled + bool unsupportedEnabled_; + beast::Journal j_; // Finds or creates state @@ -187,6 +190,8 @@ public: bool isEnabled (uint256 const& amendment) override; bool isSupported (uint256 const& amendment) override; + bool hasUnsupportedEnabled () override; + Json::Value getJson (int) override; Json::Value getJson (uint256 const&) override; @@ -207,7 +212,7 @@ public: NetClock::time_point closeTime, std::set const& enabledAmendments, majorityAmendments_t const& majorityAmendments, - ValidationSet const& validations) override; + std::vector const& validations) override; }; //------------------------------------------------------------------------------ @@ -222,6 +227,7 @@ AmendmentTableImpl::AmendmentTableImpl ( : lastUpdateSeq_ (0) , majorityTime_ (majorityTime) , majorityFraction_ (majorityFraction) + , unsupportedEnabled_ (false) , j_ (journal) { assert (majorityFraction_ != 0); @@ -340,6 +346,14 @@ AmendmentTableImpl::enable (uint256 const& amendment) return false; s->enabled = true; + + if (! s->supported) + { + JLOG (j_.error()) << + "Unsupported amendment " << amendment << " activated."; + unsupportedEnabled_ = true; + } + return true; } @@ -372,6 +386,13 @@ AmendmentTableImpl::isSupported (uint256 const& amendment) return s && s->supported; } +bool +AmendmentTableImpl::hasUnsupportedEnabled () +{ + std::lock_guard sl (mutex_); + return unsupportedEnabled_; +} + std::vector AmendmentTableImpl::doValidation ( std::set const& enabled) @@ -411,7 +432,7 @@ AmendmentTableImpl::doVoting ( NetClock::time_point closeTime, std::set const& enabledAmendments, majorityAmendments_t const& majorityAmendments, - ValidationSet const& valSet) + std::vector const& valSet) { JLOG (j_.trace()) << "voting at " << closeTime.time_since_epoch().count() << @@ -422,16 +443,16 @@ AmendmentTableImpl::doVoting ( auto vote = std::make_unique (); // process validations for ledger before flag ledger - for (auto const& entry : valSet) + for (auto const& val : valSet) { - if (entry.second->isTrusted ()) + if (val->isTrusted ()) { std::set ballot; - if (entry.second->isFieldPresent (sfAmendments)) + if (val->isFieldPresent (sfAmendments)) { auto const choices = - entry.second->getFieldV256 (sfAmendments); + val->getFieldV256 (sfAmendments); ballot.insert (choices.begin (), choices.end ()); } @@ -523,10 +544,8 @@ AmendmentTableImpl::doValidatedLedger ( LedgerIndex ledgerSeq, std::set const& enabled) { - std::lock_guard sl (mutex_); - - for (auto& e : amendmentMap_) - e.second.enabled = (enabled.count (e.first) != 0); + for (auto& e : enabled) + enable(e); } void diff --git a/src/ripple/app/misc/impl/TxQ.cpp b/src/ripple/app/misc/impl/TxQ.cpp index 39900e6284..14d6766e90 100644 --- a/src/ripple/app/misc/impl/TxQ.cpp +++ b/src/ripple/app/misc/impl/TxQ.cpp @@ -1054,7 +1054,7 @@ TxQ::apply(Application& app, OpenView& view, JLOG(j_.warn()) << "Removing last item of account " << lastRIter->account << - "from queue with average fee of" << + " from queue with average fee of " << endEffectiveFeeLevel << " in favor of " << transactionID << " with fee of " << feeLevelPaid; diff --git a/src/ripple/app/misc/impl/ValidatorKeys.cpp b/src/ripple/app/misc/impl/ValidatorKeys.cpp new file mode 100644 index 0000000000..c658269f6a --- /dev/null +++ b/src/ripple/app/misc/impl/ValidatorKeys.cpp @@ -0,0 +1,73 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include + +#include +#include +#include +#include + +namespace ripple { +ValidatorKeys::ValidatorKeys(Config const& config, beast::Journal j) +{ + if (config.exists(SECTION_VALIDATOR_TOKEN) && + config.exists(SECTION_VALIDATION_SEED)) + { + configInvalid_ = true; + JLOG(j.fatal()) << "Cannot specify both [" SECTION_VALIDATION_SEED + "] and [" SECTION_VALIDATOR_TOKEN "]"; + return; + } + + if (config.exists(SECTION_VALIDATOR_TOKEN)) + { + if (auto const token = ValidatorToken::make_ValidatorToken( + config.section(SECTION_VALIDATOR_TOKEN).lines())) + { + secretKey = token->validationSecret; + publicKey = derivePublicKey(KeyType::secp256k1, secretKey); + manifest = std::move(token->manifest); + } + else + { + configInvalid_ = true; + JLOG(j.fatal()) + << "Invalid token specified in [" SECTION_VALIDATOR_TOKEN "]"; + } + } + else if (config.exists(SECTION_VALIDATION_SEED)) + { + auto const seed = parseBase58( + config.section(SECTION_VALIDATION_SEED).lines().front()); + if (!seed) + { + configInvalid_ = true; + JLOG(j.fatal()) << + "Invalid seed specified in [" SECTION_VALIDATION_SEED "]"; + } + else + { + secretKey = generateSecretKey(KeyType::secp256k1, *seed); + publicKey = derivePublicKey(KeyType::secp256k1, secretKey); + } + } +} +} // namespace ripple diff --git a/src/ripple/app/misc/impl/ValidatorList.cpp b/src/ripple/app/misc/impl/ValidatorList.cpp index 6818214875..7a71c6bfbf 100644 --- a/src/ripple/app/misc/impl/ValidatorList.cpp +++ b/src/ripple/app/misc/impl/ValidatorList.cpp @@ -36,7 +36,7 @@ ValidatorList::ValidatorList ( , publisherManifests_ (publisherManifests) , timeKeeper_ (timeKeeper) , j_ (j) - , quorum_ (minimumQuorum ? *minimumQuorum : 1) // Genesis ledger quorum + , quorum_ (minimumQuorum.value_or(1)) // Genesis ledger quorum , minimumQuorum_ (minimumQuorum) { } @@ -409,15 +409,33 @@ ValidatorList::for_each_listed ( } std::size_t -ValidatorList::calculateQuorum (std::size_t nTrustedKeys) +ValidatorList::calculateMinimumQuorum ( + std::size_t nListedKeys, bool unlistedLocal) { - // Use 80% for large values of n, but have special cases for small numbers. - constexpr std::array quorum{{ 0, 1, 2, 2, 3, 3, 4, 5, 6, 7 }}; + // Only require 51% quorum for small number of validators to facilitate + // bootstrapping a network. + if (nListedKeys <= 5) + return nListedKeys/2 + 1; - if (nTrustedKeys < quorum.size()) - return quorum[nTrustedKeys]; + // The number of listed validators is increased to preserve the safety + // guarantee for two unlisted validators using the same set of listed + // validators. + if (unlistedLocal) + ++nListedKeys; - return nTrustedKeys - nTrustedKeys / 5; + // Guarantee safety with up to 1/3 listed validators being malicious. + // This prioritizes safety (Byzantine fault tolerance) over liveness. + // It takes at least as many malicious nodes to split/fork the network as + // to stall the network. + // At 67%, the overlap of two quorums is 34% + // 67 + 67 - 100 = 34 + // So under certain conditions, 34% of validators could vote for two + // different ledgers and split the network. + // Similarly 34% could prevent quorum from being met (by not voting) and + // stall the network. + // If/when the quorum is subsequently raised to/towards 80%, it becomes + // harder to split the network (more safe) and easier to stall it (less live). + return nListedKeys * 2/3 + 1; } } // ripple diff --git a/src/ripple/app/misc/impl/ValidatorSite.cpp b/src/ripple/app/misc/impl/ValidatorSite.cpp index 90cd60655d..bee0d5d4bd 100644 --- a/src/ripple/app/misc/impl/ValidatorSite.cpp +++ b/src/ripple/app/misc/impl/ValidatorSite.cpp @@ -144,7 +144,7 @@ ValidatorSite::setTimer () timer_.expires_at (next->nextRefresh); timer_.async_wait (std::bind (&ValidatorSite::onTimer, this, std::distance (sites_.begin (), next), - beast::asio::placeholders::error)); + std::placeholders::_1)); } } @@ -205,12 +205,12 @@ ValidatorSite::onSiteFetch( detail::response_type&& res, std::size_t siteIdx) { - if (! ec && res.status != 200) + if (! ec && res.result() != beast::http::status::ok) { std::lock_guard lock{sites_mutex_}; JLOG (j_.warn()) << "Request for validator list at " << - sites_[siteIdx].uri << " returned " << res.status; + sites_[siteIdx].uri << " returned " << res.result_int(); } else if (! ec) { diff --git a/src/ripple/app/paths/PathRequest.h b/src/ripple/app/paths/PathRequest.h index 4d2fa1ecf7..f5d2254db2 100644 --- a/src/ripple/app/paths/PathRequest.h +++ b/src/ripple/app/paths/PathRequest.h @@ -70,7 +70,7 @@ public: beast::Journal journal); // ripple_path_find semantics - // Completion function is called + // Completion function is called after path update is complete PathRequest ( Application& app, std::function const& completion, @@ -83,6 +83,8 @@ public: bool isNew (); bool needsUpdate (bool newOnly, LedgerIndex index); + + // Called when the PathRequest update is complete. void updateComplete (); std::pair doCreate ( diff --git a/src/ripple/app/paths/PathRequests.cpp b/src/ripple/app/paths/PathRequests.cpp index 69d76ceee9..0c71c48b46 100644 --- a/src/ripple/app/paths/PathRequests.cpp +++ b/src/ripple/app/paths/PathRequests.cpp @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include #include #include @@ -246,7 +248,12 @@ PathRequests::makeLegacyPathRequest( else { insertPathRequest (req); - app_.getLedgerMaster().newPathRequest(); + if (! app_.getLedgerMaster().newPathRequest()) + { + // The newPathRequest failed. Tell the caller. + result.second = rpcError (rpcTOO_BUSY); + req.reset(); + } } return std::move (result.second); diff --git a/src/ripple/app/paths/PathRequests.h b/src/ripple/app/paths/PathRequests.h index c8359566fb..fb5cc614e0 100644 --- a/src/ripple/app/paths/PathRequests.h +++ b/src/ripple/app/paths/PathRequests.h @@ -33,6 +33,7 @@ namespace ripple { class PathRequests { public: + /** A collection of all PathRequest instances. */ PathRequests (Application& app, beast::Journal journal, beast::insight::Collector::ptr const& collector) : app_ (app) @@ -43,6 +44,11 @@ public: mFull = collector->make_event ("pathfind_full"); } + /** Update all of the contained PathRequest instances. + + @param ledger Ledger we are pathfinding in. + @param shouldCancel Invocable that returns whether to cancel. + */ void updateAll (std::shared_ptr const& ledger, Job::CancelCallback shouldCancel); diff --git a/src/ripple/app/paths/RippleCalc.cpp b/src/ripple/app/paths/RippleCalc.cpp index 41e1f6197c..45e6a2ea13 100644 --- a/src/ripple/app/paths/RippleCalc.cpp +++ b/src/ripple/app/paths/RippleCalc.cpp @@ -149,7 +149,12 @@ RippleCalc::Output RippleCalc::rippleCalculate ( { JLOG (j.error()) << "Exception from flow: " << e.what (); if (!useFlowV1Output) - Rethrow(); + { + // return a tec so the tx is stored + path::RippleCalc::Output exceptResult; + exceptResult.setResult(tecINTERNAL); + return exceptResult; + } } } diff --git a/src/ripple/app/paths/impl/BookStep.cpp b/src/ripple/app/paths/impl/BookStep.cpp index 2eacadb6b7..6392ac44f1 100644 --- a/src/ripple/app/paths/impl/BookStep.cpp +++ b/src/ripple/app/paths/impl/BookStep.cpp @@ -523,8 +523,10 @@ BookStep::forEachOffer ( continue; // Make sure offer owner has authorization to own IOUs from issuer. - // An account can always own their own IOUs. - if (flowCross && (offer.owner() != offer.issueIn().account)) + // An account can always own XRP or their own IOUs. + if (flowCross && + (!isXRP (offer.issueIn().currency)) && + (offer.owner() != offer.issueIn().account)) { auto const& issuerID = offer.issueIn().account; auto const issuer = afView.read (keylet::account (issuerID)); @@ -533,10 +535,10 @@ BookStep::forEachOffer ( // Issuer requires authorization. See if offer owner has that. auto const& ownerID = offer.owner(); auto const authFlag = - ownerID > issuerID ? lsfHighAuth : lsfLowAuth; + issuerID > ownerID ? lsfHighAuth : lsfLowAuth; auto const line = afView.read (keylet::line ( - ownerID, issuerID, offer.issueOut().currency)); + ownerID, issuerID, offer.issueIn().currency)); if (!line || (((*line)[sfFlags] & authFlag) == 0)) { diff --git a/src/ripple/app/paths/impl/FlowDebugInfo.h b/src/ripple/app/paths/impl/FlowDebugInfo.h index 62d4841906..f0334d514e 100644 --- a/src/ripple/app/paths/impl/FlowDebugInfo.h +++ b/src/ripple/app/paths/impl/FlowDebugInfo.h @@ -229,21 +229,21 @@ struct FlowDebugInfo } ostr << ']'; }; - auto writeXrpAmtList = [&ostr, &write_list]( + auto writeXrpAmtList = [&write_list]( std::vector const& amts, char delim=';') { auto get_val = [](EitherAmount const& a) -> std::string { return ripple::to_string (a.xrp); }; write_list (amts, get_val, delim); }; - auto writeIouAmtList = [&ostr, &write_list]( + auto writeIouAmtList = [&write_list]( std::vector const& amts, char delim=';') { auto get_val = [](EitherAmount const& a) -> std::string { return ripple::to_string (a.iou); }; write_list (amts, get_val, delim); }; - auto writeIntList = [&ostr, &write_list]( + auto writeIntList = [&write_list]( std::vector const& vals, char delim=';') { auto get_val = []( size_t const& v) -> size_t const& { return v; }; diff --git a/src/ripple/app/tx/impl/ApplyContext.cpp b/src/ripple/app/tx/impl/ApplyContext.cpp index e0b51d41ad..34ff48ebe3 100644 --- a/src/ripple/app/tx/impl/ApplyContext.cpp +++ b/src/ripple/app/tx/impl/ApplyContext.cpp @@ -78,38 +78,49 @@ ApplyContext::checkInvariantsHelper(TER terResult, std::index_sequence) if (view_->rules().enabled(featureEnforceInvariants)) { auto checkers = getInvariantChecks(); - - // call each check's per-entry method - visit ( - [&checkers]( - uint256 const& index, - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) - { - // Sean Parent for_each_argument trick - (void)std::array{ - {((std::get(checkers). + try + { + // call each check's per-entry method + visit ( + [&checkers]( + uint256 const& index, + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) + { + // Sean Parent for_each_argument trick + (void)std::array{ + {((std::get(checkers). visitEntry(index, isDelete, before, after)), 0)...} - }; - }); + }; + }); - // Sean Parent for_each_argument trick - // (a fold expression with `&&` would be really nice here when we move - // to C++-17) - std::array finalizers {{ - std::get(checkers).finalize(tx, terResult, journal)...}}; + // Sean Parent for_each_argument trick (a fold expression with `&&` + // would be really nice here when we move to C++-17) + std::array finalizers {{ + std::get(checkers).finalize(tx, terResult, journal)...}}; - // call each check's finalizer to see that it passes - if (! std::all_of( finalizers.cbegin(), finalizers.cend(), - [](auto const& b) { return b; })) + // call each check's finalizer to see that it passes + if (! std::all_of( finalizers.cbegin(), finalizers.cend(), + [](auto const& b) { return b; })) + { + terResult = (terResult == tecINVARIANT_FAILED) ? + tefINVARIANT_FAILED : + tecINVARIANT_FAILED ; + JLOG(journal.fatal()) << + "Transaction has failed one or more invariants: " << + to_string(tx.getJson (0)); + } + } + catch(std::exception const& ex) { terResult = (terResult == tecINVARIANT_FAILED) ? tefINVARIANT_FAILED : tecINVARIANT_FAILED ; JLOG(journal.fatal()) << - "Transaction has failed one or more invariants: " << - to_string(tx.getJson (0)); + "Transaction caused an exception in an invariant" << + ", ex: " << ex.what() << + ", tx: " << to_string(tx.getJson (0)); } } diff --git a/src/ripple/app/tx/impl/CancelTicket.cpp b/src/ripple/app/tx/impl/CancelTicket.cpp index d4c0926618..9f8bb0a2dc 100644 --- a/src/ripple/app/tx/impl/CancelTicket.cpp +++ b/src/ripple/app/tx/impl/CancelTicket.cpp @@ -79,7 +79,7 @@ CancelTicket::doApply () auto viewJ = ctx_.app.journal ("View"); TER const result = dirDelete (ctx_.view (), false, hint, - getOwnerDirIndex (ticket_owner), ticketId, false, (hint == 0), viewJ); + keylet::ownerDir (ticket_owner), ticketId, false, (hint == 0), viewJ); adjustOwnerCount(view(), view().peek( keylet::account(ticket_owner)), -1, viewJ); diff --git a/src/ripple/app/tx/impl/CreateOffer.cpp b/src/ripple/app/tx/impl/CreateOffer.cpp index 7fac4b5b63..320e03f0da 100644 --- a/src/ripple/app/tx/impl/CreateOffer.cpp +++ b/src/ripple/app/tx/impl/CreateOffer.cpp @@ -234,6 +234,13 @@ CreateOffer::checkAcceptAsset(ReadView const& view, : tecNO_ISSUER; } + // This code is attached to the FlowCross amendment as a matter of + // convenience. The change is not significant enough to deserve its + // own amendment. + if (view.rules().enabled(featureFlowCross) && (issue.account == id)) + // An account can always accept its own issuance. + return tesSUCCESS; + if ((*issuerAccount)[sfFlags] & lsfRequireAuth) { auto const trustLine = view.read( @@ -1284,74 +1291,74 @@ CreateOffer::applyGuts (Sandbox& sb, Sandbox& sbCancel) // We need to place the remainder of the offer into its order book. auto const offer_index = getOfferIndex (account_, uSequence); - std::uint64_t uOwnerNode; - // Add offer to owner's directory. - std::tie(result, std::ignore) = dirAdd(sb, uOwnerNode, - keylet::ownerDir (account_), offer_index, - describeOwnerDir (account_), viewJ); + auto const ownerNode = dirAdd(sb, keylet::ownerDir (account_), + offer_index, false, describeOwnerDir (account_), viewJ); - if (result == tesSUCCESS) - { - // Update owner count. - adjustOwnerCount(sb, sleCreator, 1, viewJ); - - JLOG (j_.trace()) << - "adding to book: " << to_string (saTakerPays.issue ()) << - " : " << to_string (saTakerGets.issue ()); - - Book const book { saTakerPays.issue(), saTakerGets.issue() }; - std::uint64_t uBookNode; - bool isNewBook; - - // Add offer to order book, using the original rate - // before any crossing occured. - auto dir = keylet::quality (keylet::book (book), uRate); - - std::tie(result, isNewBook) = dirAdd (sb, uBookNode, - dir, offer_index, [&](SLE::ref sle) - { - sle->setFieldH160 (sfTakerPaysCurrency, - saTakerPays.issue().currency); - sle->setFieldH160 (sfTakerPaysIssuer, - saTakerPays.issue().account); - sle->setFieldH160 (sfTakerGetsCurrency, - saTakerGets.issue().currency); - sle->setFieldH160 (sfTakerGetsIssuer, - saTakerGets.issue().account); - sle->setFieldU64 (sfExchangeRate, uRate); - }, viewJ); - - if (result == tesSUCCESS) - { - auto sleOffer = std::make_shared(ltOFFER, offer_index); - sleOffer->setAccountID (sfAccount, account_); - sleOffer->setFieldU32 (sfSequence, uSequence); - sleOffer->setFieldH256 (sfBookDirectory, dir.key); - sleOffer->setFieldAmount (sfTakerPays, saTakerPays); - sleOffer->setFieldAmount (sfTakerGets, saTakerGets); - sleOffer->setFieldU64 (sfOwnerNode, uOwnerNode); - sleOffer->setFieldU64 (sfBookNode, uBookNode); - if (expiration) - sleOffer->setFieldU32 (sfExpiration, *expiration); - if (bPassive) - sleOffer->setFlag (lsfPassive); - if (bSell) - sleOffer->setFlag (lsfSell); - sb.insert(sleOffer); - - if (isNewBook) - ctx_.app.getOrderBookDB().addOrderBook(book); - } - } - - if (result != tesSUCCESS) + if (!ownerNode) { JLOG (j_.debug()) << - "final result: " << transToken (result); + "final result: failed to add offer to owner's directory"; + return { tecDIR_FULL, true }; } - return { result, true }; + // Update owner count. + adjustOwnerCount(sb, sleCreator, 1, viewJ); + + JLOG (j_.trace()) << + "adding to book: " << to_string (saTakerPays.issue ()) << + " : " << to_string (saTakerGets.issue ()); + + Book const book { saTakerPays.issue(), saTakerGets.issue() }; + + // Add offer to order book, using the original rate + // before any crossing occured. + auto dir = keylet::quality (keylet::book (book), uRate); + bool const bookExisted = static_cast(sb.peek (dir)); + + auto const bookNode = dirAdd (sb, dir, offer_index, true, + [&](SLE::ref sle) + { + sle->setFieldH160 (sfTakerPaysCurrency, + saTakerPays.issue().currency); + sle->setFieldH160 (sfTakerPaysIssuer, + saTakerPays.issue().account); + sle->setFieldH160 (sfTakerGetsCurrency, + saTakerGets.issue().currency); + sle->setFieldH160 (sfTakerGetsIssuer, + saTakerGets.issue().account); + sle->setFieldU64 (sfExchangeRate, uRate); + }, viewJ); + + if (!bookNode) + { + JLOG (j_.debug()) << + "final result: failed to add offer to book"; + return { tecDIR_FULL, true }; + } + + auto sleOffer = std::make_shared(ltOFFER, offer_index); + sleOffer->setAccountID (sfAccount, account_); + sleOffer->setFieldU32 (sfSequence, uSequence); + sleOffer->setFieldH256 (sfBookDirectory, dir.key); + sleOffer->setFieldAmount (sfTakerPays, saTakerPays); + sleOffer->setFieldAmount (sfTakerGets, saTakerGets); + sleOffer->setFieldU64 (sfOwnerNode, *ownerNode); + sleOffer->setFieldU64 (sfBookNode, *bookNode); + if (expiration) + sleOffer->setFieldU32 (sfExpiration, *expiration); + if (bPassive) + sleOffer->setFlag (lsfPassive); + if (bSell) + sleOffer->setFlag (lsfSell); + sb.insert(sleOffer); + + if (!bookExisted) + ctx_.app.getOrderBookDB().addOrderBook(book); + + JLOG (j_.debug()) << "final result: success"; + + return { tesSUCCESS, true }; } TER diff --git a/src/ripple/app/tx/impl/CreateTicket.cpp b/src/ripple/app/tx/impl/CreateTicket.cpp index afcbacd286..2220899a7f 100644 --- a/src/ripple/app/tx/impl/CreateTicket.cpp +++ b/src/ripple/app/tx/impl/CreateTicket.cpp @@ -99,27 +99,24 @@ CreateTicket::doApply () sleTicket->setAccountID (sfTarget, target_account); } - std::uint64_t hint; - auto viewJ = ctx_.app.journal ("View"); - auto result = dirAdd(view(), hint, keylet::ownerDir (account_), - sleTicket->key(), describeOwnerDir (account_), viewJ); + auto const page = dirAdd(view(), keylet::ownerDir (account_), + sleTicket->key(), false, describeOwnerDir (account_), viewJ); JLOG(j_.trace()) << "Creating ticket " << to_string (sleTicket->key()) << - ": " << transHuman (result.first); + ": " << (page ? "success" : "failure"); - if (result.first == tesSUCCESS) - { - sleTicket->setFieldU64(sfOwnerNode, hint); + if (!page) + return tecDIR_FULL; - // If we succeeded, the new entry counts agains the - // creator's reserve. - adjustOwnerCount(view(), sle, 1, viewJ); - } + sleTicket->setFieldU64(sfOwnerNode, *page); - return result.first; + // If we succeeded, the new entry counts agains the + // creator's reserve. + adjustOwnerCount(view(), sle, 1, viewJ); + return tesSUCCESS; } } diff --git a/src/ripple/app/tx/impl/Escrow.cpp b/src/ripple/app/tx/impl/Escrow.cpp index 18bc1cc854..3b9abf0149 100644 --- a/src/ripple/app/tx/impl/Escrow.cpp +++ b/src/ripple/app/tx/impl/Escrow.cpp @@ -255,13 +255,11 @@ EscrowCreate::doApply() // Add escrow to owner directory { - uint64_t page; - auto result = dirAdd(ctx_.view(), page, - keylet::ownerDir(account), slep->key(), - describeOwnerDir(account), ctx_.app.journal ("View")); - if (! isTesSuccess(result.first)) - return result.first; - (*slep)[sfOwnerNode] = page; + auto page = dirAdd(ctx_.view(), keylet::ownerDir(account), slep->key(), + false, describeOwnerDir(account), ctx_.app.journal ("View")); + if (!page) + return tecDIR_FULL; + (*slep)[sfOwnerNode] = *page; } // Deduct owner's balance, increment owner count @@ -431,7 +429,7 @@ EscrowFinish::doApply() { auto const page = (*slep)[sfOwnerNode]; TER const ter = dirDelete(ctx_.view(), true, - page, keylet::ownerDir(account).key, + page, keylet::ownerDir(account), k.key, false, page == 0, ctx_.app.journal ("View")); if (! isTesSuccess(ter)) return ter; @@ -497,7 +495,7 @@ EscrowCancel::doApply() { auto const page = (*slep)[sfOwnerNode]; TER const ter = dirDelete(ctx_.view(), true, - page, keylet::ownerDir(account).key, + page, keylet::ownerDir(account), k.key, false, page == 0, ctx_.app.journal ("View")); if (! isTesSuccess(ter)) return ter; diff --git a/src/ripple/app/tx/impl/PayChan.cpp b/src/ripple/app/tx/impl/PayChan.cpp index 4aeb509dd9..1a14cbe653 100644 --- a/src/ripple/app/tx/impl/PayChan.cpp +++ b/src/ripple/app/tx/impl/PayChan.cpp @@ -134,7 +134,7 @@ closeChannel ( // Remove PayChan from owner directory { auto const page = (*slep)[sfOwnerNode]; - TER const ter = dirDelete (view, true, page, keylet::ownerDir (src).key, + TER const ter = dirDelete (view, true, page, keylet::ownerDir (src), key, false, page == 0, j); if (!isTesSuccess (ter)) return ter; @@ -239,13 +239,11 @@ PayChanCreate::doApply() // Add PayChan to owner directory { - uint64_t page; - auto result = dirAdd (ctx_.view (), page, keylet::ownerDir (account), - slep->key (), describeOwnerDir (account), - ctx_.app.journal ("View")); - if (!isTesSuccess (result.first)) - return result.first; - (*slep)[sfOwnerNode] = page; + auto page = dirAdd (ctx_.view(), keylet::ownerDir(account), slep->key(), + false, describeOwnerDir (account), ctx_.app.journal ("View")); + if (!page) + return tecDIR_FULL; + (*slep)[sfOwnerNode] = *page; } // Deduct owner's balance, increment owner count @@ -346,6 +344,9 @@ PayChanClaim::preflight (PreflightContext const& ctx) if (! ctx.rules.enabled(featurePayChan)) return temDISABLED; + + bool const noTecs = ctx.rules.enabled(fix1512); + auto const ret = preflight1 (ctx); if (!isTesSuccess (ret)) return ret; @@ -359,7 +360,12 @@ PayChanClaim::preflight (PreflightContext const& ctx) return temBAD_AMOUNT; if (bal && amt && *bal > *amt) - return tecNO_PERMISSION; + { + if (noTecs) + return temBAD_AMOUNT; + else + return tecNO_PERMISSION; + } auto const flags = ctx.tx.getFlags (); if ((flags & tfClose) && (flags & tfRenew)) @@ -378,7 +384,12 @@ PayChanClaim::preflight (PreflightContext const& ctx) auto const authAmt = amt ? amt->xrp() : reqBalance; if (reqBalance > authAmt) - return tecNO_PERMISSION; + { + if (noTecs) + return temBAD_AMOUNT; + else + return tecNO_PERMISSION; + } Keylet const k (ltPAYCHAN, ctx.tx[sfPayChannel]); if (!publicKeyType(ctx.tx[sfPublicKey])) diff --git a/src/ripple/app/tx/impl/SetAccount.cpp b/src/ripple/app/tx/impl/SetAccount.cpp index 5b87239108..83408704c9 100644 --- a/src/ripple/app/tx/impl/SetAccount.cpp +++ b/src/ripple/app/tx/impl/SetAccount.cpp @@ -119,7 +119,13 @@ SetAccount::preflight (PreflightContext const& ctx) if (uRate && (uRate < QUALITY_ONE)) { - JLOG(j.trace()) << "Malformed transaction: Bad transfer rate."; + JLOG(j.trace()) << "Malformed transaction: Transfer rate too small."; + return temBAD_TRANSFER_RATE; + } + + if (ctx.rules.enabled(fix1201) && (uRate > 2 * QUALITY_ONE)) + { + JLOG(j.trace()) << "Malformed transaction: Transfer rate too large."; return temBAD_TRANSFER_RATE; } } @@ -453,7 +459,7 @@ SetAccount::doApply () JLOG(j_.trace()) << "unset transfer rate"; sle->makeFieldAbsent (sfTransferRate); } - else if (uRate > QUALITY_ONE) + else { JLOG(j_.trace()) << "set transfer rate"; sle->setFieldU32 (sfTransferRate, uRate); diff --git a/src/ripple/app/tx/impl/SetSignerList.cpp b/src/ripple/app/tx/impl/SetSignerList.cpp index 522327fc03..4a03ec36e5 100644 --- a/src/ripple/app/tx/impl/SetSignerList.cpp +++ b/src/ripple/app/tx/impl/SetSignerList.cpp @@ -236,24 +236,21 @@ SetSignerList::replaceSignerList () auto viewJ = ctx_.app.journal ("View"); // Add the signer list to the account's directory. - std::uint64_t hint; - - auto result = dirAdd(ctx_.view (), hint, ownerDirKeylet, - signerListKeylet.key, describeOwnerDir (account_), viewJ); + auto page = dirAdd(ctx_.view (), ownerDirKeylet, + signerListKeylet.key, false, describeOwnerDir (account_), viewJ); JLOG(j_.trace()) << "Create signer list for account " << - toBase58(account_) << ": " << transHuman (result.first); + toBase58(account_) << ": " << (page ? "success" : "failure"); - if (result.first == tesSUCCESS) - { - signerList->setFieldU64 (sfOwnerNode, hint); + if (!page) + return tecDIR_FULL; - // If we succeeded, the new entry counts against the - // creator's reserve. - adjustOwnerCount(view(), sle, addedOwnerCount, viewJ); - } + signerList->setFieldU64 (sfOwnerNode, *page); - return result.first; + // If we succeeded, the new entry counts against the + // creator's reserve. + adjustOwnerCount(view(), sle, addedOwnerCount, viewJ); + return tesSUCCESS; } TER @@ -293,7 +290,7 @@ SetSignerList::removeSignersFromLedger (Keylet const& accountKeylet, auto viewJ = ctx_.app.journal ("View"); TER const result = dirDelete(ctx_.view(), false, hint, - ownerDirKeylet.key, signerListKeylet.key, false, (hint == 0), viewJ); + ownerDirKeylet, signerListKeylet.key, false, (hint == 0), viewJ); if (result == tesSUCCESS) adjustOwnerCount(view(), diff --git a/src/ripple/app/tx/impl/Transactor.cpp b/src/ripple/app/tx/impl/Transactor.cpp index 1cf50da679..f758b85384 100644 --- a/src/ripple/app/tx/impl/Transactor.cpp +++ b/src/ripple/app/tx/impl/Transactor.cpp @@ -31,6 +31,7 @@ #include #include #include +#include namespace ripple { @@ -557,12 +558,11 @@ void removeUnfundedOffers (ApplyView& view, std::vector const& offers, for (auto const& index : offers) { - auto const sleOffer = view.peek (keylet::offer (index)); - if (sleOffer) + if (auto const sleOffer = view.peek (keylet::offer (index))) { // offer is unfunded offerDelete (view, sleOffer, viewJ); - if (++removed == 1000) + if (++removed == unfundedOfferRemoveLimit) return; } } @@ -648,7 +648,7 @@ Transactor::operator()() bool didApply = isTesSuccess (terResult); auto fee = ctx_.tx.getFieldAmount(sfFee).xrp (); - if (ctx_.size() > 5200) + if (ctx_.size() > oversizeMetaDataCap) terResult = tecOVERSIZE; if ((terResult == tecOVERSIZE) || diff --git a/src/ripple/basics/BasicConfig.h b/src/ripple/basics/BasicConfig.h index 4789beac9e..75c987b991 100644 --- a/src/ripple/basics/BasicConfig.h +++ b/src/ripple/basics/BasicConfig.h @@ -22,7 +22,7 @@ #include #include -#include +#include #include #include #include @@ -41,7 +41,7 @@ using IniFileSections = std::map>; */ class Section : public beast::unit_test::detail::const_container < - std::map > + std::map > { private: std::string name_; @@ -173,7 +173,7 @@ public: class BasicConfig { private: - std::map map_; + std::map map_; public: /** Returns `true` if a section with the given name exists. */ diff --git a/src/ripple/basics/Log.h b/src/ripple/basics/Log.h index ea827e9d00..10debfa5de 100644 --- a/src/ripple/basics/Log.h +++ b/src/ripple/basics/Log.h @@ -21,7 +21,7 @@ #define RIPPLE_BASICS_LOG_H_INCLUDED #include -#include +#include #include #include #include @@ -148,7 +148,7 @@ private: std::mutex mutable mutex_; std::map , - beast::detail::ci_less> sinks_; + beast::iless> sinks_; beast::severities::Severity thresh_; File file_; bool silent_ = false; diff --git a/src/ripple/basics/RangeSet.h b/src/ripple/basics/RangeSet.h index fbbaf5677b..16b29bd182 100644 --- a/src/ripple/basics/RangeSet.h +++ b/src/ripple/basics/RangeSet.h @@ -20,72 +20,113 @@ #ifndef RIPPLE_BASICS_RANGESET_H_INCLUDED #define RIPPLE_BASICS_RANGESET_H_INCLUDED -#include -#include #include +#include +#include +#include -namespace ripple { - -/** A sparse set of integers. */ -// VFALCO TODO Replace with juce::SparseSet -class RangeSet +namespace ripple { -public: - static const std::uint32_t absent = static_cast (-1); -public: - RangeSet () { } +/** A closed interval over the domain T. - bool hasValue (std::uint32_t) const; + For an instance ClosedInterval c, this represents the closed interval + (c.first(), c.last()). A single element interval has c.first() == c.last(). - std::uint32_t getFirst () const; - std::uint32_t getNext (std::uint32_t) const; - std::uint32_t getLast () const; - std::uint32_t getPrev (std::uint32_t) const; + This is simply a type-alias for boost interval container library interval + set, so users should consult that documentation for available supporting + member and free functions. +*/ +template +using ClosedInterval = boost::icl::closed_interval; - // largest number not in the set that is less than the given number - std::uint32_t prevMissing (std::uint32_t) const; +/** Create a closed range interval - // Add an item to the set - void setValue (std::uint32_t); + Helper function to create a closed range interval without having to qualify + the template argument. +*/ +template +ClosedInterval +range(T low, T high) +{ + return ClosedInterval(low, high); +} - // Add the closed interval to the set - void setRange (std::uint32_t, std::uint32_t); +/** A set of closed intervals over the domain T. - void clearValue (std::uint32_t); + Represents a set of values of the domain T using the minimum number + of disjoint ClosedInterval. This is useful to represent ranges of + T where a few instances are missing, e.g. the set 1-5,8-9,11-14. - std::string toString () const; + This is simply a type-alias for boost interval container library interval + set, so users should consult that documentation for available supporting + member and free functions. +*/ +template +using RangeSet = boost::icl::interval_set>; - /** Returns the sum of the Lebesgue measures of all sub-ranges. */ - std::size_t - lebesgue_sum() const; - /** Check invariants of the data. +/** Convert a ClosedInterval to a styled string - This is for diagnostics, and does nothing in release builds. - */ - void checkInternalConsistency () const noexcept; + The styled string is + "c.first()-c.last()" if c.first() != c.last() + "c.first()" if c.first() == c.last() -private: - void simplify (); + @param ci The closed interval to convert + @return The style string +*/ +template +std::string to_string(ClosedInterval const & ci) +{ + if (ci.first() == ci.last()) + return std::to_string(ci.first()); + return std::to_string(ci.first()) + "-" + std::to_string(ci.last()); +} -private: - using Map = std::map ; +/** Convert the given RangeSet to a styled string. - using const_iterator = Map::const_iterator; - using const_reverse_iterator = Map::const_reverse_iterator; - using value_type = Map::value_type; - using iterator = Map::iterator; + The styled string represention is the set of disjoint intervals joined by + commas. The string "empty" is returned if the set is empty. - static bool contains (value_type const& it, std::uint32_t v) + @param rs The rangeset to convert + @return The styled string +*/ +template +std::string to_string(RangeSet const & rs) +{ + using ripple::to_string; + + if (rs.empty()) + return "empty"; + std::string res = ""; + for (auto const & interval : rs) { - return (it.first <= v) && (it.second >= v); + if (!res.empty()) + res += ","; + res += to_string(interval); } + return res; +} - // First is lowest value in range, last is highest value in range - Map mRanges; -}; - -} // ripple +/** Find the largest value not in the set that is less than a given value. + @param rs The set of interest + @param t The value that must be larger than the result + @param minVal (Default is 0) The smallest allowed value + @return The largest v such that minV <= v < t and !contains(rs, v) or + boost::none if no such v exists. +*/ +template +boost::optional +prevMissing(RangeSet const & rs, T t, T minVal = 0) +{ + if (rs.empty() || t == minVal) + return boost::none; + RangeSet tgt{ ClosedInterval{minVal, t - 1} }; + tgt -= rs; + if (tgt.empty()) + return boost::none; + return boost::icl::last(tgt); +} + } // namespace ripple #endif diff --git a/src/ripple/basics/impl/RangeSet.cpp b/src/ripple/basics/impl/RangeSet.cpp deleted file mode 100644 index ed13d5349e..0000000000 --- a/src/ripple/basics/impl/RangeSet.cpp +++ /dev/null @@ -1,270 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2013 Ripple Labs Inc. - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ -//============================================================================== - -#include -#include -#include -#include -#include -#include -#include - -namespace ripple { - -// VFALCO NOTE std::min and std::max not good enough? -// NOTE Why isn't this written as a template? -// TODO Replace this with std calls. -// -inline std::uint32_t min (std::uint32_t x, std::uint32_t y) -{ - return (x < y) ? x : y; -} -inline std::uint32_t max (std::uint32_t x, std::uint32_t y) -{ - return (x > y) ? x : y; -} - -bool RangeSet::hasValue (std::uint32_t v) const -{ - for (auto const& it : mRanges) - { - if (contains (it, v)) - return true; - } - return false; -} - -std::uint32_t RangeSet::getFirst () const -{ - const_iterator it = mRanges.begin (); - - if (it == mRanges.end ()) - return absent; - - return it->first; -} - -std::uint32_t RangeSet::getNext (std::uint32_t v) const -{ - for (auto const& it : mRanges) - { - if (it.first > v) - return it.first; - - if (contains (it, v + 1)) - return v + 1; - } - return absent; -} - -std::uint32_t RangeSet::getLast () const -{ - const_reverse_iterator it = mRanges.rbegin (); - - if (it == mRanges.rend ()) - return absent; - - return it->second; -} - -std::uint32_t RangeSet::getPrev (std::uint32_t v) const -{ - BOOST_REVERSE_FOREACH (const value_type & it, mRanges) - { - if (it.second < v) - return it.second; - - if (contains (it, v + 1)) - return v - 1; - } - return absent; -} - -// Return the largest number not in the set that is less than the given number -// -std::uint32_t RangeSet::prevMissing (std::uint32_t v) const -{ - std::uint32_t result = absent; - - if (v != 0) - { - checkInternalConsistency (); - - // Handle the case where the loop reaches the terminating condition - // - result = v - 1; - - for (const_reverse_iterator cur = mRanges.rbegin (); cur != mRanges.rend (); ++cur) - { - // See if v-1 is in the range - if (contains (*cur, result)) - { - result = cur->first - 1; - break; - } - } - } - - assert (result == absent || !hasValue (result)); - - return result; -} - -void RangeSet::setValue (std::uint32_t v) -{ - if (!hasValue (v)) - { - mRanges[v] = v; - - simplify (); - } -} - -void RangeSet::setRange (std::uint32_t minV, std::uint32_t maxV) -{ - while (hasValue (minV)) - { - ++minV; - - if (minV >= maxV) - return; - } - - mRanges[minV] = maxV; - - simplify (); -} - -void RangeSet::clearValue (std::uint32_t v) -{ - for (iterator it = mRanges.begin (); it != mRanges.end (); ++it) - { - if (contains (*it, v)) - { - if (it->first == v) - { - if (it->second == v) - { - mRanges.erase (it); - } - else - { - std::uint32_t oldEnd = it->second; - mRanges.erase(it); - mRanges[v + 1] = oldEnd; - } - } - else if (it->second == v) - { - -- (it->second); - } - else - { - std::uint32_t oldEnd = it->second; - it->second = v - 1; - mRanges[v + 1] = oldEnd; - } - - checkInternalConsistency(); - return; - } - } -} - -std::string RangeSet::toString () const -{ - std::string ret; - for (auto const& it : mRanges) - { - if (!ret.empty ()) - ret += ","; - - if (it.first == it.second) - ret += beast::lexicalCastThrow ((it.first)); - else - ret += beast::lexicalCastThrow (it.first) + "-" - + beast::lexicalCastThrow (it.second); - } - - if (ret.empty ()) - return "empty"; - - return ret; -} - -void RangeSet::simplify () -{ - iterator it = mRanges.begin (); - - while (1) - { - iterator nit = it; - - if (++nit == mRanges.end ()) - { - checkInternalConsistency(); - return; - } - - if (it->second >= (nit->first - 1)) - { - // ranges overlap - it->second = std::max(it->second, nit->second); - mRanges.erase (nit); - } - else - { - it = nit; - } - } -} - -std::size_t -RangeSet::lebesgue_sum() const -{ - std::size_t sum = mRanges.size(); - for (auto const& e : mRanges) - sum += e.second - e.first; - return sum; -} - -void RangeSet::checkInternalConsistency () const noexcept -{ -#ifndef NDEBUG - if (mRanges.size () > 1) - { - const_iterator const last = std::prev (mRanges.end ()); - - for (const_iterator cur = mRanges.begin (); cur != last; ++cur) - { - const_iterator const next = std::next (cur); - assert (cur->first <= cur->second); - assert (next->first <= next->second); - assert (cur->second + 1 < next->first); - } - } - else if (mRanges.size () == 1) - { - const_iterator const iter = mRanges.begin (); - assert (iter->first <= iter->second); - } -#endif -} - -} // ripple diff --git a/src/ripple/basics/impl/ResolverAsio.cpp b/src/ripple/basics/impl/ResolverAsio.cpp index da5fd9dde8..ffabbb95c8 100644 --- a/src/ripple/basics/impl/ResolverAsio.cpp +++ b/src/ripple/basics/impl/ResolverAsio.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include @@ -336,8 +335,8 @@ public: m_resolver.async_resolve (query, std::bind ( &ResolverAsioImpl::do_finish, this, name, - beast::asio::placeholders::error, handler, - beast::asio::placeholders::iterator, + std::placeholders::_1, handler, + std::placeholders::_2, CompletionCounter (this))); } diff --git a/src/ripple/basics/impl/make_SSLContext.cpp b/src/ripple/basics/impl/make_SSLContext.cpp index 397fdedbfd..0d3270df2f 100644 --- a/src/ripple/basics/impl/make_SSLContext.cpp +++ b/src/ripple/basics/impl/make_SSLContext.cpp @@ -35,7 +35,7 @@ namespace detail { // the rippled server-server use case, where we only need MITM // detection/prevention, we also have websocket and rpc scenarios // and want to ensure weak ciphers can't be used. -char const defaultCipherList[] = +std::string const defaultCipherList = "HIGH:MEDIUM:!aNULL:!MD5:!DSS:!3DES:!RC4:!EXPORT"; template @@ -68,6 +68,15 @@ struct custom_delete } }; +template <> +struct custom_delete +{ + void operator() (DH* dh) const + { + DH_free (dh); + } +}; + template using custom_delete_unique_ptr = std::unique_ptr >; @@ -113,7 +122,7 @@ static evp_pkey_ptr evp_pkey_new() return evp_pkey_ptr (evp_pkey); } -static void evp_pkey_assign_rsa (EVP_PKEY* evp_pkey, rsa_ptr&& rsa) +static void evp_pkey_assign_rsa (EVP_PKEY* evp_pkey, rsa_ptr rsa) { if (! EVP_PKEY_assign_RSA (evp_pkey, rsa.get())) LogicError ("EVP_PKEY_assign_RSA failed"); @@ -154,15 +163,15 @@ static void x509_sign (X509* x509, EVP_PKEY* evp_pkey) LogicError ("X509_sign failed"); } -static void ssl_ctx_use_certificate (SSL_CTX* const ctx, x509_ptr& cert) +static void ssl_ctx_use_certificate (SSL_CTX* const ctx, x509_ptr cert) { - if (SSL_CTX_use_certificate (ctx, cert.release()) <= 0) + if (SSL_CTX_use_certificate (ctx, cert.get()) <= 0) LogicError ("SSL_CTX_use_certificate failed"); } -static void ssl_ctx_use_privatekey (SSL_CTX* const ctx, evp_pkey_ptr& key) +static void ssl_ctx_use_privatekey (SSL_CTX* const ctx, evp_pkey_ptr key) { - if (SSL_CTX_use_PrivateKey (ctx, key.release()) <= 0) + if (SSL_CTX_use_PrivateKey (ctx, key.get()) <= 0) LogicError ("SSL_CTX_use_PrivateKey failed"); } @@ -220,11 +229,15 @@ static void info_handler (SSL const* ssl, int event, int) { +#if OPENSSL_VERSION_NUMBER < 0x10100000L if ((ssl->s3) && (event & SSL_CB_HANDSHAKE_START)) { if (disallowRenegotiation (ssl, SSL_in_before (ssl))) ssl->s3->flags |= SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS; } +#else + // empty, flag removed in OpenSSL 1.1 +#endif } #endif @@ -256,17 +269,17 @@ initAnonymous ( x509_sign (cert.get(), pkey.get()); SSL_CTX* const ctx = context.native_handle(); - ssl_ctx_use_certificate (ctx, cert); - ssl_ctx_use_privatekey (ctx, pkey); + ssl_ctx_use_certificate (ctx, std::move(cert)); + ssl_ctx_use_privatekey (ctx, std::move(pkey)); } static void initAuthenticated ( boost::asio::ssl::context& context, - std::string key_file, - std::string cert_file, - std::string chain_file) + std::string const& key_file, + std::string const& cert_file, + std::string const& chain_file) { SSL_CTX* const ssl = context.native_handle (); @@ -353,7 +366,7 @@ initAuthenticated ( } std::shared_ptr -get_context (std::string cipherList) +get_context (std::string const& cipherList) { auto c = std::make_shared ( boost::asio::ssl::context::sslv23); @@ -365,10 +378,9 @@ get_context (std::string cipherList) boost::asio::ssl::context::single_dh_use); { - if (cipherList.empty()) - cipherList = defaultCipherList; + auto const& l = !cipherList.empty() ? cipherList : defaultCipherList; auto result = SSL_CTX_set_cipher_list ( - c->native_handle (), cipherList.c_str()); + c->native_handle (), l.c_str()); if (result != 1) LogicError ("SSL_CTX_set_cipher_list failed"); } @@ -406,11 +418,11 @@ get_context (std::string cipherList) unsigned char const *data = ¶ms[0]; - DH* const dh = d2i_DHparams (nullptr, &data, sizeof(params)); - if (dh == nullptr) + custom_delete_unique_ptr const dh {d2i_DHparams (nullptr, &data, sizeof(params))}; + if (!dh) LogicError ("d2i_DHparams returned nullptr."); - SSL_CTX_set_tmp_dh (c->native_handle (), dh); + SSL_CTX_set_tmp_dh (c->native_handle (), dh.get()); #ifdef SSL_FLAGS_NO_RENEGOTIATE_CIPHERS SSL_CTX_set_info_callback (c->native_handle (), info_handler); @@ -424,7 +436,7 @@ get_context (std::string cipherList) //------------------------------------------------------------------------------ std::shared_ptr -make_SSLContext(std::string cipherList) +make_SSLContext(std::string const& cipherList) { auto context = openssl::detail::get_context(cipherList); openssl::detail::initAnonymous(*context); @@ -436,10 +448,10 @@ make_SSLContext(std::string cipherList) std::shared_ptr make_SSLContextAuthed ( - std::string keyFile, - std::string certFile, - std::string chainFile, - std::string cipherList) + std::string const& keyFile, + std::string const& certFile, + std::string const& chainFile, + std::string const& cipherList) { auto context = openssl::detail::get_context(cipherList); openssl::detail::initAuthenticated(*context, @@ -448,4 +460,3 @@ make_SSLContextAuthed ( } } // ripple - diff --git a/src/ripple/basics/make_SSLContext.h b/src/ripple/basics/make_SSLContext.h index c6bfbaa137..db5127885b 100644 --- a/src/ripple/basics/make_SSLContext.h +++ b/src/ripple/basics/make_SSLContext.h @@ -28,15 +28,15 @@ namespace ripple { /** Create a self-signed SSL context that allows anonymous Diffie Hellman. */ std::shared_ptr make_SSLContext( - std::string cipherList); + std::string const& cipherList); /** Create an authenticated SSL context using the specified files. */ std::shared_ptr make_SSLContextAuthed ( - std::string keyFile, - std::string certFile, - std::string chainFile, - std::string cipherList); + std::string const& keyFile, + std::string const& certFile, + std::string const& chainFile, + std::string const& cipherList); } diff --git a/src/ripple/basics/random.h b/src/ripple/basics/random.h index 3cac22f605..b4388fd588 100644 --- a/src/ripple/basics/random.h +++ b/src/ripple/basics/random.h @@ -22,7 +22,7 @@ #include #include -#include +#include #include #include #include @@ -52,7 +52,7 @@ namespace detail { // Determines if a type can be called like an Engine template using is_engine = - beast::detail::is_call_possible; + beast::detail::is_invocable; } /** Return the default random engine. diff --git a/src/ripple/basics/tagged_integer.h b/src/ripple/basics/tagged_integer.h new file mode 100644 index 0000000000..b713ecc491 --- /dev/null +++ b/src/ripple/basics/tagged_integer.h @@ -0,0 +1,223 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright 2014, Nikolaos D. Bougalis + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_UTILITY_TAGGED_INTEGER_H_INCLUDED +#define BEAST_UTILITY_TAGGED_INTEGER_H_INCLUDED + +#include +#include +#include +#include +#include +#include + +namespace ripple { + +/** A type-safe wrap around standard integral types + + The tag is used to implement type safety, catching mismatched types at + compile time. Multiple instantiations wrapping the same underlying integral + type are distinct types (distinguished by tag) and will not interoperate. A + tagged_integer supports all the usual assignment, arithmetic, comparison and + shifting operations defined for the underlying type + + The tag is not meant as a unit, which would require restricting the set of + allowed arithmetic operations. +*/ +template +class tagged_integer : boost::operators> + , boost::shiftable> +{ +private: + Int m_value; + +public: + using value_type = Int; + using tag_type = Tag; + + tagged_integer() = default; + + template < + class OtherInt, + class = typename std::enable_if< + std::is_integral::value && + sizeof(OtherInt) <= sizeof(Int)>::type> + explicit + /* constexpr */ + tagged_integer(OtherInt value) noexcept + : m_value(value) + { + } + + bool + operator<(const tagged_integer & rhs) const noexcept + { + return m_value < rhs.m_value; + } + + bool + operator==(const tagged_integer & rhs) const noexcept + { + return m_value == rhs.m_value; + } + + tagged_integer& + operator+=(tagged_integer const& rhs) noexcept + { + m_value += rhs.m_value; + return *this; + } + + tagged_integer& + operator-=(tagged_integer const& rhs) noexcept + { + m_value -= rhs.m_value; + return *this; + } + + tagged_integer& + operator*=(tagged_integer const& rhs) noexcept + { + m_value *= rhs.m_value; + return *this; + } + + tagged_integer& + operator/=(tagged_integer const& rhs) noexcept + { + m_value /= rhs.m_value; + return *this; + } + + tagged_integer& + operator%=(tagged_integer const& rhs) noexcept + { + m_value %= rhs.m_value; + return *this; + } + + tagged_integer& + operator|=(tagged_integer const& rhs) noexcept + { + m_value |= rhs.m_value; + return *this; + } + + tagged_integer& + operator&=(tagged_integer const& rhs) noexcept + { + m_value &= rhs.m_value; + return *this; + } + + tagged_integer& + operator^=(tagged_integer const& rhs) noexcept + { + m_value ^= rhs.m_value; + return *this; + } + + tagged_integer& + operator<<=(const tagged_integer& rhs) noexcept + { + m_value <<= rhs.m_value; + return *this; + } + + tagged_integer& + operator>>=(const tagged_integer& rhs) noexcept + { + m_value >>= rhs.m_value; + return *this; + } + + tagged_integer + operator~() const noexcept + { + return tagged_integer{~m_value}; + } + + tagged_integer + operator+() const noexcept + { + return *this; + } + + tagged_integer + operator-() const noexcept + { + return tagged_integer{-m_value}; + } + + tagged_integer& + operator++ () noexcept + { + ++m_value; + return *this; + } + + tagged_integer& + operator-- () noexcept + { + --m_value; + return *this; + } + + explicit + operator Int() const noexcept + { + return m_value; + } + + friend + std::ostream& + operator<< (std::ostream& s, tagged_integer const& t) + { + s << t.m_value; + return s; + } + + friend + std::istream& + operator>> (std::istream& s, tagged_integer& t) + { + s >> t.m_value; + return s; + } + + friend + std::string + to_string(tagged_integer const& t) + { + return std::to_string(t.m_value); + } +}; + +} // ripple + +namespace beast { +template +struct is_contiguously_hashable, HashAlgorithm> + : public is_contiguously_hashable +{ +}; + +} // beast +#endif + diff --git a/src/ripple/beast/insight/impl/StatsDCollector.cpp b/src/ripple/beast/insight/impl/StatsDCollector.cpp index 90c5dca61b..bcad0d99c6 100644 --- a/src/ripple/beast/insight/impl/StatsDCollector.cpp +++ b/src/ripple/beast/insight/impl/StatsDCollector.cpp @@ -24,7 +24,6 @@ #include #include #include -#include #include #include #include @@ -383,8 +382,8 @@ public: #endif m_socket.async_send (buffers, std::bind ( &StatsDCollectorImp::on_send, this, keepAlive, - beast::asio::placeholders::error, - beast::asio::placeholders::bytes_transferred)); + std::placeholders::_1, + std::placeholders::_2)); buffers.clear (); size = 0; } @@ -400,8 +399,8 @@ public: #endif m_socket.async_send (buffers, std::bind ( &StatsDCollectorImp::on_send, this, keepAlive, - beast::asio::placeholders::error, - beast::asio::placeholders::bytes_transferred)); + std::placeholders::_1, + std::placeholders::_2)); } } @@ -411,7 +410,7 @@ public: m_timer.expires_from_now(1s); m_timer.async_wait (std::bind ( &StatsDCollectorImp::on_timer, this, - beast::asio::placeholders::error)); + std::placeholders::_1)); } void on_timer (boost::system::error_code ec) diff --git a/src/ripple/beast/rfc2616.h b/src/ripple/beast/rfc2616.h index 2c9a09d7c5..b2c22d4031 100644 --- a/src/ripple/beast/rfc2616.h +++ b/src/ripple/beast/rfc2616.h @@ -20,6 +20,8 @@ #ifndef BEAST_RFC2616_HPP #define BEAST_RFC2616_HPP +#include +#include #include #include #include @@ -277,7 +279,7 @@ split_commas(FwdIt first, FwdIt last) template > Result -split_commas(boost::string_ref const& s) +split_commas(beast::string_view const& s) { return split_commas(s.begin(), s.end()); } @@ -464,6 +466,17 @@ token_in_list(boost::string_ref const& value, return false; } +template +bool +is_keep_alive(beast::http::message const& m) +{ + if(m.version <= 10) + return beast::http::token_list{ + m[beast::http::field::connection]}.exists("keep-alive"); + return ! beast::http::token_list{ + m[beast::http::field::connection]}.exists("close"); +} + } // rfc2616 } // beast diff --git a/src/ripple/beast/utility/tagged_integer.h b/src/ripple/beast/utility/tagged_integer.h deleted file mode 100644 index 01e95e5375..0000000000 --- a/src/ripple/beast/utility/tagged_integer.h +++ /dev/null @@ -1,243 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of Beast: https://github.com/vinniefalco/Beast - Copyright 2014, Nikolaos D. Bougalis - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ -//============================================================================== - -#ifndef BEAST_UTILITY_TAGGED_INTEGER_H_INCLUDED -#define BEAST_UTILITY_TAGGED_INTEGER_H_INCLUDED - -#include - -#include -#include -#include -#include - -namespace beast { - -/** A type-safe wrap around standard unsigned integral types - - The tag is used to implement type safety, catching mismatched types at - compile time. Multiple instantiations wrapping the same underlying integral - type are distinct types (distinguished by tag) and will not interoperate. - - A tagged_integer supports all the comparison operators that are available - for the underlying integral type. It only supports a subset of arithmetic - operators, restricting mutation to only safe and meaningful types. -*/ -template -class tagged_integer -{ -private: - static_assert (std::is_unsigned ::value, - "The specified Int type must be unsigned"); - - Int m_value; - -public: - using value_type = Int; - using tag_type = Tag; - - tagged_integer() = default; - - template < - class OtherInt, - class = typename std::enable_if < - std::is_integral ::value && - sizeof (OtherInt) <= sizeof (Int) - >::type - > - explicit - /* constexpr */ - tagged_integer (OtherInt value) noexcept - : m_value (value) - { - } - - // Arithmetic operators - tagged_integer& - operator++ () noexcept - { - ++m_value; - return *this; - } - - tagged_integer - operator++ (int) noexcept - { - tagged_integer orig (*this); - ++(*this); - return orig; - } - - tagged_integer& - operator-- () noexcept - { - --m_value; - return *this; - } - - tagged_integer - operator-- (int) noexcept - { - tagged_integer orig (*this); - --(*this); - return orig; - } - - template - typename std::enable_if < - std::is_integral ::value && - sizeof (OtherInt) <= sizeof (Int), - tagged_integer >::type& - operator+= (OtherInt rhs) noexcept - { - m_value += rhs; - return *this; - } - - template - typename std::enable_if < - std::is_integral ::value && - sizeof (OtherInt) <= sizeof (Int), - tagged_integer >::type& - operator-= (OtherInt rhs) noexcept - { - m_value -= rhs; - return *this; - } - - template - friend - typename std::enable_if < - std::is_integral ::value && - sizeof (OtherInt) <= sizeof (Int), - tagged_integer >::type - operator+ (tagged_integer const& lhs, - OtherInt rhs) noexcept - { - return tagged_integer (lhs.m_value + rhs); - } - - template - friend - typename std::enable_if < - std::is_integral ::value && - sizeof (OtherInt) <= sizeof (Int), - tagged_integer >::type - operator+ (OtherInt lhs, - tagged_integer const& rhs) noexcept - { - return tagged_integer (lhs + rhs.m_value); - } - - template - friend - typename std::enable_if < - std::is_integral ::value && - sizeof (OtherInt) <= sizeof (Int), - tagged_integer >::type - operator- (tagged_integer const& lhs, - OtherInt rhs) noexcept - { - return tagged_integer (lhs.m_value - rhs); - } - - friend - Int - operator- (tagged_integer const& lhs, - tagged_integer const& rhs) noexcept - { - return lhs.m_value - rhs.m_value; - } - - // Comparison operators - friend - bool - operator== (tagged_integer const& lhs, - tagged_integer const& rhs) noexcept - { - return lhs.m_value == rhs.m_value; - } - - friend - bool - operator!= (tagged_integer const& lhs, - tagged_integer const& rhs) noexcept - { - return lhs.m_value != rhs.m_value; - } - - friend - bool - operator< (tagged_integer const& lhs, - tagged_integer const& rhs) noexcept - { - return lhs.m_value < rhs.m_value; - } - - friend - bool - operator<= (tagged_integer const& lhs, - tagged_integer const& rhs) noexcept - { - return lhs.m_value <= rhs.m_value; - } - - friend - bool - operator> (tagged_integer const& lhs, - tagged_integer const& rhs) noexcept - { - return lhs.m_value > rhs.m_value; - } - - friend - bool - operator>= (tagged_integer const& lhs, - tagged_integer const& rhs) noexcept - { - return lhs.m_value >= rhs.m_value; - } - - friend - std::ostream& - operator<< (std::ostream& s, tagged_integer const& t) - { - s << t.m_value; - return s; - } - - friend - std::istream& - operator>> (std::istream& s, tagged_integer& t) - { - s >> t.m_value; - return s; - } -}; - -template -struct is_contiguously_hashable, HashAlgorithm> - : public is_contiguously_hashable -{ -}; - -} - -#endif - diff --git a/src/ripple/consensus/LedgerTiming.cpp b/src/ripple/consensus/Consensus.cpp similarity index 81% rename from src/ripple/consensus/LedgerTiming.cpp rename to src/ripple/consensus/Consensus.cpp index fe069a2a53..a1e6af96da 100644 --- a/src/ripple/consensus/LedgerTiming.cpp +++ b/src/ripple/consensus/Consensus.cpp @@ -19,9 +19,7 @@ #include #include -#include -#include -#include +#include namespace ripple { @@ -33,13 +31,15 @@ shouldCloseLedger( std::size_t proposersValidated, std::chrono::milliseconds prevRoundTime, std::chrono::milliseconds - timeSincePrevClose, // Time since last ledger's close time + timeSincePrevClose, // Time since last ledger's close time std::chrono::milliseconds openTime, // Time waiting to close this ledger - std::chrono::seconds idleInterval, + std::chrono::milliseconds idleInterval, + ConsensusParms const& parms, beast::Journal j) { using namespace std::chrono_literals; - if ((prevRoundTime < -1s) || (prevRoundTime > 10min) || (timeSincePrevClose > 10min)) + if ((prevRoundTime < -1s) || (prevRoundTime > 10min) || + (timeSincePrevClose > 10min)) { // These are unexpected cases, we just close the ledger JLOG(j.warn()) << "shouldCloseLedger Trans=" @@ -64,7 +64,7 @@ shouldCloseLedger( } // Preserve minimum ledger open time - if (openTime < LEDGER_MIN_CLOSE) + if (openTime < parms.ledgerMIN_CLOSE) { JLOG(j.debug()) << "Must wait minimum time before closing"; return false; @@ -84,7 +84,11 @@ shouldCloseLedger( } bool -checkConsensusReached(std::size_t agreeing, std::size_t total, bool count_self) +checkConsensusReached( + std::size_t agreeing, + std::size_t total, + bool count_self, + std::size_t minConsensusPct) { // If we are alone, we have a consensus if (total == 0) @@ -96,9 +100,9 @@ checkConsensusReached(std::size_t agreeing, std::size_t total, bool count_self) ++total; } - int currentPercentage = (agreeing * 100) / total; + std::size_t currentPercentage = (agreeing * 100) / total; - return currentPercentage > minimumConsensusPercentage; + return currentPercentage >= minConsensusPct; } ConsensusState @@ -109,6 +113,7 @@ checkConsensus( std::size_t currentFinished, std::chrono::milliseconds previousAgreeTime, std::chrono::milliseconds currentAgreeTime, + ConsensusParms const& parms, bool proposing, beast::Journal j) { @@ -118,14 +123,14 @@ checkConsensus( << " time=" << currentAgreeTime.count() << "/" << previousAgreeTime.count(); - if (currentAgreeTime <= LEDGER_MIN_CONSENSUS) + if (currentAgreeTime <= parms.ledgerMIN_CONSENSUS) return ConsensusState::No; if (currentProposers < (prevProposers * 3 / 4)) { // Less than 3/4 of the last ledger's proposers are present; don't // rush: we may need more time. - if (currentAgreeTime < (previousAgreeTime + LEDGER_MIN_CONSENSUS)) + if (currentAgreeTime < (previousAgreeTime + parms.ledgerMIN_CONSENSUS)) { JLOG(j.trace()) << "too fast, not enough proposers"; return ConsensusState::No; @@ -134,7 +139,8 @@ checkConsensus( // Have we, together with the nodes on our UNL list, reached the threshold // to declare consensus? - if (checkConsensusReached(currentAgree, currentProposers, proposing)) + if (checkConsensusReached( + currentAgree, currentProposers, proposing, parms.minCONSENSUS_PCT)) { JLOG(j.debug()) << "normal consensus"; return ConsensusState::Yes; @@ -142,7 +148,8 @@ checkConsensus( // Have sufficient nodes on our UNL list moved on and reached the threshold // to declare consensus? - if (checkConsensusReached(currentFinished, currentProposers, false)) + if (checkConsensusReached( + currentFinished, currentProposers, false, parms.minCONSENSUS_PCT)) { JLOG(j.warn()) << "We see no consensus, but 80% of nodes have moved on"; return ConsensusState::MovedOn; @@ -153,4 +160,4 @@ checkConsensus( return ConsensusState::No; } -} // ripple +} // namespace ripple diff --git a/src/ripple/consensus/Consensus.h b/src/ripple/consensus/Consensus.h index 31c01bb5d4..2484a4b16b 100644 --- a/src/ripple/consensus/Consensus.h +++ b/src/ripple/consensus/Consensus.h @@ -24,11 +24,72 @@ #include #include #include +#include +#include +#include #include #include namespace ripple { + +/** Determines whether the current ledger should close at this time. + + This function should be called when a ledger is open and there is no close + in progress, or when a transaction is received and no close is in progress. + + @param anyTransactions indicates whether any transactions have been received + @param prevProposers proposers in the last closing + @param proposersClosed proposers who have currently closed this ledger + @param proposersValidated proposers who have validated the last closed + ledger + @param prevRoundTime time for the previous ledger to reach consensus + @param timeSincePrevClose time since the previous ledger's (possibly rounded) + close time + @param openTime duration this ledger has been open + @param idleInterval the network's desired idle interval + @param parms Consensus constant parameters + @param j journal for logging +*/ +bool +shouldCloseLedger( + bool anyTransactions, + std::size_t prevProposers, + std::size_t proposersClosed, + std::size_t proposersValidated, + std::chrono::milliseconds prevRoundTime, + std::chrono::milliseconds timeSincePrevClose, + std::chrono::milliseconds openTime, + std::chrono::milliseconds idleInterval, + ConsensusParms const & parms, + beast::Journal j); + +/** Determine whether the network reached consensus and whether we joined. + + @param prevProposers proposers in the last closing (not including us) + @param currentProposers proposers in this closing so far (not including us) + @param currentAgree proposers who agree with us + @param currentFinished proposers who have validated a ledger after this one + @param previousAgreeTime how long, in milliseconds, it took to agree on the + last ledger + @param currentAgreeTime how long, in milliseconds, we've been trying to + agree + @param parms Consensus constant parameters + @param proposing whether we should count ourselves + @param j journal for logging +*/ +ConsensusState +checkConsensus( + std::size_t prevProposers, + std::size_t currentProposers, + std::size_t currentAgree, + std::size_t currentFinished, + std::chrono::milliseconds previousAgreeTime, + std::chrono::milliseconds currentAgreeTime, + ConsensusParms const & parms, + bool proposing, + beast::Journal j); + /** Generic implementation of consensus algorithm. Achieves consensus on the next ledger. @@ -41,7 +102,8 @@ namespace ripple { The basic flow: 1. A call to `startRound` places the node in the `Open` phase. In this - phase, the node is waiting for transactions to include in its open ledger. + phase, the node is waiting for transactions to include in its open + ledger. 2. Successive calls to `timerEntry` check if the node can close the ledger. Once the node `Close`s the open ledger, it transitions to the `Establish` phase. In this phase, the node shares/receives peer @@ -54,13 +116,17 @@ namespace ripple { ledger with the network, does some book-keeping, then makes a call to `startRound` to start the cycle again. - This class uses CRTP to allow adapting Consensus for specific applications. + This class uses a generic interface to allow adapting Consensus for specific + applications. The Adaptor template implements a set of helper functions that + plug the consensus algorithm into a specific application. It also identifies + the types that play important roles in Consensus (transactions, ledgers, ...). + The code stubs below outline the interface and type requirements. The traits + types must be copy constructible and assignable. - The Derived template argument is used to embed consensus within the - larger application framework. The Traits template identifies types that - play important roles in Consensus (transactions, ledgers, etc.) and which must - conform to the generic interface outlined below. The Traits types must be copy - constructible and assignable. + @warning The generic implementation is not thread safe and the public methods + are not intended to be run concurrently. When in a concurrent environment, + the application is responsible for ensuring thread-safety. Simply locking + whenever touching the Consensus instance is one option. @code // A single transaction @@ -121,25 +187,36 @@ namespace ripple { Json::Value getJson() const; }; - struct Traits + // Wraps a peer's ConsensusProposal + struct PeerPosition { - using Ledger_t = Ledger; - using NodeID_t = std::uint32_t; - using TxSet_t = TxSet; - } + ConsensusProposal< + std::uint32_t, //NodeID, + typename Ledger::ID, + typename TxSet::ID> const & + proposal() const; - class ConsensusImp : public Consensus + }; + + + class Adaptor { + public: + //----------------------------------------------------------------------- + // Define consensus types + using Ledger_t = Ledger; + using NodeID_t = std::uint32_t; + using TxSet_t = TxSet; + using PeerPosition_t = PeerPosition; + + //----------------------------------------------------------------------- + // // Attempt to acquire a specific ledger. boost::optional acquireLedger(Ledger::ID const & ledgerID); // Acquire the transaction set associated with a proposed position. boost::optional acquireTxSet(TxSet::ID const & setID); - // Get peers' proposed positions. Returns an iterable - // with value_type convertable to ConsensusPosition<...> - auto const & proposals(Ledger::ID const & ledgerID); - // Whether any transactions are in the open ledger bool hasOpenTransactions() const; @@ -156,6 +233,8 @@ namespace ripple { Ledger const & prevLedger, Mode mode); + // Called whenever consensus operating mode changes + void onModeChange(ConsensusMode before, ConsensusMode after); // Called when ledger closes Result onClose(Ledger const &, Ledger const & prev, Mode mode); @@ -179,9 +258,7 @@ namespace ripple { void propose(ConsensusProposal<...> const & pos); // Relay a received peer proposal on to other peer's. - // The argument type should be convertible to ConsensusProposal<...> - // but may be a different type. - void relay(implementation_defined const & pos); + void relay(PeerPosition_t const & prop); // Relay a disputed transaction to peers void relay(Txn const & tx); @@ -189,159 +266,53 @@ namespace ripple { // Share given transaction set with peers void relay(TxSet const &s); + // Consensus timing parameters and constants + ConsensusParms const & + parms() const; }; @endcode - @tparam Derived The deriving class which adapts the Consensus algorithm. - @tparam Traits Provides definitions of types used in Consensus. + @tparam Adaptor Defines types and provides helper functions needed to adapt + Consensus to the larger application. */ -template +template class Consensus { - //! Phases of consensensus: - //! "close" "accept" - //! open ------- > establish ---------> accepted - //! ^ | | - //! |---------------| | - //! ^ "startRound" | - //! |------------------------------------| - //! - //! The typical transition goes from open to establish to accepted and - //! then a call to startRound begins the process anew. - //! However, if a wrong prior ledger is detected and recovered - //! during the establish or accept phase, consensus will internally go back - //! to open (see handleWrongLedger). - enum class Phase { - //! We haven't closed our ledger yet, but others might have - open, - - //! Establishing consensus by exchanging proposals with our peers - establish, - - //! We have accepted a new last closed ledger and are waiting on a call - //! to startRound to begin the next consensus round. No changes - //! to consensus phase occur while in this phase. - accepted, - }; - - using Ledger_t = typename Traits::Ledger_t; - using TxSet_t = typename Traits::TxSet_t; - using NodeID_t = typename Traits::NodeID_t; + using Ledger_t = typename Adaptor::Ledger_t; + using TxSet_t = typename Adaptor::TxSet_t; + using NodeID_t = typename Adaptor::NodeID_t; using Tx_t = typename TxSet_t::Tx; + using PeerPosition_t = typename Adaptor::PeerPosition_t; using Proposal_t = ConsensusProposal< NodeID_t, typename Ledger_t::ID, typename TxSet_t::ID>; -protected: - //! How we participating in Consensus - //! proposing observing - //! \ / - //| \---> wrongLedger <---/ - //! ^ - //! | - //! | - //! v - //! switchedLedger - //! We enter the round proposing or observing. If we detect we - //! are working on the wrong prior ledger, we go to - //! wrongLedger and attempt to acquire the right one (ref - //! handleWrongLedger). Once we acquire the right one, we go to - //! switchedLedger. If we again detect the wrongLedger before this round - //! ends, we go back to wrongLedger until we acquire the right one. - enum class Mode { - //! We a normal participant in consensus and propose our position - proposing, - //! We are observing peer positions, but not proposing our position - observing, - //! We have the wrong ledger and are attempting to acquire it - wrongLedger, - //! We switched ledger since we started this consensus round but are now - //! running on what we believe is the correct ledger. This mode is as - //! if we entered the round observing, but is used to indicate we did - //! have the wrongLedger at some point. - switchedLedger - }; + using Result = ConsensusResult; - //! Measure duration of phases of consensus - class Stopwatch + // Helper class to ensure adaptor is notified whenver the ConsensusMode + // changes + class MonitoredMode { - using time_point = std::chrono::steady_clock::time_point; - time_point start_; - std::chrono::milliseconds dur_; + ConsensusMode mode_; public: - std::chrono::milliseconds - read() const + MonitoredMode(ConsensusMode m) : mode_{m} { - return dur_; + } + ConsensusMode + get() const + { + return mode_; } void - tick(std::chrono::milliseconds fixed) + set(ConsensusMode mode, Adaptor& a) { - dur_ += fixed; - } - - void - reset(time_point tp) - { - start_ = tp; - dur_ = std::chrono::milliseconds{0}; - } - - void - tick(time_point tp) - { - using namespace std::chrono; - dur_ = duration_cast(tp - start_); + a.onModeChange(mode_, mode); + mode_ = mode; } }; - - //! Initial ledger close times, not rounded by closeTimeResolution - struct CloseTimes - { - // Close time estimates, keep ordered for predictable traverse - std::map peers; - NetClock::time_point self; - }; - - /** Enacpsulates the result of consensus. - - While in the establish phase, represents our work in progress consensus - result. In the accept phase, represents our final consensus result - for this round. - */ - struct Result - { - using Dispute_t = DisputedTx; - - Result(TxSet_t&& s, Proposal_t&& p) - : set{std::move(s)}, position{std::move(p)} - { - assert(set.id() == position.position()); - } - - //! The set of transactions consensus agrees go in the ledger - TxSet_t set; - - //! Our proposed position on transactions/close time - Proposal_t position; - - //! Transactions which are under dispute with our peers - hash_map disputes; - - // Set of TxSet ids we have already compared/created disputes - hash_set compares; - - // Measures the duration of the establish phase for this consensus round - Stopwatch roundTime; - - // Indicates state in which consensus ended. Once in the accept phase - // will be either Yes or MovedOn - ConsensusState state = ConsensusState::No; - }; - public: //! Clock type for measuring time within the consensus code using clock_type = beast::abstract_clock; @@ -351,9 +322,10 @@ public: /** Constructor. @param clock The clock used to internally sample consensus progress + @param adaptor The instance of the adaptor class @param j The journal to log debug output */ - Consensus(clock_type const& clock, beast::Journal j); + Consensus(clock_type const& clock, Adaptor& adaptor, beast::Journal j); /** Kick-off the next round of consensus. @@ -383,7 +355,7 @@ public: bool peerProposal( NetClock::time_point const& now, - Proposal_t const& newProposal); + PeerPosition_t const& newProposal); /** Call periodically to drive consensus forward. @@ -431,39 +403,9 @@ public: typename Ledger_t::ID prevLedgerID() const { - std::lock_guard _(*lock_); return prevLedgerID_; } - //! Get the number of proposing peers that participated in the previous - //! round. - std::size_t - prevProposers() const - { - return prevProposers_; - } - - /** Get duration of the previous round. - - The duration of the round is the establish phase, measured from closing - the open ledger to accepting the consensus result. - - @return Last round duration in milliseconds - */ - std::chrono::milliseconds - prevRoundTime() const - { - return prevRoundTime_; - } - - /** Get the current consensus mode. - */ - Mode - mode() const - { - return mode_; - } - /** Get the Json state of the consensus process. Called by the consensus_info RPC. @@ -474,24 +416,13 @@ public: Json::Value getJson(bool full) const; -protected: - - // Prevent deleting derived instance through base pointer - ~Consensus() = default; - - /** Revoke our outstanding proposal, if any, and cease proposing - until this round ends. - */ - void - leaveConsensus(); - private: void startRoundInternal( NetClock::time_point const& now, typename Ledger_t::ID const& prevLedgerID, Ledger_t const& prevLedger, - Mode mode); + ConsensusMode mode); // Change our view of the previous ledger void @@ -511,6 +442,13 @@ private: void playbackProposals(); + /** Handle a replayed or a new peer proposal. + */ + bool + peerProposalInternal( + NetClock::time_point const& now, + PeerPosition_t const& newProposal); + /** Handle pre-close phase. In the pre-close phase, the ledger is open as we wait for new @@ -551,24 +489,16 @@ private: void updateDisputes(NodeID_t const& node, TxSet_t const& other); - Derived& - impl() - { - return *static_cast(this); - } - - static std::string - to_string(Phase p); - - static std::string - to_string(Mode m); + // Revoke our outstanding proposal, if any, and cease proposing + // until this round ends. + void + leaveConsensus(); private: - // TODO: Move this to clients - std::unique_ptr lock_; + Adaptor& adaptor_; - Phase phase_ = Phase::accepted; - Mode mode_ = Mode::observing; + ConsensusPhase phase_{ConsensusPhase::accepted}; + MonitoredMode mode_{ConsensusMode::observing}; bool firstRound_ = true; bool haveCloseTimeConsensus_ = false; @@ -579,12 +509,12 @@ private: int convergePercent_{0}; // How long has this round been open - Stopwatch openTime_; + ConsensusTimer openTime_; NetClock::duration closeResolution_ = ledgerDefaultTimeResolution; // Time it took for the last consensus round to converge - std::chrono::milliseconds prevRoundTime_ = LEDGER_IDLE_INTERVAL; + std::chrono::milliseconds prevRoundTime_; //------------------------------------------------------------------------- // Network time measurements of consensus progress @@ -606,11 +536,17 @@ private: hash_map acquired_; boost::optional result_; - CloseTimes rawCloseTimes_; + ConsensusCloseTimes rawCloseTimes_; + //------------------------------------------------------------------------- // Peer related consensus data - // Convergence tracking, trusted peers indexed by hash of public key - hash_map peerProposals_; + + // Peer proposed positions for the current round + hash_map currPeerPositions_; + + // Recently received peer positions, available when transitioning between + // ledgers or roundss + hash_map> recentPeerPositions_; // The number of proposers who participated in the last consensus round std::size_t prevProposers_ = 0; @@ -622,30 +558,30 @@ private: beast::Journal j_; }; -template -Consensus::Consensus( +template +Consensus::Consensus( clock_type const& clock, + Adaptor& adaptor, beast::Journal journal) - : lock_(std::make_unique()) + : adaptor_(adaptor) , clock_(clock) , j_{journal} { JLOG(j_.debug()) << "Creating consensus object"; } -template +template void -Consensus::startRound( +Consensus::startRound( NetClock::time_point const& now, typename Ledger_t::ID const& prevLedgerID, Ledger_t prevLedger, bool proposing) { - std::lock_guard _(*lock_); - if (firstRound_) { // take our initial view of closeTime_ from the seed ledger + prevRoundTime_ = adaptor_.parms().ledgerIDLE_INTERVAL; prevCloseTime_ = prevLedger.closeTime(); firstRound_ = false; } @@ -654,41 +590,38 @@ Consensus::startRound( prevCloseTime_ = rawCloseTimes_.self; } - Mode startMode = proposing ? Mode::proposing : Mode::observing; + ConsensusMode startMode = + proposing ? ConsensusMode::proposing : ConsensusMode::observing; // We were handed the wrong ledger if (prevLedger.id() != prevLedgerID) { // try to acquire the correct one - if(auto newLedger = impl().acquireLedger(prevLedgerID)) + if (auto newLedger = adaptor_.acquireLedger(prevLedgerID)) { prevLedger = *newLedger; } - else // Unable to acquire the correct ledger + else // Unable to acquire the correct ledger { - startMode = Mode::wrongLedger; + startMode = ConsensusMode::wrongLedger; JLOG(j_.info()) << "Entering consensus with: " << previousLedger_.id(); JLOG(j_.info()) << "Correct LCL is: " << prevLedgerID; } } - startRoundInternal( - now, - prevLedgerID, - prevLedger, - startMode); + startRoundInternal(now, prevLedgerID, prevLedger, startMode); } -template +template void -Consensus::startRoundInternal( +Consensus::startRoundInternal( NetClock::time_point const& now, typename Ledger_t::ID const& prevLedgerID, Ledger_t const& prevLedger, - Mode mode) + ConsensusMode mode) { - phase_ = Phase::open; - mode_ = mode; + phase_ = ConsensusPhase::open; + mode_.set(mode, adaptor_); now_ = now; prevLedgerID_ = prevLedgerID; previousLedger_ = prevLedger; @@ -696,7 +629,7 @@ Consensus::startRoundInternal( convergePercent_ = 0; haveCloseTimeConsensus_ = false; openTime_.reset(clock_.now()); - peerProposals_.clear(); + currPeerPositions_.clear(); acquired_.clear(); rawCloseTimes_.peers.clear(); rawCloseTimes_.self = {}; @@ -708,7 +641,7 @@ Consensus::startRoundInternal( previousLedger_.seq() + 1); playbackProposals(); - if (peerProposals_.size() > (prevProposers_ / 2)) + if (currPeerPositions_.size() > (prevProposers_ / 2)) { // We may be falling behind, don't wait for the timer // consider closing the ledger immediately @@ -716,25 +649,45 @@ Consensus::startRoundInternal( } } -template +template bool -Consensus::peerProposal( +Consensus::peerProposal( NetClock::time_point const& now, - Proposal_t const& newProposal) + PeerPosition_t const& newPeerPos) { - auto const peerID = newProposal.nodeID(); + NodeID_t const& peerID = newPeerPos.proposal().nodeID(); - std::lock_guard _(*lock_); + // Always need to store recent positions + { + auto& props = recentPeerPositions_[peerID]; - // Nothing to do if we are currently working on a ledger - if (phase_ == Phase::accepted) + if (props.size() >= 10) + props.pop_front(); + + props.push_back(newPeerPos); + } + return peerProposalInternal(now, newPeerPos); +} + +template +bool +Consensus::peerProposalInternal( + NetClock::time_point const& now, + PeerPosition_t const& newPeerPos) +{ + // Nothing to do for now if we are currently working on a ledger + if (phase_ == ConsensusPhase::accepted) return false; now_ = now; - if (newProposal.prevLedger() != prevLedgerID_) + Proposal_t const& newPeerProp = newPeerPos.proposal(); + + NodeID_t const& peerID = newPeerProp.nodeID(); + + if (newPeerProp.prevLedger() != prevLedgerID_) { - JLOG(j_.debug()) << "Got proposal for " << newProposal.prevLedger() + JLOG(j_.debug()) << "Got proposal for " << newPeerProp.prevLedger() << " but we are on " << prevLedgerID_; return false; } @@ -748,18 +701,18 @@ Consensus::peerProposal( { // update current position - auto currentPosition = peerProposals_.find(peerID); + auto peerPosIt = currPeerPositions_.find(peerID); - if (currentPosition != peerProposals_.end()) + if (peerPosIt != currPeerPositions_.end()) { - if (newProposal.proposeSeq() <= - currentPosition->second.proposeSeq()) + if (newPeerProp.proposeSeq() <= + peerPosIt->second.proposal().proposeSeq()) { return false; } } - if (newProposal.isBowOut()) + if (newPeerProp.isBowOut()) { using std::to_string; @@ -769,59 +722,57 @@ Consensus::peerProposal( for (auto& it : result_->disputes) it.second.unVote(peerID); } - if (currentPosition != peerProposals_.end()) - peerProposals_.erase(peerID); + if (peerPosIt != currPeerPositions_.end()) + currPeerPositions_.erase(peerID); deadNodes_.insert(peerID); return true; } - if (currentPosition != peerProposals_.end()) - currentPosition->second = newProposal; + if (peerPosIt != currPeerPositions_.end()) + peerPosIt->second = newPeerPos; else - peerProposals_.emplace(peerID, newProposal); + currPeerPositions_.emplace(peerID, newPeerPos); } - if (newProposal.isInitial()) + if (newPeerProp.isInitial()) { // Record the close time estimate JLOG(j_.trace()) << "Peer reports close time as " - << newProposal.closeTime().time_since_epoch().count(); - ++rawCloseTimes_.peers[newProposal.closeTime()]; + << newPeerProp.closeTime().time_since_epoch().count(); + ++rawCloseTimes_.peers[newPeerProp.closeTime()]; } - JLOG(j_.trace()) << "Processing peer proposal " << newProposal.proposeSeq() - << "/" << newProposal.position(); + JLOG(j_.trace()) << "Processing peer proposal " << newPeerProp.proposeSeq() + << "/" << newPeerProp.position(); { - auto ait = acquired_.find(newProposal.position()); + auto const ait = acquired_.find(newPeerProp.position()); if (ait == acquired_.end()) { // acquireTxSet will return the set if it is available, or // spawn a request for it and return none/nullptr. It will call // gotTxSet once it arrives - if (auto set = impl().acquireTxSet(newProposal.position())) + if (auto set = adaptor_.acquireTxSet(newPeerProp.position())) gotTxSet(now_, *set); else JLOG(j_.debug()) << "Don't have tx set for peer"; } else if (result_) { - updateDisputes(newProposal.nodeID(), ait->second); + updateDisputes(newPeerProp.nodeID(), ait->second); } } return true; } -template +template void -Consensus::timerEntry(NetClock::time_point const& now) +Consensus::timerEntry(NetClock::time_point const& now) { - std::lock_guard _(*lock_); - // Nothing to do if we are currently working on a ledger - if (phase_ == Phase::accepted) + if (phase_ == ConsensusPhase::accepted) return; now_ = now; @@ -829,26 +780,24 @@ Consensus::timerEntry(NetClock::time_point const& now) // Check we are on the proper ledger (this may change phase_) checkLedger(); - if(phase_ == Phase::open) + if (phase_ == ConsensusPhase::open) { phaseOpen(); } - else if (phase_ == Phase::establish) + else if (phase_ == ConsensusPhase::establish) { phaseEstablish(); } } -template +template void -Consensus::gotTxSet( +Consensus::gotTxSet( NetClock::time_point const& now, TxSet_t const& txSet) { - std::lock_guard _(*lock_); - // Nothing to do if we've finished work on a ledger - if (phase_ == Phase::accepted) + if (phase_ == ConsensusPhase::accepted) return; now_ = now; @@ -870,11 +819,11 @@ Consensus::gotTxSet( // so this txSet must differ assert(id != result_->position.position()); bool any = false; - for (auto const& p : peerProposals_) + for (auto const& it : currPeerPositions_) { - if (p.second.position() == id) + if (it.second.proposal().position() == id) { - updateDisputes(p.first, txSet); + updateDisputes(it.first, txSet); any = true; } } @@ -887,40 +836,42 @@ Consensus::gotTxSet( } } -template +template void -Consensus::simulate( +Consensus::simulate( NetClock::time_point const& now, boost::optional consensusDelay) { - std::lock_guard _(*lock_); - JLOG(j_.info()) << "Simulating consensus"; now_ = now; closeLedger(); result_->roundTime.tick(consensusDelay.value_or(100ms)); - prevProposers_ = peerProposals_.size(); + result_->proposers = prevProposers_ = currPeerPositions_.size(); prevRoundTime_ = result_->roundTime.read(); - phase_ = Phase::accepted; - impl().onForceAccept( - *result_, previousLedger_, closeResolution_, rawCloseTimes_, mode_); + phase_ = ConsensusPhase::accepted; + adaptor_.onForceAccept( + *result_, + previousLedger_, + closeResolution_, + rawCloseTimes_, + mode_.get(), + getJson(true)); JLOG(j_.info()) << "Simulation complete"; } -template +template Json::Value -Consensus::getJson(bool full) const +Consensus::getJson(bool full) const { using std::to_string; using Int = Json::Value::Int; Json::Value ret(Json::objectValue); - std::lock_guard _(*lock_); - ret["proposing"] = (mode_ == Mode::proposing); - ret["proposers"] = static_cast(peerProposals_.size()); + ret["proposing"] = (mode_.get() == ConsensusMode::proposing); + ret["proposers"] = static_cast(currPeerPositions_.size()); - if (mode_ != Mode::wrongLedger) + if (mode_.get() != ConsensusMode::wrongLedger) { ret["synched"] = true; ret["ledger_seq"] = previousLedger_.seq() + 1; @@ -929,7 +880,7 @@ Consensus::getJson(bool full) const else ret["synched"] = false; - ret["phase"] = Consensus::to_string(phase_); + ret["phase"] = to_string(phase_); if (result_ && !result_->disputes.empty() && !full) ret["disputes"] = static_cast(result_->disputes.size()); @@ -948,11 +899,11 @@ Consensus::getJson(bool full) const ret["previous_proposers"] = static_cast(prevProposers_); ret["previous_mseconds"] = static_cast(prevRoundTime_.count()); - if (!peerProposals_.empty()) + if (!currPeerPositions_.empty()) { Json::Value ppj(Json::objectValue); - for (auto& pp : peerProposals_) + for (auto const& pp : currPeerPositions_) { ppj[to_string(pp.first)] = pp.second.getJson(); } @@ -962,7 +913,7 @@ Consensus::getJson(bool full) const if (!acquired_.empty()) { Json::Value acq(Json::arrayValue); - for (auto& at : acquired_) + for (auto const& at : acquired_) { acq.append(to_string(at.first)); } @@ -972,7 +923,7 @@ Consensus::getJson(bool full) const if (result_ && !result_->disputes.empty()) { Json::Value dsj(Json::objectValue); - for (auto& dt : result_->disputes) + for (auto const& dt : result_->disputes) { dsj[to_string(dt.first)] = dt.second.getJson(); } @@ -982,7 +933,7 @@ Consensus::getJson(bool full) const if (!rawCloseTimes_.peers.empty()) { Json::Value ctj(Json::objectValue); - for (auto& ct : rawCloseTimes_.peers) + for (auto const& ct : rawCloseTimes_.peers) { ctj[std::to_string(ct.first.time_since_epoch().count())] = ct.second; @@ -1005,10 +956,9 @@ Consensus::getJson(bool full) const } // Handle a change in the prior ledger during a consensus round -template +template void -Consensus::handleWrongLedger( - typename Ledger_t::ID const& lgrId) +Consensus::handleWrongLedger(typename Ledger_t::ID const& lgrId) { assert(lgrId != prevLedgerID_ || previousLedger_.id() != lgrId); @@ -1027,7 +977,7 @@ Consensus::handleWrongLedger( result_->compares.clear(); } - peerProposals_.clear(); + currPeerPositions_.clear(); rawCloseTimes_.peers.clear(); deadNodes_.clear(); @@ -1039,65 +989,75 @@ Consensus::handleWrongLedger( return; // we need to switch the ledger we're working from - if (auto newLedger = impl().acquireLedger(prevLedgerID_)) + if (auto newLedger = adaptor_.acquireLedger(prevLedgerID_)) { JLOG(j_.info()) << "Have the consensus ledger " << prevLedgerID_; - startRoundInternal(now_, lgrId, *newLedger, Mode::switchedLedger); + startRoundInternal( + now_, lgrId, *newLedger, ConsensusMode::switchedLedger); } else { - mode_ = Mode::wrongLedger; + mode_.set(ConsensusMode::wrongLedger, adaptor_); } } -template +template void -Consensus::checkLedger() +Consensus::checkLedger() { - auto netLgr = impl().getPrevLedger(prevLedgerID_, previousLedger_, mode_); + auto netLgr = + adaptor_.getPrevLedger(prevLedgerID_, previousLedger_, mode_.get()); if (netLgr != prevLedgerID_) { JLOG(j_.warn()) << "View of consensus changed during " << to_string(phase_) << " status=" << to_string(phase_) << ", " - << " mode=" << to_string(mode_); + << " mode=" << to_string(mode_.get()); JLOG(j_.warn()) << prevLedgerID_ << " to " << netLgr; JLOG(j_.warn()) << previousLedger_.getJson(); + JLOG(j_.debug()) << "State on consensus change " << getJson(true); handleWrongLedger(netLgr); } else if (previousLedger_.id() != prevLedgerID_) handleWrongLedger(netLgr); } -template +template void -Consensus::playbackProposals() +Consensus::playbackProposals() { - for (auto const& p : impl().proposals(prevLedgerID_)) + for (auto const& it : recentPeerPositions_) { - if (peerProposal(now_, p)) - impl().relay(p); + for (auto const& pos : it.second) + { + if (pos.proposal().prevLedger() == prevLedgerID_) + { + if (peerProposalInternal(now_, pos)) + adaptor_.relay(pos); + } + } } } -template +template void -Consensus::phaseOpen() +Consensus::phaseOpen() { using namespace std::chrono; // it is shortly before ledger close time - bool anyTransactions = impl().hasOpenTransactions(); - auto proposersClosed = peerProposals_.size(); - auto proposersValidated = impl().proposersValidated(prevLedgerID_); + bool anyTransactions = adaptor_.hasOpenTransactions(); + auto proposersClosed = currPeerPositions_.size(); + auto proposersValidated = adaptor_.proposersValidated(prevLedgerID_); openTime_.tick(clock_.now()); // This computes how long since last ledger's close time milliseconds sinceClose; { - bool previousCloseCorrect = (mode_ != Mode::wrongLedger) && + bool previousCloseCorrect = + (mode_.get() != ConsensusMode::wrongLedger) && previousLedger_.closeAgree() && (previousLedger_.closeTime() != (previousLedger_.parentCloseTime() + 1s)); @@ -1112,9 +1072,9 @@ Consensus::phaseOpen() sinceClose = -duration_cast(lastCloseTime - now_); } - auto const idleInterval = std::max( - LEDGER_IDLE_INTERVAL, - duration_cast(2 * previousLedger_.closeTimeResolution())); + auto const idleInterval = std::max( + adaptor_.parms().ledgerIDLE_INTERVAL, + 2 * previousLedger_.closeTimeResolution()); // Decide if we should close the ledger if (shouldCloseLedger( @@ -1126,27 +1086,31 @@ Consensus::phaseOpen() sinceClose, openTime_.read(), idleInterval, + adaptor_.parms(), j_)) { closeLedger(); } } -template +template void -Consensus::phaseEstablish() +Consensus::phaseEstablish() { // can only establish consensus if we already took a stance assert(result_); using namespace std::chrono; + ConsensusParms const & parms = adaptor_.parms(); + result_->roundTime.tick(clock_.now()); + result_->proposers = currPeerPositions_.size(); convergePercent_ = result_->roundTime.read() * 100 / - std::max(prevRoundTime_, AV_MIN_CONSENSUS_TIME); + std::max(prevRoundTime_, parms.avMIN_CONSENSUS_TIME); // Give everyone a chance to take an initial position - if (result_->roundTime.read() < LEDGER_MIN_CONSENSUS) + if (result_->roundTime.read() < parms.ledgerMIN_CONSENSUS) return; updateOurPositions(); @@ -1161,40 +1125,45 @@ Consensus::phaseEstablish() return; } - JLOG(j_.info()) << "Converge cutoff (" << peerProposals_.size() + JLOG(j_.info()) << "Converge cutoff (" << currPeerPositions_.size() << " participants)"; - prevProposers_ = peerProposals_.size(); + prevProposers_ = currPeerPositions_.size(); prevRoundTime_ = result_->roundTime.read(); - phase_ = Phase::accepted; - impl().onAccept( - *result_, previousLedger_, closeResolution_, rawCloseTimes_, mode_); + phase_ = ConsensusPhase::accepted; + adaptor_.onAccept( + *result_, + previousLedger_, + closeResolution_, + rawCloseTimes_, + mode_.get(), + getJson(true)); } -template +template void -Consensus::closeLedger() +Consensus::closeLedger() { // We should not be closing if we already have a position assert(!result_); - phase_ = Phase::establish; + phase_ = ConsensusPhase::establish; rawCloseTimes_.self = now_; - result_.emplace(impl().onClose(previousLedger_, now_, mode_)); + result_.emplace(adaptor_.onClose(previousLedger_, now_, mode_.get())); result_->roundTime.reset(clock_.now()); // Share the newly created transaction set if we haven't already // received it from a peer if (acquired_.emplace(result_->set.id(), result_->set).second) - impl().relay(result_->set); + adaptor_.relay(result_->set); - if (mode_ == Mode::proposing) - impl().propose(result_->position); + if (mode_.get() == ConsensusMode::proposing) + adaptor_.propose(result_->position); // Create disputes with any peer positions we have transactions for - for (auto const& p : peerProposals_) + for (auto const& pit : currPeerPositions_) { - auto pos = p.second.position(); - auto it = acquired_.find(pos); + auto const& pos = pit.second.proposal().position(); + auto const it = acquired_.find(pos); if (it != acquired_.end()) { createDisputes(it->second); @@ -1222,37 +1191,39 @@ participantsNeeded(int participants, int percent) return (result == 0) ? 1 : result; } -template +template void -Consensus::updateOurPositions() +Consensus::updateOurPositions() { // We must have a position if we are updating it assert(result_); + ConsensusParms const & parms = adaptor_.parms(); // Compute a cutoff time - auto const peerCutoff = now_ - PROPOSE_FRESHNESS; - auto const ourCutoff = now_ - PROPOSE_INTERVAL; + auto const peerCutoff = now_ - parms.proposeFRESHNESS; + auto const ourCutoff = now_ - parms.proposeINTERVAL; // Verify freshness of peer positions and compute close times std::map effCloseTimes; { - auto it = peerProposals_.begin(); - while (it != peerProposals_.end()) + auto it = currPeerPositions_.begin(); + while (it != currPeerPositions_.end()) { - if (it->second.isStale(peerCutoff)) + Proposal_t const& peerProp = it->second.proposal(); + if (peerProp.isStale(peerCutoff)) { // peer's proposal is stale, so remove it - auto const& peerID = it->second.nodeID(); + NodeID_t const& peerID = peerProp.nodeID(); JLOG(j_.warn()) << "Removing stale proposal from " << peerID; for (auto& dt : result_->disputes) dt.second.unVote(peerID); - it = peerProposals_.erase(it); + it = currPeerPositions_.erase(it); } else { // proposal is still fresh ++effCloseTimes[effCloseTime( - it->second.closeTime(), + peerProp.closeTime(), closeResolution_, previousLedger_.closeTime())]; ++it; @@ -1271,7 +1242,9 @@ Consensus::updateOurPositions() // Because the threshold for inclusion increases, // time can change our position on a dispute if (it.second.updateVote( - convergePercent_, (mode_ == Mode::proposing))) + convergePercent_, + mode_.get()== ConsensusMode::proposing, + parms)) { if (!mutableSet) mutableSet.emplace(result_->set); @@ -1296,7 +1269,7 @@ Consensus::updateOurPositions() NetClock::time_point consensusCloseTime = {}; haveCloseTimeConsensus_ = false; - if (peerProposals_.empty()) + if (currPeerPositions_.empty()) { // no other times haveCloseTimeConsensus_ = true; @@ -1309,17 +1282,17 @@ Consensus::updateOurPositions() { int neededWeight; - if (convergePercent_ < AV_MID_CONSENSUS_TIME) - neededWeight = AV_INIT_CONSENSUS_PCT; - else if (convergePercent_ < AV_LATE_CONSENSUS_TIME) - neededWeight = AV_MID_CONSENSUS_PCT; - else if (convergePercent_ < AV_STUCK_CONSENSUS_TIME) - neededWeight = AV_LATE_CONSENSUS_PCT; + if (convergePercent_ < parms.avMID_CONSENSUS_TIME) + neededWeight = parms.avINIT_CONSENSUS_PCT; + else if (convergePercent_ < parms.avLATE_CONSENSUS_TIME) + neededWeight = parms.avMID_CONSENSUS_PCT; + else if (convergePercent_ < parms.avSTUCK_CONSENSUS_TIME) + neededWeight = parms.avLATE_CONSENSUS_PCT; else - neededWeight = AV_STUCK_CONSENSUS_PCT; + neededWeight = parms.avSTUCK_CONSENSUS_PCT; - int participants = peerProposals_.size(); - if (mode_ == Mode::proposing) + int participants = currPeerPositions_.size(); + if (mode_.get() == ConsensusMode::proposing) { ++effCloseTimes[effCloseTime( result_->position.closeTime(), @@ -1333,9 +1306,9 @@ Consensus::updateOurPositions() // Threshold to declare consensus int const threshConsensus = - participantsNeeded(participants, AV_CT_CONSENSUS_PCT); + participantsNeeded(participants, parms.avCT_CONSENSUS_PCT); - JLOG(j_.info()) << "Proposers:" << peerProposals_.size() + JLOG(j_.info()) << "Proposers:" << currPeerPositions_.size() << " nw:" << neededWeight << " thrV:" << threshVote << " thrC:" << threshConsensus; @@ -1361,8 +1334,9 @@ Consensus::updateOurPositions() { JLOG(j_.debug()) << "No CT consensus:" - << " Proposers:" << peerProposals_.size() - << " Mode:" << to_string(mode_) << " Thresh:" << threshConsensus + << " Proposers:" << currPeerPositions_.size() + << " Mode:" << to_string(mode_.get()) + << " Thresh:" << threshConsensus << " Pos:" << consensusCloseTime.time_since_epoch().count(); } } @@ -1396,26 +1370,26 @@ Consensus::updateOurPositions() if (acquired_.emplace(newID, result_->set).second) { if (!result_->position.isBowOut()) - impl().relay(result_->set); + adaptor_.relay(result_->set); - for (auto const& p : peerProposals_) + for (auto const& it : currPeerPositions_) { - if (p.second.position() == newID) - { - updateDisputes(p.first, result_->set); - } + Proposal_t const& p = it.second.proposal(); + if (p.position() == newID) + updateDisputes(it.first, result_->set); } } // Share our new position if we are still participating this round - if (!result_->position.isBowOut() && (mode_ == Mode::proposing)) - impl().propose(result_->position); + if (!result_->position.isBowOut() && + (mode_.get() == ConsensusMode::proposing)) + adaptor_.propose(result_->position); } } -template +template bool -Consensus::haveConsensus() +Consensus::haveConsensus() { // Must have a stance if we are checking for consensus assert(result_); @@ -1426,9 +1400,10 @@ Consensus::haveConsensus() auto ourPosition = result_->position.position(); // Count number of agreements/disagreements with our position - for (auto& it : peerProposals_) + for (auto const& it : currPeerPositions_) { - if (it.second.position() == ourPosition) + Proposal_t const& peerProp = it.second.proposal(); + if (peerProp.position() == ourPosition) { ++agree; } @@ -1437,11 +1412,11 @@ Consensus::haveConsensus() using std::to_string; JLOG(j_.debug()) << to_string(it.first) << " has " - << to_string(it.second.position()); + << to_string(peerProp.position()); ++disagree; } } - auto currentFinished = impl().proposersFinished(prevLedgerID_); + auto currentFinished = adaptor_.proposersFinished(prevLedgerID_); JLOG(j_.debug()) << "Checking for TX consensus: agree=" << agree << ", disagree=" << disagree; @@ -1454,7 +1429,8 @@ Consensus::haveConsensus() currentFinished, prevRoundTime_, result_->roundTime.read(), - mode_ == Mode::proposing, + adaptor_.parms(), + mode_.get() == ConsensusMode::proposing, j_); if (result_->state == ConsensusState::No) @@ -1471,26 +1447,26 @@ Consensus::haveConsensus() return true; } -template +template void -Consensus::leaveConsensus() +Consensus::leaveConsensus() { - if (mode_ == Mode::proposing) + if (mode_.get() == ConsensusMode::proposing) { if (result_ && !result_->position.isBowOut()) { result_->position.bowOut(now_); - impl().propose(result_->position); + adaptor_.propose(result_->position); } - mode_ = Mode::observing; + mode_.set(ConsensusMode::observing, adaptor_); JLOG(j_.info()) << "Bowing out of consensus"; } } -template +template void -Consensus::createDisputes(TxSet_t const& o) +Consensus::createDisputes(TxSet_t const& o) { // Cannot create disputes without our stance assert(result_); @@ -1529,25 +1505,23 @@ Consensus::createDisputes(TxSet_t const& o) typename Result::Dispute_t dtx{tx, result_->set.exists(txID), j_}; // Update all of the available peer's votes on the disputed transaction - for (auto& pit : peerProposals_) + for (auto const& pit : currPeerPositions_) { - auto cit(acquired_.find(pit.second.position())); - + Proposal_t const& peerProp = pit.second.proposal(); + auto const cit = acquired_.find(peerProp.position()); if (cit != acquired_.end()) dtx.setVote(pit.first, cit->second.exists(txID)); } - impl().relay(dtx.tx()); + adaptor_.relay(dtx.tx()); result_->disputes.emplace(txID, std::move(dtx)); } JLOG(j_.debug()) << dc << " differences found"; } -template +template void -Consensus::updateDisputes( - NodeID_t const& node, - TxSet_t const& other) +Consensus::updateDisputes(NodeID_t const& node, TxSet_t const& other) { // Cannot updateDisputes without our stance assert(result_); @@ -1564,41 +1538,6 @@ Consensus::updateDisputes( } } -template -std::string -Consensus::to_string(Phase p) -{ - switch (p) - { - case Phase::open: - return "open"; - case Phase::establish: - return "establish"; - case Phase::accepted: - return "accepted"; - default: - return "unknown"; - } -} - -template -std::string -Consensus::to_string(Mode m) -{ - switch (m) - { - case Mode::proposing: - return "proposing"; - case Mode::observing: - return "observing"; - case Mode::wrongLedger: - return "wrongLedger"; - case Mode::switchedLedger: - return "switchedLedger"; - default: - return "unknown"; - } -} -} // ripple +} // namespace ripple #endif diff --git a/src/ripple/consensus/ConsensusParms.h b/src/ripple/consensus/ConsensusParms.h new file mode 100644 index 0000000000..02ed12a61f --- /dev/null +++ b/src/ripple/consensus/ConsensusParms.h @@ -0,0 +1,135 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012-2017 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_CONSENSUS_CONSENSUS_PARMS_H_INCLUDED +#define RIPPLE_CONSENSUS_CONSENSUS_PARMS_H_INCLUDED + +#include +#include + +namespace ripple { + +using namespace std::chrono_literals; + +/** Consensus algorithm parameters + + Parameters which control the consensus algorithm. This are not + meant to be changed arbitrarily. +*/ +struct ConsensusParms +{ + + //------------------------------------------------------------------------- + // Validation and proposal durations are relative to NetClock times, so use + // second resolution + /** The duration a validation remains current after its ledger's + close time. + + This is a safety to protect against very old validations and the time + it takes to adjust the close time accuracy window. + */ + std::chrono::seconds validationVALID_WALL = 5min; + + /** Duration a validation remains current after first observed. + + The duration a validation remains current after the time we + first saw it. This provides faster recovery in very rare cases where the + number of validations produced by the network is lower than normal + */ + std::chrono::seconds validationVALID_LOCAL = 3min; + + /** Duration pre-close in which validations are acceptable. + + The number of seconds before a close time that we consider a validation + acceptable. This protects against extreme clock errors + */ + std::chrono::seconds validationVALID_EARLY = 3min; + + + //! How long we consider a proposal fresh + std::chrono::seconds proposeFRESHNESS = 20s; + + //! How often we force generating a new proposal to keep ours fresh + std::chrono::seconds proposeINTERVAL = 12s; + + + //------------------------------------------------------------------------- + // Consensus durations are relative to the internal Consenus clock and use + // millisecond resolution. + + //! The percentage threshold above which we can declare consensus. + std::size_t minCONSENSUS_PCT = 80; + + //! The duration a ledger may remain idle before closing + std::chrono::milliseconds ledgerIDLE_INTERVAL = 15s; + + //! The number of seconds we wait minimum to ensure participation + std::chrono::milliseconds ledgerMIN_CONSENSUS = 1950ms; + + //! Minimum number of seconds to wait to ensure others have computed the LCL + std::chrono::milliseconds ledgerMIN_CLOSE = 2s; + + //! How often we check state or change positions + std::chrono::milliseconds ledgerGRANULARITY = 1s; + + /** The minimum amount of time to consider the previous round + to have taken. + + The minimum amount of time to consider the previous round + to have taken. This ensures that there is an opportunity + for a round at each avalanche threshold even if the + previous consensus was very fast. This should be at least + twice the interval between proposals (0.7s) divided by + the interval between mid and late consensus ([85-50]/100). + */ + std::chrono::milliseconds avMIN_CONSENSUS_TIME = 5s; + + //------------------------------------------------------------------------------ + // Avalanche tuning + // As a function of the percent this round's duration is of the prior round, + // we increase the threshold for yes vots to add a tranasaction to our + // position. + + //! Percentage of nodes on our UNL that must vote yes + std::size_t avINIT_CONSENSUS_PCT = 50; + + //! Percentage of previous round duration before we advance + std::size_t avMID_CONSENSUS_TIME = 50; + + //! Percentage of nodes that most vote yes after advancing + std::size_t avMID_CONSENSUS_PCT = 65; + + //! Percentage of previous round duration before we advance + std::size_t avLATE_CONSENSUS_TIME = 85; + + //! Percentage of nodes that most vote yes after advancing + std::size_t avLATE_CONSENSUS_PCT = 70; + + //! Percentage of previous round duration before we are stuck + std::size_t avSTUCK_CONSENSUS_TIME = 200; + + //! Percentage of nodes that must vote yes after we are stuck + std::size_t avSTUCK_CONSENSUS_PCT = 95; + + //! Percentage of nodes required to reach agreement on ledger close time + std::size_t avCT_CONSENSUS_PCT = 75; +}; + +} // ripple +#endif diff --git a/src/ripple/consensus/ConsensusTypes.h b/src/ripple/consensus/ConsensusTypes.h new file mode 100644 index 0000000000..c570b4fded --- /dev/null +++ b/src/ripple/consensus/ConsensusTypes.h @@ -0,0 +1,242 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012-2017 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_CONSENSUS_CONSENSUS_TYPES_H_INCLUDED +#define RIPPLE_CONSENSUS_CONSENSUS_TYPES_H_INCLUDED + +#include +#include +#include +#include +#include + +namespace ripple { + +/** Represents how a node currently participates in Consensus. + + A node participates in consensus in varying modes, depending on how + the node was configured by its operator and how well it stays in sync + with the network during consensus. + + @code + proposing observing + \ / + \---> wrongLedger <---/ + ^ + | + | + v + switchedLedger + @endcode + + We enter the round proposing or observing. If we detect we are working + on the wrong prior ledger, we go to wrongLedger and attempt to acquire + the right one. Once we acquire the right one, we go to the switchedLedger + mode. It is possible we fall behind again and find there is a new better + ledger, moving back and forth between wrongLedger and switchLedger as + we attempt to catch up. +*/ +enum class ConsensusMode { + //! We are normal participant in consensus and propose our position + proposing, + //! We are observing peer positions, but not proposing our position + observing, + //! We have the wrong ledger and are attempting to acquire it + wrongLedger, + //! We switched ledgers since we started this consensus round but are now + //! running on what we believe is the correct ledger. This mode is as + //! if we entered the round observing, but is used to indicate we did + //! have the wrongLedger at some point. + switchedLedger +}; + +inline std::string +to_string(ConsensusMode m) +{ + switch (m) + { + case ConsensusMode::proposing: + return "proposing"; + case ConsensusMode::observing: + return "observing"; + case ConsensusMode::wrongLedger: + return "wrongLedger"; + case ConsensusMode::switchedLedger: + return "switchedLedger"; + default: + return "unknown"; + } +} + +/** Phases of consensus for a single ledger round. + + @code + "close" "accept" + open ------- > establish ---------> accepted + ^ | | + |---------------| | + ^ "startRound" | + |------------------------------------| + @endcode + + The typical transition goes from open to establish to accepted and + then a call to startRound begins the process anew. However, if a wrong prior + ledger is detected and recovered during the establish or accept phase, + consensus will internally go back to open (see Consensus::handleWrongLedger). +*/ +enum class ConsensusPhase { + //! We haven't closed our ledger yet, but others might have + open, + + //! Establishing consensus by exchanging proposals with our peers + establish, + + //! We have accepted a new last closed ledger and are waiting on a call + //! to startRound to begin the next consensus round. No changes + //! to consensus phase occur while in this phase. + accepted, +}; + +inline std::string +to_string(ConsensusPhase p) +{ + switch (p) + { + case ConsensusPhase::open: + return "open"; + case ConsensusPhase::establish: + return "establish"; + case ConsensusPhase::accepted: + return "accepted"; + default: + return "unknown"; + } +} + +/** Measures the duration of phases of consensus + */ +class ConsensusTimer +{ + using time_point = std::chrono::steady_clock::time_point; + time_point start_; + std::chrono::milliseconds dur_; + +public: + std::chrono::milliseconds + read() const + { + return dur_; + } + + void + tick(std::chrono::milliseconds fixed) + { + dur_ += fixed; + } + + void + reset(time_point tp) + { + start_ = tp; + dur_ = std::chrono::milliseconds{0}; + } + + void + tick(time_point tp) + { + using namespace std::chrono; + dur_ = duration_cast(tp - start_); + } +}; + +/** Stores the set of initial close times + + The initial consensus proposal from each peer has that peer's view of + when the ledger closed. This object stores all those close times for + analysis of clock drift between peerss. +*/ +struct ConsensusCloseTimes +{ + //! Close time estimates, keep ordered for predictable traverse + std::map peers; + + //! Our close time estimate + NetClock::time_point self; +}; + +/** Whether we have or don't have a consensus */ +enum class ConsensusState { + No, //!< We do not have consensus + MovedOn, //!< The network has consensus without us + Yes //!< We have consensus along with the network +}; + +/** Encapsulates the result of consensus. + + Stores all relevant data for the outcome of consensus on a single + ledger. + + @tparam Traits Traits class defining the concrete consensus types used + by the application. +*/ +template +struct ConsensusResult +{ + using Ledger_t = typename Traits::Ledger_t; + using TxSet_t = typename Traits::TxSet_t; + using NodeID_t = typename Traits::NodeID_t; + + using Tx_t = typename TxSet_t::Tx; + using Proposal_t = ConsensusProposal< + NodeID_t, + typename Ledger_t::ID, + typename TxSet_t::ID>; + using Dispute_t = DisputedTx; + + ConsensusResult(TxSet_t&& s, Proposal_t&& p) + : set{std::move(s)}, position{std::move(p)} + { + assert(set.id() == position.position()); + } + + //! The set of transactions consensus agrees go in the ledger + TxSet_t set; + + //! Our proposed position on transactions/close time + Proposal_t position; + + //! Transactions which are under dispute with our peers + hash_map disputes; + + // Set of TxSet ids we have already compared/created disputes + hash_set compares; + + // Measures the duration of the establish phase for this consensus round + ConsensusTimer roundTime; + + // Indicates state in which consensus ended. Once in the accept phase + // will be either Yes or MovedOn + ConsensusState state = ConsensusState::No; + + // The number of peers proposing during the round + std::size_t proposers = 0; +}; +} // namespace ripple + +#endif diff --git a/src/ripple/consensus/DisputedTx.h b/src/ripple/consensus/DisputedTx.h index 41e3d8ebe0..c0ef35f956 100644 --- a/src/ripple/consensus/DisputedTx.h +++ b/src/ripple/consensus/DisputedTx.h @@ -23,7 +23,7 @@ #include #include #include -#include +#include #include #include #include @@ -112,10 +112,11 @@ public: @param percentTime Percentage progress through consensus, e.g. 50% through or 90%. @param proposing Whether we are proposing to our peers in this round. + @param p Consensus parameters controlling thresholds for voting @return Whether our vote changed */ bool - updateVote(int percentTime, bool proposing); + updateVote(int percentTime, bool proposing, ConsensusParms const& p); //! JSON representation of dispute, used for debugging Json::Value @@ -190,7 +191,10 @@ DisputedTx::unVote(NodeID_t const& peer) template bool -DisputedTx::updateVote(int percentTime, bool proposing) +DisputedTx::updateVote( + int percentTime, + bool proposing, + ConsensusParms const& p) { if (ourVote_ && (nays_ == 0)) return false; @@ -212,14 +216,14 @@ DisputedTx::updateVote(int percentTime, bool proposing) // // To prevent avalanche stalls, we increase the needed weight slightly // over time. - if (percentTime < AV_MID_CONSENSUS_TIME) - newPosition = weight > AV_INIT_CONSENSUS_PCT; - else if (percentTime < AV_LATE_CONSENSUS_TIME) - newPosition = weight > AV_MID_CONSENSUS_PCT; - else if (percentTime < AV_STUCK_CONSENSUS_TIME) - newPosition = weight > AV_LATE_CONSENSUS_PCT; + if (percentTime < p.avMID_CONSENSUS_TIME) + newPosition = weight > p.avINIT_CONSENSUS_PCT; + else if (percentTime < p.avLATE_CONSENSUS_TIME) + newPosition = weight > p.avMID_CONSENSUS_PCT; + else if (percentTime < p.avSTUCK_CONSENSUS_TIME) + newPosition = weight > p.avLATE_CONSENSUS_PCT; else - newPosition = weight > AV_STUCK_CONSENSUS_PCT; + newPosition = weight > p.avSTUCK_CONSENSUS_PCT; } else { diff --git a/src/ripple/consensus/LedgerTiming.h b/src/ripple/consensus/LedgerTiming.h index db135031b0..08552c347d 100644 --- a/src/ripple/consensus/LedgerTiming.h +++ b/src/ripple/consensus/LedgerTiming.h @@ -27,15 +27,9 @@ namespace ripple { -//------------------------------------------------------------------------------ -// These are protocol parameters used to control the behavior of the system and -// they should not be changed arbitrarily. - -//! The percentage threshold above which we can declare consensus. -auto constexpr minimumConsensusPercentage = 80; - using namespace std::chrono_literals; -/** Possible close time resolutions. + +/** Possible ledger close time resolutions. Values should not be duplicated. @see getNextLedgerTimeResolution @@ -52,84 +46,6 @@ auto constexpr increaseLedgerTimeResolutionEvery = 8; //! How often we decrease the close time resolution (in numbers of ledgers) auto constexpr decreaseLedgerTimeResolutionEvery = 1; -//! The number of seconds a ledger may remain idle before closing -auto constexpr LEDGER_IDLE_INTERVAL = 15s; - -/** The number of seconds a validation remains current after its ledger's close - time. - - This is a safety to protect against very old validations and the time - it takes to adjust the close time accuracy window. -*/ -auto constexpr VALIDATION_VALID_WALL = 5min; - -/** Duration a validation remains current after first observed. - - The number of seconds a validation remains current after the time we first - saw it. This provides faster recovery in very rare cases where the number - of validations produced by the network is lower than normal -*/ -auto constexpr VALIDATION_VALID_LOCAL = 3min; - -/** Duration pre-close in which validations are acceptable. - - The number of seconds before a close time that we consider a validation - acceptable. This protects against extreme clock errors -*/ -auto constexpr VALIDATION_VALID_EARLY = 3min; - -//! The number of seconds we wait minimum to ensure participation -auto constexpr LEDGER_MIN_CONSENSUS = 1950ms; - -//! Minimum number of seconds to wait to ensure others have computed the LCL -auto constexpr LEDGER_MIN_CLOSE = 2s; - -//! How often we check state or change positions -auto constexpr LEDGER_GRANULARITY = 1s; - -//! How long we consider a proposal fresh -auto constexpr PROPOSE_FRESHNESS = 20s; - -//! How often we force generating a new proposal to keep ours fresh -auto constexpr PROPOSE_INTERVAL = 12s; - -//------------------------------------------------------------------------------ -// Avalanche tuning -//! Percentage of nodes on our UNL that must vote yes -auto constexpr AV_INIT_CONSENSUS_PCT = 50; - -//! Percentage of previous close time before we advance -auto constexpr AV_MID_CONSENSUS_TIME = 50; - -//! Percentage of nodes that most vote yes after advancing -auto constexpr AV_MID_CONSENSUS_PCT = 65; - -//! Percentage of previous close time before we advance -auto constexpr AV_LATE_CONSENSUS_TIME = 85; - -//! Percentage of nodes that most vote yes after advancing -auto constexpr AV_LATE_CONSENSUS_PCT = 70; - -//! Percentage of previous close time before we are stuck -auto constexpr AV_STUCK_CONSENSUS_TIME = 200; - -//! Percentage of nodes that must vote yes after we are stuck -auto constexpr AV_STUCK_CONSENSUS_PCT = 95; - -//! Percentage of nodes required to reach agreement on ledger close time -auto constexpr AV_CT_CONSENSUS_PCT = 75; - -/** The minimum amount of time to consider the previous round - to have taken. - - The minimum amount of time to consider the previous round - to have taken. This ensures that there is an opportunity - for a round at each avalanche threshold even if the - previous consensus was very fast. This should be at least - twice the interval between proposals (0.7s) divided by - the interval between mid and late consensus ([85-50]/100). -*/ -auto constexpr AV_MIN_CONSENSUS_TIME = 5s; /** Calculates the close time resolution for the specified ledger. @@ -223,6 +139,8 @@ effCloseTime( typename time_point::duration const resolution, time_point priorCloseTime) { + using namespace std::chrono_literals; + if (closeTime == time_point{}) return closeTime; @@ -230,78 +148,5 @@ effCloseTime( roundCloseTime(closeTime, resolution), (priorCloseTime + 1s)); } -/** Determines whether the current ledger should close at this time. - - This function should be called when a ledger is open and there is no close - in progress, or when a transaction is received and no close is in progress. - - @param anyTransactions indicates whether any transactions have been received - @param prevProposers proposers in the last closing - @param proposersClosed proposers who have currently closed this ledger - @param proposersValidated proposers who have validated the last closed - ledger - @param prevRoundTime time for the previous ledger to reach consensus - @param timeSincePrevClose time since the previous ledger's (possibly rounded) - close time - @param openTime duration this ledger has been open - @param idleInterval the network's desired idle interval - @param j journal for logging -*/ -bool -shouldCloseLedger( - bool anyTransactions, - std::size_t prevProposers, - std::size_t proposersClosed, - std::size_t proposersValidated, - std::chrono::milliseconds prevRoundTime, - std::chrono::milliseconds timeSincePrevClose, - std::chrono::milliseconds openTime, - std::chrono::seconds idleInterval, - beast::Journal j); - -/** Determine if a consensus has been reached - - This function determines if a consensus has been reached - - @param agreeing count of agreements with our position - @param total count of participants other than us - @param count_self whether we count ourselves - @return True if a consensus has been reached -*/ -bool -checkConsensusReached(std::size_t agreeing, std::size_t total, bool count_self); - -/** Whether we have or don't have a consensus */ -enum class ConsensusState { - No, //!< We do not have consensus - MovedOn, //!< The network has consensus without us - Yes //!< We have consensus along with the network -}; - -/** Determine whether the network reached consensus and whether we joined. - - @param prevProposers proposers in the last closing (not including us) - @param currentProposers proposers in this closing so far (not including us) - @param currentAgree proposers who agree with us - @param currentFinished proposers who have validated a ledger after this one - @param previousAgreeTime how long, in milliseconds, it took to agree on the - last ledger - @param currentAgreeTime how long, in milliseconds, we've been trying to - agree - @param proposing whether we should count ourselves - @param j journal for logging -*/ -ConsensusState -checkConsensus( - std::size_t prevProposers, - std::size_t currentProposers, - std::size_t currentAgree, - std::size_t currentFinished, - std::chrono::milliseconds previousAgreeTime, - std::chrono::milliseconds currentAgreeTime, - bool proposing, - beast::Journal j); - -} // ripple - +} #endif diff --git a/src/ripple/consensus/Validations.h b/src/ripple/consensus/Validations.h new file mode 100644 index 0000000000..cd1688bf44 --- /dev/null +++ b/src/ripple/consensus/Validations.h @@ -0,0 +1,725 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012-2017 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_CONSENSUS_VALIDATIONS_H_INCLUDED +#define RIPPLE_CONSENSUS_VALIDATIONS_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { + +/** Timing parameters to control validation staleness and expiration. + + @note These are protocol level parameters that should not be changed without + careful consideration. They are *not* implemented as static constexpr + to allow simulation code to test alternate parameter settings. + */ +struct ValidationParms +{ + /** The number of seconds a validation remains current after its ledger's + close time. + + This is a safety to protect against very old validations and the time + it takes to adjust the close time accuracy window. + */ + std::chrono::seconds validationCURRENT_WALL = std::chrono::minutes{5}; + + /** Duration a validation remains current after first observed. + + The number of seconds a validation remains current after the time we + first saw it. This provides faster recovery in very rare cases where the + number of validations produced by the network is lower than normal + */ + std::chrono::seconds validationCURRENT_LOCAL = std::chrono::minutes{3}; + + /** Duration pre-close in which validations are acceptable. + + The number of seconds before a close time that we consider a validation + acceptable. This protects against extreme clock errors + */ + std::chrono::seconds validationCURRENT_EARLY = std::chrono::minutes{3}; + + /** Duration a set of validations for a given ledger hash remain valid + + The number of seconds before a set of validations for a given ledger + hash can expire. This keeps validations for recent ledgers available + for a reasonable interval. + */ + std::chrono::seconds validationSET_EXPIRES = std::chrono::minutes{10}; +}; + +/** Whether a validation is still current + + Determines whether a validation can still be considered the current + validation from a node based on when it was signed by that node and first + seen by this node. + + @param p ValidationParms with timing parameters + @param now Current time + @param signTime When the validation was signed + @param seenTime When the validation was first seen locally +*/ +inline bool +isCurrent( + ValidationParms const& p, + NetClock::time_point now, + NetClock::time_point signTime, + NetClock::time_point seenTime) +{ + // Because this can be called on untrusted, possibly + // malicious validations, we do our math in a way + // that avoids any chance of overflowing or underflowing + // the signing time. + + return (signTime > (now - p.validationCURRENT_EARLY)) && + (signTime < (now + p.validationCURRENT_WALL)) && + ((seenTime == NetClock::time_point{}) || + (seenTime < (now + p.validationCURRENT_LOCAL))); +} + +/** Maintains current and recent ledger validations. + + Manages storage and queries related to validations received on the network. + Stores the most current validation from nodes and sets of recent + validations grouped by ledger identifier. + + Stored validations are not necessarily from trusted nodes, so clients + and implementations should take care to use `trusted` member functions or + check the validation's trusted status. + + This class uses a policy design to allow adapting the handling of stale + validations in various circumstances. Below is a set of stubs illustrating + the required type interface. + + @warning The MutexType is used to manage concurrent access to private + members of Validations but does not manage any data in the + StalePolicy instance. + + @code + + // Identifier types that should be equality-comparable and copyable + struct LedgerID; + struct NodeID; + struct NodeKey; + + struct Validation + { + // Ledger ID associated with this validation + LedgerID ledgerID() const; + + // Sequence number of validation's ledger (0 means no sequence number) + std::uint32_t seq() const + + // When the validation was signed + NetClock::time_point signTime() const; + + // When the validation was first observed by this node + NetClock::time_point seenTime() const; + + // Signing key of node that published the validation + NodeKey key() const; + + // Identifier of node that published the validation + NodeID nodeID() const; + + // Whether the publishing node was trusted at the time the validation + // arrived + bool trusted() const; + + implementation_specific_t + unwrap() -> return the implementation-specific type being wrapped + + // ... implementation specific + }; + + class StalePolicy + { + // Handle a newly stale validation, this should do minimal work since + // it is called by Validations while it may be iterating Validations + // under lock + void onStale(Validation && ); + + // Flush the remaining validations (typically done on shutdown) + void flush(hash_map && remaining); + + // Return the current network time (used to determine staleness) + NetClock::time_point now() const; + + // ... implementation specific + }; + @endcode + + @tparam StalePolicy Determines how to determine and handle stale validations + @tparam Validation Conforming type representing a ledger validation + @tparam MutexType Mutex used to manage concurrent access + +*/ +template +class Validations +{ + template + using decay_result_t = std::decay_t>; + + using WrappedValidationType = + decay_result_t; + using LedgerID = + decay_result_t; + using NodeKey = decay_result_t; + using NodeID = decay_result_t; + + + using ScopedLock = std::lock_guard; + + // Manages concurrent access to current_ and byLedger_ + MutexType mutex_; + + //! For the most recent validation, we also want to store the ID + //! of the ledger it replaces + struct ValidationAndPrevID + { + ValidationAndPrevID(Validation const& v) : val{v}, prevLedgerID{0} + { + } + + Validation val; + LedgerID prevLedgerID; + }; + + //! The latest validation from each node + hash_map current_; + + //! Recent validations from nodes, indexed by ledger identifier + beast::aged_unordered_map< + LedgerID, + hash_map, + std::chrono::steady_clock, + beast::uhash<>> + byLedger_; + + //! Parameters to determine validation staleness + ValidationParms const parms_; + + beast::Journal j_; + + //! StalePolicy details providing now(), onStale() and flush() callbacks + //! Is NOT managed by the mutex_ above + StalePolicy stalePolicy_; + +private: + /** Iterate current validations. + + Iterate current validations, optionally removing any stale validations + if a time is specified. + + @param t (Optional) Time used to determine staleness + @param pre Invokable with signature (std::size_t) called prior to + looping. + @param f Invokable with signature (NodeKey const &, Validations const &) + for each current validation. + + @note The invokable `pre` is called _prior_ to checking for staleness + and reflects an upper-bound on the number of calls to `f. + @warning The invokable `f` is expected to be a simple transformation of + its arguments and will be called with mutex_ under lock. + */ + + template + void + current(boost::optional t, Pre&& pre, F&& f) + { + ScopedLock lock{mutex_}; + pre(current_.size()); + auto it = current_.begin(); + while (it != current_.end()) + { + // Check for staleness, if time specified + if (t && + !isCurrent( + parms_, *t, it->second.val.signTime(), it->second.val.seenTime())) + { + // contains a stale record + stalePolicy_.onStale(std::move(it->second.val)); + it = current_.erase(it); + } + else + { + auto cit = typename decltype(current_)::const_iterator{it}; + // contains a live record + f(cit->first, cit->second); + ++it; + } + } + } + + /** Iterate the set of validations associated with a given ledger id + + @param ledgerID The identifier of the ledger + @param pre Invokable with signature(std::size_t) + @param f Invokable with signature (NodeKey const &, Validation const &) + + @note The invokable `pre` is called prior to iterating validations. The + argument is the number of times `f` will be called. + @warning The invokable f is expected to be a simple transformation of + its arguments and will be called with mutex_ under lock. + */ + template + void + byLedger(LedgerID const& ledgerID, Pre&& pre, F&& f) + { + ScopedLock lock{mutex_}; + auto it = byLedger_.find(ledgerID); + if (it != byLedger_.end()) + { + // Update set time since it is being used + byLedger_.touch(it); + pre(it->second.size()); + for (auto const& keyVal : it->second) + f(keyVal.first, keyVal.second); + } + } + +public: + /** Constructor + + @param p ValidationParms to control staleness/expiration of validaitons + @param c Clock to use for expiring validations stored by ledger + @param j Journal used for logging + @param ts Parameters for constructing StalePolicy instance + */ + template + Validations( + ValidationParms const& p, + beast::abstract_clock& c, + beast::Journal j, + Ts&&... ts) + : byLedger_(c), parms_(p), j_(j), stalePolicy_(std::forward(ts)...) + { + } + + /** Return the validation timing parameters + */ + ValidationParms const& + parms() const + { + return parms_; + } + + /** Return the journal + */ + beast::Journal + journal() const + { + return j_; + } + + /** Result of adding a new validation + */ + enum class AddOutcome { + /// This was a new validation and was added + current, + /// Already had this validation + repeat, + /// Not current or was older than current from this node + stale, + /// Had a validation with same sequence number + sameSeq, + }; + + /** Add a new validation + + Attempt to add a new validation. + + @param key The NodeKey to use for the validation + @param val The validation to store + @return The outcome of the attempt + + @note The provided key may differ from the validation's + key() member since we might be storing by master key and the + validation might be signed by a temporary or rotating key. + + */ + AddOutcome + add(NodeKey const& key, Validation const& val) + { + NetClock::time_point t = stalePolicy_.now(); + if (!isCurrent(parms_, t, val.signTime(), val.seenTime())) + return AddOutcome::stale; + + LedgerID const& id = val.ledgerID(); + + // This is only seated if a validation became stale + boost::optional maybeStaleValidation; + + AddOutcome result = AddOutcome::current; + + { + ScopedLock lock{mutex_}; + + auto const ret = byLedger_[id].emplace(key, val); + + // This validation is a repeat if we already have + // one with the same id and signing key. + if (!ret.second && ret.first->second.key() == val.key()) + return AddOutcome::repeat; + + // Attempt to insert + auto const ins = current_.emplace(key, val); + + if (!ins.second) + { + // Had a previous validation from the node, consider updating + Validation& oldVal = ins.first->second.val; + LedgerID const previousLedgerID = ins.first->second.prevLedgerID; + + std::uint32_t const oldSeq{oldVal.seq()}; + std::uint32_t const newSeq{val.seq()}; + + // Sequence of 0 indicates a missing sequence number + if (oldSeq && newSeq && oldSeq == newSeq) + { + result = AddOutcome::sameSeq; + + // If the validation key was revoked, update the + // existing validation in the byLedger_ set + if (val.key() != oldVal.key()) + { + auto const mapIt = byLedger_.find(oldVal.ledgerID()); + if (mapIt != byLedger_.end()) + { + auto& validationMap = mapIt->second; + // If a new validation with the same ID was + // reissued we simply replace. + if(oldVal.ledgerID() == val.ledgerID()) + { + auto replaceRes = validationMap.emplace(key, val); + // If it was already there, replace + if(!replaceRes.second) + replaceRes.first->second = val; + } + else + { + // If the new validation has a different ID, + // we remove the old. + validationMap.erase(key); + // Erase the set if it is now empty + if (validationMap.empty()) + byLedger_.erase(mapIt); + } + } + } + } + + if (val.signTime() > oldVal.signTime() || + val.key() != oldVal.key()) + { + // This is either a newer validation or a new signing key + LedgerID const prevID = [&]() { + // In the normal case, the prevID is the ID of the + // ledger we replace + if (oldVal.ledgerID() != val.ledgerID()) + return oldVal.ledgerID(); + // In the case the key was revoked and a new validation + // for the same ledger ID was sent, the previous ledger + // is still the one the now revoked validation had + return previousLedgerID; + }(); + + // Allow impl to take over oldVal + maybeStaleValidation.emplace(std::move(oldVal)); + // Replace old val in the map and set the previous ledger ID + ins.first->second.val = val; + ins.first->second.prevLedgerID = prevID; + } + else + { + // We already have a newer validation from this source + result = AddOutcome::stale; + } + } + } + + // Handle the newly stale validation outside the lock + if (maybeStaleValidation) + { + stalePolicy_.onStale(std::move(*maybeStaleValidation)); + } + + return result; + } + + /** Expire old validation sets + + Remove validation sets that were accessed more than + validationSET_EXPIRES ago. + */ + void + expire() + { + ScopedLock lock{mutex_}; + beast::expire(byLedger_, parms_.validationSET_EXPIRES); + } + + /** Distribution of current trusted validations + + Calculates the distribution of current validations but allows + ledgers one away from the current ledger to count as the current. + + @param currentLedger The identifier of the ledger we believe is current + @param priorLedger The identifier of our previous current ledger + @param cutoffBefore Ignore ledgers with sequence number before this + + @return Map representing the distribution of ledgerID by count + */ + hash_map + currentTrustedDistribution( + LedgerID const& currentLedger, + LedgerID const& priorLedger, + std::uint32_t cutoffBefore) + { + bool const valCurrentLedger = currentLedger != beast::zero; + bool const valPriorLedger = priorLedger != beast::zero; + + hash_map ret; + + current( + stalePolicy_.now(), + // The number of validations does not correspond to the number of + // distinct ledgerIDs so we do not call reserve on ret. + [](std::size_t) {}, + [this, + &cutoffBefore, + ¤tLedger, + &valCurrentLedger, + &valPriorLedger, + &priorLedger, + &ret](NodeKey const&, ValidationAndPrevID const& vp) { + Validation const& v = vp.val; + LedgerID const& prevLedgerID = vp.prevLedgerID; + if (!v.trusted()) + return; + + std::uint32_t const seq = v.seq(); + if ((seq == 0) || (seq >= cutoffBefore)) + { + // contains a live record + bool countPreferred = + valCurrentLedger && (v.ledgerID() == currentLedger); + + if (!countPreferred && // allow up to one ledger slip in + // either direction + ((valCurrentLedger && + (prevLedgerID == currentLedger)) || + (valPriorLedger && (v.ledgerID() == priorLedger)))) + { + countPreferred = true; + JLOG(this->j_.trace()) << "Counting for " << currentLedger + << " not " << v.ledgerID(); + } + + if (countPreferred) + ret[currentLedger]++; + else + ret[v.ledgerID()]++; + } + }); + + return ret; + } + + /** Count the number of current trusted validators working on the next + ledger. + + Counts the number of current trusted validations that replaced the + provided ledger. Does not check or update staleness of the validations. + + @param ledgerID The identifier of the preceding ledger of interest + @return The number of current trusted validators with ledgerID as the + prior ledger. + */ + std::size_t + getNodesAfter(LedgerID const& ledgerID) + { + std::size_t count = 0; + + // Historically this did not not check for stale validations + // That may not be important, but this preserves the behavior + current( + boost::none, + [&](std::size_t) {}, // nothing to reserve + [&](NodeKey const&, ValidationAndPrevID const& v) { + if (v.val.trusted() && v.prevLedgerID == ledgerID) + ++count; + }); + return count; + } + + /** Get the currently trusted validations + + @return Vector of validations from currently trusted validators + */ + std::vector + currentTrusted() + { + std::vector ret; + + current( + stalePolicy_.now(), + [&](std::size_t numValidations) { ret.reserve(numValidations); }, + [&](NodeKey const&, ValidationAndPrevID const& v) { + if (v.val.trusted()) + ret.push_back(v.val.unwrap()); + }); + return ret; + } + + /** Get the set of known public keys associated with current validations + + @return The set of of knowns keys for current trusted and untrusted + validations + */ + hash_set + getCurrentPublicKeys() + { + hash_set ret; + current( + stalePolicy_.now(), + [&](std::size_t numValidations) { ret.reserve(numValidations); }, + [&](NodeKey const& k, ValidationAndPrevID const&) { ret.insert(k); }); + + return ret; + } + + /** Count the number of trusted validations for the given ledger + + @param ledgerID The identifier of ledger of interest + @return The number of trusted validations + */ + std::size_t + numTrustedForLedger(LedgerID const& ledgerID) + { + std::size_t count = 0; + byLedger( + ledgerID, + [&](std::size_t) {}, // nothing to reserve + [&](NodeKey const&, Validation const& v) { + if (v.trusted()) + ++count; + }); + return count; + } + + /** Get set of trusted validations associated with a given ledger + + @param ledgerID The identifier of ledger of interest + @return Trusted validations associated with ledger + */ + std::vector + getTrustedForLedger(LedgerID const& ledgerID) + { + std::vector res; + byLedger( + ledgerID, + [&](std::size_t numValidations) { res.reserve(numValidations); }, + [&](NodeKey const&, Validation const& v) { + if (v.trusted()) + res.emplace_back(v.unwrap()); + }); + + return res; + } + + /** Return the sign times of all validations associated with a given ledger + + @param ledgerID The identifier of ledger of interest + @return Vector of times + */ + std::vector + getTrustedValidationTimes(LedgerID const& ledgerID) + { + std::vector times; + byLedger( + ledgerID, + [&](std::size_t numValidations) { times.reserve(numValidations); }, + [&](NodeKey const&, Validation const& v) { + if (v.trusted()) + times.emplace_back(v.signTime()); + }); + return times; + } + + /** Returns fees reported by trusted validators in the given ledger + + @param ledgerID The identifier of ledger of interest + @param baseFee The fee to report if not present in the validation + @return Vector of fees + */ + std::vector + fees(LedgerID const& ledgerID, std::uint32_t baseFee) + { + std::vector res; + byLedger( + ledgerID, + [&](std::size_t numValidations) { res.reserve(numValidations); }, + [&](NodeKey const&, Validation const& v) { + if (v.trusted()) + { + boost::optional loadFee = v.loadFee(); + if (loadFee) + res.push_back(*loadFee); + else + res.push_back(baseFee); + } + }); + return res; + } + + /** Flush all current validations + */ + void + flush() + { + JLOG(j_.info()) << "Flushing validations"; + + hash_map flushed; + { + ScopedLock lock{mutex_}; + for (auto it : current_) + { + flushed.emplace(it.first, std::move(it.second.val)); + } + current_.clear(); + } + + stalePolicy_.flush(std::move(flushed)); + + JLOG(j_.debug()) << "Validations flushed"; + } +}; +} // namespace ripple +#endif diff --git a/src/ripple/core/ClosureCounter.h b/src/ripple/core/ClosureCounter.h new file mode 100644 index 0000000000..609ffd2f92 --- /dev/null +++ b/src/ripple/core/ClosureCounter.h @@ -0,0 +1,207 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2017 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_CORE_CLOSURE_COUNTER_H_INCLUDED +#define RIPPLE_CORE_CLOSURE_COUNTER_H_INCLUDED + +#include +#include +#include +#include +#include +#include + +namespace ripple { + +// A class that does reference counting for postponed closures -- a closure +// who's execution is delayed by a timer or queue. The reference counting +// allows a Stoppable to assure that all such postponed closures are +// completed before the Stoppable declares itself stopped(). +// +// Ret_t is the type that the counted closure returns. +// Args_t are the types of the arguments that will be passed to the closure. +template +class ClosureCounter +{ +private: + std::mutex mutable mutex_ {}; + std::condition_variable allClosuresDoneCond_ {}; // guard with mutex_ + bool waitForClosures_ {false}; // guard with mutex_ + std::atomic closureCount_ {0}; + + // Increment the count. + ClosureCounter& operator++() + { + ++closureCount_; + return *this; + } + + // Decrement the count. If we're stopping and the count drops to zero + // notify allClosuresDoneCond_. + ClosureCounter& operator--() + { + // Even though closureCount_ is atomic, we decrement its value under + // a lock. This removes a small timing window that occurs if the + // waiting thread is handling a spurious wakeup when closureCount_ + // drops to zero. + std::lock_guard lock {mutex_}; + + // Update closureCount_. Notify if stopping and closureCount_ == 0. + if ((--closureCount_ == 0) && waitForClosures_) + allClosuresDoneCond_.notify_all(); + return *this; + } + + // A private template class that helps count the number of closures + // in flight. This allows Stoppables to hold off declaring stopped() + // until all their postponed closures are dispatched. + template + class Wrapper + { + private: + ClosureCounter& counter_; + std::remove_reference_t closure_; + + static_assert ( + std::is_same()...)), Ret_t>::value, + "Closure arguments don't match ClosureCounter Ret_t or Args_t"); + + public: + Wrapper() = delete; + + Wrapper (Wrapper const& rhs) + : counter_ (rhs.counter_) + , closure_ (rhs.closure_) + { + ++counter_; + } + + Wrapper (Wrapper&& rhs) + : counter_ (rhs.counter_) + , closure_ (std::move (rhs.closure_)) + { + ++counter_; + } + + Wrapper (ClosureCounter& counter, Closure&& closure) + : counter_ (counter) + , closure_ (std::forward (closure)) + { + ++counter_; + } + + Wrapper& operator=(Wrapper const& rhs) = delete; + Wrapper& operator=(Wrapper&& rhs) = delete; + + ~Wrapper() + { + --counter_; + } + + // Note that Args_t is not deduced, it is explicit. So Args_t&& + // would be an rvalue reference, not a forwarding reference. We + // want to forward exactly what the user declared. + Ret_t operator ()(Args_t... args) + { + return closure_ (std::forward(args)...); + } + }; + +public: + ClosureCounter() = default; + // Not copyable or movable. Outstanding counts would be hard to sort out. + ClosureCounter (ClosureCounter const&) = delete; + + ClosureCounter& operator=(ClosureCounter const&) = delete; + + /** Destructor verifies all in-flight closures are complete. */ + ~ClosureCounter() + { + using namespace std::chrono_literals; + join ("ClosureCounter", 1s, debugLog()); + } + + /** Returns once all counted in-flight closures are destroyed. + + @param name Name reported if join time exceeds wait. + @param wait If join() exceeds this duration report to Journal. + @param j Journal written to if wait is exceeded. + */ + void join (char const* name, + std::chrono::milliseconds wait, beast::Journal j) + { + std::unique_lock lock {mutex_}; + waitForClosures_ = true; + if (closureCount_ > 0) + { + if (! allClosuresDoneCond_.wait_for ( + lock, wait, [this] { return closureCount_ == 0; })) + { + if (auto stream = j.error()) + stream << name + << " waiting for ClosureCounter::join()."; + allClosuresDoneCond_.wait ( + lock, [this] { return closureCount_ == 0; }); + } + } + } + + /** Wrap the passed closure with a reference counter. + + @param closure Closure that accepts Args_t parameters and returns Ret_t. + @return If join() has been called returns boost::none. Otherwise + returns a boost::optional that wraps closure with a + reference counter. + */ + template + boost::optional> + wrap (Closure&& closure) + { + boost::optional> ret; + + std::lock_guard lock {mutex_}; + if (! waitForClosures_) + ret.emplace (*this, std::forward (closure)); + + return ret; + } + + /** Current number of Closures outstanding. Only useful for testing. */ + int count() const + { + return closureCount_; + } + + /** Returns true if this has been joined. + + Even if true is returned, counted closures may still be in flight. + However if (joined() && (count() == 0)) there should be no more + counted closures in flight. + */ + bool joined() const + { + std::lock_guard lock {mutex_}; + return waitForClosures_; + } +}; + +} // ripple + +#endif // RIPPLE_CORE_CLOSURE_COUNTER_H_INCLUDED diff --git a/src/ripple/core/Config.h b/src/ripple/core/Config.h index 8be4e41242..b2d123eabb 100644 --- a/src/ripple/core/Config.h +++ b/src/ripple/core/Config.h @@ -24,7 +24,7 @@ #include #include // VFALCO Breaks levelization #include -#include +#include #include #include // VFALCO FIX: This include should not be here #include // VFALCO FIX: This include should not be here @@ -150,7 +150,7 @@ public: int PATH_SEARCH_MAX = 10; // Validation - boost::optional VALIDATION_QUORUM; // Minimum validations to consider ledger authoritative + boost::optional VALIDATION_QUORUM; // validations to consider ledger authoritative std::uint64_t FEE_DEFAULT = 10; std::uint64_t FEE_ACCOUNT_RESERVE = 200*SYSTEM_CURRENCY_PARTS; diff --git a/src/ripple/core/Coro.ipp b/src/ripple/core/Coro.ipp index 8eeadd32f3..73198e922b 100644 --- a/src/ripple/core/Coro.ipp +++ b/src/ripple/core/Coro.ipp @@ -48,7 +48,9 @@ inline JobQueue::Coro:: ~Coro() { +#ifndef NDEBUG assert(finished_); +#endif } inline @@ -64,7 +66,7 @@ yield() const } inline -void +bool JobQueue::Coro:: post() { @@ -74,23 +76,76 @@ post() } // sp keeps 'this' alive - jq_.addJob(type_, name_, + if (jq_.addJob(type_, name_, [this, sp = shared_from_this()](Job&) { - { - std::lock_guard lock(jq_.m_mutex); - --jq_.nSuspend_; - } - auto saved = detail::getLocalValues().release(); - detail::getLocalValues().reset(&lvs_); - std::lock_guard lock(mutex_); - coro_(); - detail::getLocalValues().release(); - detail::getLocalValues().reset(saved); - std::lock_guard lk(mutex_run_); - running_ = false; - cv_.notify_all(); - }); + resume(); + })) + { + return true; + } + + // The coroutine will not run. Clean up running_. + std::lock_guard lk(mutex_run_); + running_ = false; + cv_.notify_all(); + return false; +} + +inline +void +JobQueue::Coro:: +resume() +{ + { + std::lock_guard lk(mutex_run_); + running_ = true; + } + { + std::lock_guard lock(jq_.m_mutex); + --jq_.nSuspend_; + } + auto saved = detail::getLocalValues().release(); + detail::getLocalValues().reset(&lvs_); + std::lock_guard lock(mutex_); + assert (coro_); + coro_(); + detail::getLocalValues().release(); + detail::getLocalValues().reset(saved); + std::lock_guard lk(mutex_run_); + running_ = false; + cv_.notify_all(); +} + +inline +bool +JobQueue::Coro:: +runnable() const +{ + return static_cast(coro_); +} + +inline +void +JobQueue::Coro:: +expectEarlyExit() +{ +#ifndef NDEBUG + if (! finished_) +#endif + { + // expectEarlyExit() must only ever be called from outside the + // Coro's stack. It you're inside the stack you can simply return + // and be done. + // + // That said, since we're outside the Coro's stack, we need to + // decrement the nSuspend that the Coro's call to yield caused. + std::lock_guard lock(jq_.m_mutex); + --jq_.nSuspend_; +#ifndef NDEBUG + finished_ = true; +#endif + } } inline diff --git a/src/ripple/core/DeadlineTimer.h b/src/ripple/core/DeadlineTimer.h deleted file mode 100644 index a2cbae5a60..0000000000 --- a/src/ripple/core/DeadlineTimer.h +++ /dev/null @@ -1,119 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2013 Ripple Labs Inc. - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ -//============================================================================== - -#ifndef RIPPLE_CORE_DEADLINETIMER_H_INCLUDED -#define RIPPLE_CORE_DEADLINETIMER_H_INCLUDED - -#include -#include - -namespace ripple { - -/** Provides periodic or one time notifications at a specified time interval. -*/ -class DeadlineTimer - : public beast::List ::Node -{ -public: - using clock = std::chrono::steady_clock; ///< DeadlineTimer clock. - using duration = std::chrono::milliseconds; ///< DeadlineTimer duration. - /** DeadlineTimer time_point. */ - using time_point = std::chrono::time_point; - - /** Listener for a deadline timer. - - The listener is called on an auxiliary thread. It is suggested - not to perform any time consuming operations during the call. - */ - // VFALCO TODO Perhaps allow construction using a ServiceQueue to use - // for notifications. - // - class Listener - { - public: - /** Entry point called by DeadlineTimer when a deadline elapses. */ - virtual void onDeadlineTimer (DeadlineTimer&) = 0; - }; - - /** Create a deadline timer with the specified listener attached. - - @param listener pointer to Listener that is called at the deadline. - */ - explicit DeadlineTimer (Listener* listener); - - /// @cond INTERNAL - DeadlineTimer (DeadlineTimer const&) = delete; - DeadlineTimer& operator= (DeadlineTimer const&) = delete; - /// @endcond - - /** Destructor. */ - ~DeadlineTimer (); - - /** Cancel all notifications. - It is okay to call this on an inactive timer. - @note It is guaranteed that no notifications will occur after this - function returns. - */ - void cancel (); - - /** Set the timer to go off once in the future. - If the timer is already active, this will reset it. - @note If the timer is already active, the old one might go off - before this function returns. - @param delay duration until the timer will send a notification. - This must be greater than zero. - */ - void setExpiration (duration delay); - - /** Set the timer to go off repeatedly with the specified period. - If the timer is already active, this will reset it. - @note If the timer is already active, the old one might go off - before this function returns. - @param interval duration until the timer will send a notification. - This must be greater than zero. - */ - void setRecurringExpiration (duration interval); - - /** Equality comparison. - Timers are equal if they have the same address. - */ - inline bool operator== (DeadlineTimer const& other) const - { - return this == &other; - } - - /** Inequality comparison. */ - inline bool operator!= (DeadlineTimer const& other) const - { - return this != &other; - } - -private: - class Manager; - - Listener* const m_listener; - bool m_isActive; - - time_point notificationTime_; - duration recurring_; // > 0ms if recurring. -}; - -} - -#endif diff --git a/src/ripple/core/Job.h b/src/ripple/core/Job.h index e70ebc4d08..c06e463ec1 100644 --- a/src/ripple/core/Job.h +++ b/src/ripple/core/Job.h @@ -21,6 +21,9 @@ #define RIPPLE_CORE_JOB_H_INCLUDED #include +#include + +#include namespace ripple { diff --git a/src/ripple/core/JobCounter.h b/src/ripple/core/JobCounter.h deleted file mode 100644 index 834f3bd677..0000000000 --- a/src/ripple/core/JobCounter.h +++ /dev/null @@ -1,191 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2017 Ripple Labs Inc. - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ -//============================================================================== - -#ifndef RIPPLE_CORE_JOB_COUNTER_H_INCLUDED -#define RIPPLE_CORE_JOB_COUNTER_H_INCLUDED - -#include -#include -#include -#include -#include -#include - -namespace ripple { - -// A class that does reference counting for Jobs. The reference counting -// allows a Stoppable to assure that all child Jobs in the JobQueue are -// completed before the Stoppable declares itself stopped(). -class JobCounter -{ -private: - std::mutex mutable mutex_ {}; - std::condition_variable allJobsDoneCond_ {}; // guard with mutex_ - bool waitForJobs_ {false}; // guard with mutex_ - std::atomic jobCount_ {0}; - - // Increment the count. - JobCounter& operator++() - { - ++jobCount_; - return *this; - } - - // Decrement the count. If we're stopping and the count drops to zero - // notify allJobsDoneCond_. - JobCounter& operator--() - { - // Even though jobCount_ is atomic, we decrement its value under a - // lock. This removes a small timing window that occurs if the - // waiting thread is handling a spurious wakeup when jobCount_ - // drops to zero. - std::lock_guard lock {mutex_}; - - // Update jobCount_. Notify if we're stopping and all jobs are done. - if ((--jobCount_ == 0) && waitForJobs_) - allJobsDoneCond_.notify_all(); - return *this; - } - - // A private template class that helps count the number of Jobs - // in flight. This allows Stoppables to hold off declaring stopped() - // until all their JobQueue Jobs are dispatched. - template - class CountedJob - { - private: - JobCounter& counter_; - JobHandler handler_; - - static_assert ( - std::is_same())), void>::value, - "JobHandler must be callable with Job&"); - - public: - CountedJob() = delete; - - CountedJob (CountedJob const& rhs) - : counter_ (rhs.counter_) - , handler_ (rhs.handler_) - { - ++counter_; - } - - CountedJob (CountedJob&& rhs) - : counter_ (rhs.counter_) - , handler_ (std::move (rhs.handler_)) - { - ++counter_; - } - - CountedJob (JobCounter& counter, JobHandler&& handler) - : counter_ (counter) - , handler_ (std::move (handler)) - { - ++counter_; - } - - CountedJob& operator=(CountedJob const& rhs) = delete; - CountedJob& operator=(CountedJob&& rhs) = delete; - - ~CountedJob() - { - --counter_; - } - - void operator ()(Job& job) - { - return handler_ (job); - } - }; - -public: - JobCounter() = default; - // Not copyable or movable. Outstanding counts would be hard to sort out. - JobCounter (JobCounter const&) = delete; - - JobCounter& operator=(JobCounter const&) = delete; - - /** Destructor verifies all in-flight jobs are complete. */ - ~JobCounter() - { - join(); - } - - /** Returns once all counted in-flight Jobs are destroyed. */ - void join() - { - std::unique_lock lock {mutex_}; - waitForJobs_ = true; - if (jobCount_ > 0) - { - allJobsDoneCond_.wait ( - lock, [this] { return jobCount_ == 0; }); - } - } - - /** Wrap the passed lambda with a reference counter. - - @param handler Lambda that accepts a Job& parameter and returns void. - @return If join() has been called returns boost::none. Otherwise - returns a boost::optional that wraps handler with a - reference counter. - */ - template - boost::optional> - wrap (JobHandler&& handler) - { - // The current intention is that wrap() may only be called with an - // rvalue lambda. That can be adjusted in the future if needed, - // but the following static_assert covers current expectations. - static_assert (std::is_rvalue_reference::value, - "JobCounter::wrap() only supports rvalue lambdas."); - - boost::optional> ret; - - std::lock_guard lock {mutex_}; - if (! waitForJobs_) - ret.emplace (*this, std::move (handler)); - - return ret; - } - - /** Current number of Jobs outstanding. Only useful for testing. */ - int count() const - { - return jobCount_; - } - - /** Returns true if this has been joined. - - Even if true is returned, counted Jobs may still be in flight. - However if (joined() && (count() == 0)) there should be no more - counted Jobs in flight. - */ - bool joined() const - { - std::lock_guard lock {mutex_}; - return waitForJobs_; - } -}; - -} // ripple - -#endif // RIPPLE_CORE_JOB_COUNTER_H_INCLUDED diff --git a/src/ripple/core/JobQueue.h b/src/ripple/core/JobQueue.h index cf2a873197..b5c9228e89 100644 --- a/src/ripple/core/JobQueue.h +++ b/src/ripple/core/JobQueue.h @@ -22,7 +22,6 @@ #include #include -#include #include #include #include @@ -97,9 +96,31 @@ public: When the job runs, the coroutine's stack is restored and execution continues at the beginning of coroutine function or the statement after the previous call to yield. - Undefined behavior if called consecutively without a corresponding yield. + Undefined behavior if called after the coroutine has completed + with a return (as opposed to a yield()). + Undefined behavior if post() or resume() called consecutively + without a corresponding yield. + + @return true if the Coro's job is added to the JobQueue. */ - void post(); + bool post(); + + /** Resume coroutine execution. + Effects: + The coroutine continues execution from where it last left off + using this same thread. + Undefined behavior if called after the coroutine has completed + with a return (as opposed to a yield()). + Undefined behavior if resume() or post() called consecutively + without a corresponding yield. + */ + void resume(); + + /** Returns true if the Coro is still runnable (has not returned). */ + bool runnable() const; + + /** Once called, the Coro allows early exit without an assert. */ + void expectEarlyExit(); /** Waits until coroutine returns from the user function. */ void join(); @@ -113,29 +134,23 @@ public: /** Adds a job to the JobQueue. - @param t The type of job. + @param type The type of job. @param name Name of the job. - @param func std::function with signature void (Job&). Called when the job is executed. - */ - void addJob (JobType type, std::string const& name, JobFunction const& func); - - /** Adds a counted job to the JobQueue. - - @param t The type of job. - @param name Name of the job. - @param counter JobCounter for counting the Job. @param jobHandler Lambda with signature void (Job&). Called when the job is executed. - @return true if JobHandler added, false if JobCounter is already joined. + @return true if jobHandler added to queue. */ - template - bool addCountedJob (JobType type, - std::string const& name, JobCounter& counter, JobHandler&& jobHandler) + template ()(std::declval())), void>::value>> + bool addJob (JobType type, + std::string const& name, JobHandler&& jobHandler) { - if (auto optionalCountedJob = counter.wrap (std::move (jobHandler))) + if (auto optionalCountedJob = + Stoppable::jobCounter().wrap (std::forward(jobHandler))) { - addJob (type, name, std::move (*optionalCountedJob)); - return true; + return addRefCountedJob ( + type, name, std::move (*optionalCountedJob)); } return false; } @@ -145,9 +160,11 @@ public: @param t The type of job. @param name Name of the job. @param f Has a signature of void(std::shared_ptr). Called when the job executes. + + @return shared_ptr to posted Coro. nullptr if post was not successful. */ template - void postCoro (JobType t, std::string const& name, F&& f); + std::shared_ptr postCoro (JobType t, std::string const& name, F&& f); /** Jobs waiting at this priority. */ @@ -227,6 +244,16 @@ private: // Signals the service stopped if the stopped condition is met. void checkStopped (std::lock_guard const& lock); + // Adds a reference counted job to the JobQueue. + // + // param type The type of job. + // param name Name of the job. + // param func std::function with signature void (Job&). Called when the job is executed. + // + // return true if func added to queue. + bool addRefCountedJob ( + JobType type, std::string const& name, JobFunction const& func); + // Signals an added Job for processing. // // Pre-conditions: @@ -311,15 +338,15 @@ private: other requests while the RPC command completes its work asynchronously. postCoro() creates a Coro object. When the Coro ctor is called, and its - coro_ member is initialized(a boost::coroutines::pull_type), execution + coro_ member is initialized (a boost::coroutines::pull_type), execution automatically passes to the coroutine, which we don't want at this point, since we are still in the handler thread context. It's important to note here that construction of a boost pull_type automatically passes execution to the coroutine. A pull_type object automatically generates a push_type that is - used as the as a parameter(do_yield) in the signature of the function the + passed as a parameter (do_yield) in the signature of the function the pull_type was created with. This function is immediately called during coro_ construction and within it, Coro::yield_ is assigned the push_type - parameter(do_yield) address and called(yield()) so we can return execution + parameter (do_yield) address and called (yield()) so we can return execution back to the caller's stack. postCoro() then calls Coro::post(), which schedules a job on the job @@ -368,15 +395,23 @@ private: namespace ripple { template -void JobQueue::postCoro (JobType t, std::string const& name, F&& f) +std::shared_ptr +JobQueue::postCoro (JobType t, std::string const& name, F&& f) { /* First param is a detail type to make construction private. Last param is the function the coroutine runs. Signature of void(std::shared_ptr). */ - auto const coro = std::make_shared( + auto coro = std::make_shared( Coro_create_t{}, *this, t, name, std::forward(f)); - coro->post(); + if (! coro->post()) + { + // The Coro was not successfully posted. Disable it so it's destructor + // can run with no negative side effects. Then destroy it. + coro->expectEarlyExit(); + coro.reset(); + } + return coro; } } diff --git a/src/ripple/core/Stoppable.h b/src/ripple/core/Stoppable.h index fde3eb5912..698e853161 100644 --- a/src/ripple/core/Stoppable.h +++ b/src/ripple/core/Stoppable.h @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include #include #include @@ -30,6 +32,9 @@ namespace ripple { +// Give a reasonable name for the JobCounter +using JobCounter = ClosureCounter; + class RootStoppable; /** Provides an interface for starting and stopping. @@ -167,6 +172,30 @@ class RootStoppable; when the last thread is about to exit it would call stopped(). @note A Stoppable may not be restarted. + + The form of the Stoppable tree in the rippled application evolves as + the source code changes and reacts to new demands. As of March in 2017 + the Stoppable tree had this form: + + @code + + Application + | + +--------------------+--------------------+ + | | | + LoadManager SHAMapStore NodeStoreScheduler + | + JobQueue + | + +-----------+-----------+-----------+-----------+----+--------+ + | | | | | | + | NetworkOPs | InboundLedgers | OrderbookDB + | | | + Overlay InboundTransactions LedgerMaster + | | + PeerFinder LedgerCleaner + + @endcode */ /** @{ */ class Stoppable @@ -190,6 +219,9 @@ public: /** Returns `true` if all children have stopped. */ bool areChildrenStopped () const; + /* JobQueue uses this method for Job counting. */ + inline JobCounter& jobCounter (); + /** Sleep or wake up on stop. @return `true` if we are stopping @@ -282,9 +314,8 @@ private: std::string m_name; RootStoppable& m_root; Child m_child; - std::atomic m_started; - std::atomic m_stopped; - std::atomic m_childrenStopped; + std::atomic m_stopped {false}; + std::atomic m_childrenStopped {false}; Children m_children; beast::WaitableEvent m_stoppedEvent; }; @@ -296,7 +327,7 @@ class RootStoppable : public Stoppable public: explicit RootStoppable (std::string name); - ~RootStoppable () = default; + ~RootStoppable (); bool isStopping() const; @@ -326,6 +357,18 @@ public: */ void stop (beast::Journal j); + /** Return true if start() was ever called. */ + bool started () const + { + return m_started; + } + + /* JobQueue uses this method for Job counting. */ + JobCounter& rootJobCounter () + { + return jobCounter_; + } + /** Sleep or wake up on stop. @return `true` if we are stopping @@ -346,15 +389,24 @@ private: */ bool stopAsync(beast::Journal j); - std::atomic m_prepared; - std::atomic m_calledStop; + std::atomic m_prepared {false}; + std::atomic m_started {false}; + std::atomic m_calledStop {false}; std::mutex m_; std::condition_variable c_; + JobCounter jobCounter_; }; /** @} */ //------------------------------------------------------------------------------ +JobCounter& Stoppable::jobCounter () +{ + return m_root.rootJobCounter(); +} + +//------------------------------------------------------------------------------ + template bool RootStoppable::alertable_sleep_for( diff --git a/src/ripple/core/impl/Config.cpp b/src/ripple/core/impl/Config.cpp index 6e61c518da..43599a88e0 100644 --- a/src/ripple/core/impl/Config.cpp +++ b/src/ripple/core/impl/Config.cpp @@ -27,7 +27,7 @@ #include #include #include -#include +#include #include #include #include @@ -325,15 +325,15 @@ void Config::loadFromString (std::string const& fileContents) if (getSingleSection (secConfig, SECTION_NODE_SIZE, strTemp, j_)) { - if (beast::detail::ci_equal(strTemp, "tiny")) + if (beast::detail::iequals(strTemp, "tiny")) NODE_SIZE = 0; - else if (beast::detail::ci_equal(strTemp, "small")) + else if (beast::detail::iequals(strTemp, "small")) NODE_SIZE = 1; - else if (beast::detail::ci_equal(strTemp, "medium")) + else if (beast::detail::iequals(strTemp, "medium")) NODE_SIZE = 2; - else if (beast::detail::ci_equal(strTemp, "large")) + else if (beast::detail::iequals(strTemp, "large")) NODE_SIZE = 3; - else if (beast::detail::ci_equal(strTemp, "huge")) + else if (beast::detail::iequals(strTemp, "huge")) NODE_SIZE = 4; else { @@ -380,9 +380,9 @@ void Config::loadFromString (std::string const& fileContents) if (getSingleSection (secConfig, SECTION_LEDGER_HISTORY, strTemp, j_)) { - if (beast::detail::ci_equal(strTemp, "full")) + if (beast::detail::iequals(strTemp, "full")) LEDGER_HISTORY = 1000000000u; - else if (beast::detail::ci_equal(strTemp, "none")) + else if (beast::detail::iequals(strTemp, "none")) LEDGER_HISTORY = 0; else LEDGER_HISTORY = beast::lexicalCastThrow (strTemp); @@ -390,9 +390,9 @@ void Config::loadFromString (std::string const& fileContents) if (getSingleSection (secConfig, SECTION_FETCH_DEPTH, strTemp, j_)) { - if (beast::detail::ci_equal(strTemp, "none")) + if (beast::detail::iequals(strTemp, "none")) FETCH_DEPTH = 0; - else if (beast::detail::ci_equal(strTemp, "full")) + else if (beast::detail::iequals(strTemp, "full")) FETCH_DEPTH = 1000000000u; else FETCH_DEPTH = beast::lexicalCastThrow (strTemp); @@ -534,7 +534,13 @@ void Config::loadFromString (std::string const& fileContents) { auto const part = section("features"); for(auto const& s : part.values()) - features.insert(feature(s)); + { + if (auto const f = getRegisteredFeature(s)) + features.insert(*f); + else + Throw( + "Unknown feature: " + s + " in config file."); + } } } diff --git a/src/ripple/core/impl/DeadlineTimer.cpp b/src/ripple/core/impl/DeadlineTimer.cpp deleted file mode 100644 index 5673fc4e9a..0000000000 --- a/src/ripple/core/impl/DeadlineTimer.cpp +++ /dev/null @@ -1,338 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2013 Ripple Labs Inc. - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ -//============================================================================== - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace ripple { - -class DeadlineTimer::Manager -{ -private: - using Items = beast::List ; - - // Use RAII to manage our recursion counter. - // - // NOTE: Creation of any lock(mutex_) should be immediately followed - // by constructing a named CountRecursion. Otherwise the mutex recursion - // tracking will be faulty. - class CountRecursion - { - int& counter_; - - public: - CountRecursion (CountRecursion const&) = delete; - CountRecursion& operator=(CountRecursion const&) = delete; - - explicit CountRecursion (int& counter) - : counter_ {counter} - { - ++counter_; - } - - ~CountRecursion() - { - --counter_; - } - }; - - Manager () - { - thread_ = std::thread {&Manager::run, this}; - } - - ~Manager () - { - { - std::lock_guard lock {mutex_}; - CountRecursion c {recursionCount_}; - shouldExit_ = true; - wakeup_.notify_one(); - } - thread_.join(); - assert (m_items.empty ()); - } - -public: - static - Manager& - instance() - { - static Manager m; - return m; - } - - // Okay to call on an active timer. - // However, an extra notification may still happen due to concurrency. - // - void activate (DeadlineTimer& timer, - duration recurring, - time_point when) - { - using namespace std::chrono_literals; - assert (recurring >= 0ms); - - std::lock_guard lock {mutex_}; - CountRecursion c {recursionCount_}; - - if (timer.m_isActive) - { - m_items.erase (m_items.iterator_to (timer)); - - timer.m_isActive = false; - } - - timer.recurring_ = recurring; - timer.notificationTime_ = when; - - insertSorted (timer); - timer.m_isActive = true; - - wakeup_.notify_one(); - } - - // Okay to call this on an inactive timer. - // This can happen naturally based on concurrency. - // - void deactivate (DeadlineTimer& timer) - { - std::lock_guard lock {mutex_}; - CountRecursion c {recursionCount_}; - - if (timer.m_isActive) - { - m_items.erase (m_items.iterator_to (timer)); - - timer.m_isActive = false; - - wakeup_.notify_one(); - } - } - - void run () - { - using namespace std::chrono; - beast::setCurrentThreadName ("DeadlineTimer"); - bool shouldExit = true; - - do - { - { - auto const currentTime = - time_point_cast(clock::now()); - auto nextDeadline = currentTime; - - std::unique_lock lock {mutex_}; - CountRecursion c {recursionCount_}; - - // See if a timer expired - if (!shouldExit_ && !m_items.empty ()) - { - DeadlineTimer* const timer = &m_items.front (); - - // Has this timer expired? - if (timer->notificationTime_ <= currentTime) - { - // Expired, remove it from the list. - assert (timer->m_isActive); - m_items.pop_front (); - - // Is the timer recurring? - if (timer->recurring_ > 0ms) - { - // Yes so set the timer again. - timer->notificationTime_ = - currentTime + timer->recurring_; - - // Put it back into the list as active - insertSorted (*timer); - } - else - { - // Not a recurring timer, deactivate it. - timer->m_isActive = false; - } - - // Given the current code structure this call must - // happen inside the lock. Once the lock is released - // the timer might be canceled and it would be invalid - // to call timer->m_listener. - timer->m_listener->onDeadlineTimer (*timer); - - // re-loop - nextDeadline = currentTime - 1s; - } - else - { - // Timer has not yet expired. - nextDeadline = timer->notificationTime_; - - // Can't be zero and come into the else clause. - assert (nextDeadline > currentTime); - } - } - - if (!shouldExit_) - { - // It's bad news to invoke std::condition_variable_any - // wait() or wait_until() on a recursive_mutex if the - // recursion depth is greater than one. That's because - // wait() and wait_until() will only release one level - // of lock. - // - // We believe that the lock recursion depth can only be - // one at this point in the code, given the current code - // structure (December 2016). Here's why: - // - // 1. The DeadlineTimer::Manager runs exclusively on its - // own dedicated thread. This is the only thread where - // wakeup_.wait() or wakeup_.wait_until() are called. - // - // 2. So in order for the recursive_mutex to be called - // recursively, it must result from the call through - // timer->m_listener->onDeadlineTimer (*timer). - // - // 3. Any callback into DeadlineTimer from a Listener - // may do one of two things: a call to activate() or - // a call to deactivate(). Either of these will invoke - // the lock recursively. Then they both invoke - // condition_variable_any wakeup_.notify_one() under - // the recursive lock. Then they release the recursive - // lock. Once this local lock release occurs the - // recursion depth should be back to one. - // - // 4. So, once the Listener callback completes then the - // recursive_lock is no longer recursively held. That - // means when we enter the wakeup_.wait() or the - // wakeup_.wait_until() the lock is never held - // recursively. - // - // In case that analysis is, or becomes, incorrect the - // following LogicError should fire. - if (recursionCount_ != 1) - LogicError ("DeadlineTimer mutex recursion violation."); - - if (nextDeadline > currentTime) - // Wake up at the next deadline or next notify. - // Cast to clock::duration to work around VS-2015 bug. - // Harmless on other platforms. - wakeup_.wait_until (lock, - time_point_cast(nextDeadline)); - - else if (nextDeadline == currentTime) - // There is no deadline. Wake up at the next notify. - wakeup_.wait (lock); - - else; - // Do not wait. This can happen if the recurring - // timer duration is extremely short or if a listener - // burns lots of time in their callback. - } - // shouldExit is used outside the lock. - shouldExit = shouldExit_; - } // Note that we release the lock here. - - } while (!shouldExit); - } - - // Caller is responsible for locking - void insertSorted (DeadlineTimer& timer) - { - if (! m_items.empty ()) - { - Items::iterator before {m_items.begin()}; - - for (;;) - { - if (before->notificationTime_ >= timer.notificationTime_) - { - m_items.insert (before, timer); - break; - } - - ++before; - - if (before == m_items.end ()) - { - m_items.push_back (timer); - break; - } - } - } - else - { - m_items.push_back (timer); - } - } - -private: - std::recursive_mutex mutex_; - std::condition_variable_any wakeup_; // Works with std::recursive_mutex. - std::thread thread_; - bool shouldExit_ {false}; - int recursionCount_ {0}; - - Items m_items; -}; - -//------------------------------------------------------------------------------ - -DeadlineTimer::DeadlineTimer (Listener* listener) - : m_listener (listener) - , m_isActive (false) -{ -} - -DeadlineTimer::~DeadlineTimer () -{ - Manager::instance().deactivate (*this); -} - -void DeadlineTimer::cancel () -{ - Manager::instance().deactivate (*this); -} - -void DeadlineTimer::setExpiration (std::chrono::milliseconds delay) -{ - using namespace std::chrono; - assert (delay > 0ms); - - auto const when = time_point_cast(clock::now() + delay); - - Manager::instance().activate (*this, 0ms, when); -} - -void DeadlineTimer::setRecurringExpiration (std::chrono::milliseconds interval) -{ - using namespace std::chrono; - assert (interval > 0ms); - - auto const when = time_point_cast(clock::now() + interval); - - Manager::instance().activate (*this, interval, when); -} - -} // ripple diff --git a/src/ripple/core/impl/JobQueue.cpp b/src/ripple/core/impl/JobQueue.cpp index ffb919a437..f68897e802 100644 --- a/src/ripple/core/impl/JobQueue.cpp +++ b/src/ripple/core/impl/JobQueue.cpp @@ -67,8 +67,8 @@ JobQueue::collect () job_count = m_jobSet.size (); } -void -JobQueue::addJob (JobType type, std::string const& name, +bool +JobQueue::addRefCountedJob (JobType type, std::string const& name, JobFunction const& func) { assert (type != jtINVALID); @@ -76,7 +76,7 @@ JobQueue::addJob (JobType type, std::string const& name, auto iter (m_jobData.find (type)); assert (iter != m_jobData.end ()); if (iter == m_jobData.end ()) - return; + return false; JobTypeData& data (iter->second); @@ -108,6 +108,7 @@ JobQueue::addJob (JobType type, std::string const& name, data.load (), func, m_cancelCallback))); queueJob (*result.first, lock); } + return true; } int diff --git a/src/ripple/core/impl/SNTPClock.cpp b/src/ripple/core/impl/SNTPClock.cpp index 3f9b0f8443..646fc912ed 100644 --- a/src/ripple/core/impl/SNTPClock.cpp +++ b/src/ripple/core/impl/SNTPClock.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include #include #include @@ -164,12 +163,12 @@ public: socket_.async_receive_from (buffer (buf_, 256), ep_, std::bind( &SNTPClientImp::onRead, this, - beast::asio::placeholders::error, - beast::asio::placeholders::bytes_transferred)); + std::placeholders::_1, + std::placeholders::_2)); timer_.expires_from_now(NTP_QUERY_FREQUENCY); timer_.async_wait(std::bind( &SNTPClientImp::onTimer, this, - beast::asio::placeholders::error)); + std::placeholders::_1)); // VFALCO Is it correct to launch the thread // here after queuing I/O? @@ -222,7 +221,7 @@ public: timer_.expires_from_now(NTP_QUERY_FREQUENCY); timer_.async_wait(std::bind( &SNTPClientImp::onTimer, this, - beast::asio::placeholders::error)); + std::placeholders::_1)); } void @@ -286,8 +285,8 @@ public: socket_.async_receive_from(buffer(buf_, 256), ep_, std::bind(&SNTPClientImp::onRead, this, - beast::asio::placeholders::error, - beast::asio::placeholders::bytes_transferred)); + std::placeholders::_1, + std::placeholders::_2)); } //-------------------------------------------------------------------------- @@ -339,8 +338,8 @@ public: boost::asio::ip::udp::v4 (), best->first, "ntp"); resolver_.async_resolve (query, std::bind ( &SNTPClientImp::resolveComplete, this, - beast::asio::placeholders::error, - beast::asio::placeholders::iterator)); + std::placeholders::_1, + std::placeholders::_2)); JLOG(j_.trace()) << "SNTPClock: Resolve pending for " << best->first; return true; @@ -396,8 +395,8 @@ public: reinterpret_cast (SNTPQueryData)[NTP_OFF_XMITTS_FRAC] = query.nonce; socket_.async_send_to(buffer(SNTPQueryData, 48), *sel, std::bind (&SNTPClientImp::onSend, this, - beast::asio::placeholders::error, - beast::asio::placeholders::bytes_transferred)); + std::placeholders::_1, + std::placeholders::_2)); } } diff --git a/src/ripple/core/impl/SociDB.cpp b/src/ripple/core/impl/SociDB.cpp index aeae7b8729..c43daa05b6 100644 --- a/src/ripple/core/impl/SociDB.cpp +++ b/src/ripple/core/impl/SociDB.cpp @@ -226,7 +226,13 @@ private: running_ = true; } - jobQueue_.addJob (jtWAL, "WAL", [this] (Job&) { checkpoint(); }); + // If the Job is not added to the JobQueue then we're not running_. + if (! jobQueue_.addJob ( + jtWAL, "WAL", [this] (Job&) { checkpoint(); })) + { + std::lock_guard lock (mutex_); + running_ = false; + } } void checkpoint () diff --git a/src/ripple/core/impl/Stoppable.cpp b/src/ripple/core/impl/Stoppable.cpp index 862e592a01..ecd55475ef 100644 --- a/src/ripple/core/impl/Stoppable.cpp +++ b/src/ripple/core/impl/Stoppable.cpp @@ -26,9 +26,6 @@ Stoppable::Stoppable (std::string name, RootStoppable& root) : m_name (std::move (name)) , m_root (root) , m_child (this) - , m_started (false) - , m_stopped (false) - , m_childrenStopped (false) { } @@ -36,9 +33,6 @@ Stoppable::Stoppable (std::string name, Stoppable& parent) : m_name (std::move (name)) , m_root (parent.m_root) , m_child (this) - , m_started (false) - , m_stopped (false) - , m_childrenStopped (false) { // Must not have stopping parent. assert (! parent.isStopping()); @@ -48,8 +42,8 @@ Stoppable::Stoppable (std::string name, Stoppable& parent) Stoppable::~Stoppable () { - // Children must be stopped. - assert (!m_started || m_childrenStopped); + // Either we must not have started, or Children must be stopped. + assert (!m_root.started() || m_childrenStopped); } bool Stoppable::isStopping() const @@ -113,12 +107,13 @@ void Stoppable::stopAsyncRecursive (beast::Journal j) auto const start = high_resolution_clock::now(); onStop (); auto const ms = duration_cast( - high_resolution_clock::now() - start).count(); + high_resolution_clock::now() - start); #ifdef NDEBUG - if (ms >= 10) + using namespace std::chrono_literals; + if (ms >= 10ms) if (auto stream = j.fatal()) - stream << m_name << "::onStop took " << ms << "ms"; + stream << m_name << "::onStop took " << ms.count() << "ms"; #else (void)ms; #endif @@ -159,11 +154,15 @@ void Stoppable::stopRecursive (beast::Journal j) RootStoppable::RootStoppable (std::string name) : Stoppable (std::move (name), *this) - , m_prepared (false) - , m_calledStop (false) { } +RootStoppable::~RootStoppable () +{ + using namespace std::chrono_literals; + jobCounter_.join(m_name.c_str(), 1s, debugLog()); +} + bool RootStoppable::isStopping() const { return m_calledStop; @@ -194,7 +193,7 @@ void RootStoppable::stop (beast::Journal j) stopRecursive (j); } -bool RootStoppable::stopAsync(beast::Journal j) +bool RootStoppable::stopAsync (beast::Journal j) { bool alreadyCalled; { @@ -211,6 +210,11 @@ bool RootStoppable::stopAsync(beast::Journal j) stream << "Stoppable::stop called again"; return false; } + + // Wait until all in-flight JobQueue Jobs are completed. + using namespace std::chrono_literals; + jobCounter_.join (m_name.c_str(), 1s, j); + c_.notify_all(); stopAsyncRecursive(j); return true; diff --git a/src/ripple/json/Output.h b/src/ripple/json/Output.h index a7b6107363..5122357449 100644 --- a/src/ripple/json/Output.h +++ b/src/ripple/json/Output.h @@ -20,19 +20,19 @@ #ifndef RIPPLE_JSON_OUTPUT_H_INCLUDED #define RIPPLE_JSON_OUTPUT_H_INCLUDED -#include +#include #include namespace Json { class Value; -using Output = std::function ; +using Output = std::function ; inline Output stringOutput (std::string& s) { - return [&](boost::string_ref const& b) { s.append (b.data(), b.size()); }; + return [&](beast::string_view const& b) { s.append (b.data(), b.size()); }; } /** Writes a minimal representation of a Json value to an Output in O(n) time. diff --git a/src/ripple/json/impl/Writer.cpp b/src/ripple/json/impl/Writer.cpp index 5b2f1f6105..d6064ed293 100644 --- a/src/ripple/json/impl/Writer.cpp +++ b/src/ripple/json/impl/Writer.cpp @@ -93,13 +93,13 @@ public: stack_.top().type = ct; } - void output (boost::string_ref const& bytes) + void output (beast::string_view const& bytes) { markStarted (); output_ (bytes); } - void stringOutput (boost::string_ref const& bytes) + void stringOutput (beast::string_view const& bytes) { markStarted (); std::size_t position = 0, writtenUntil = 0; diff --git a/src/ripple/ledger/ApplyView.h b/src/ripple/ledger/ApplyView.h index 5e555e8430..c2d740bec7 100644 --- a/src/ripple/ledger/ApplyView.h +++ b/src/ripple/ledger/ApplyView.h @@ -22,6 +22,7 @@ #include #include +#include namespace ripple { @@ -107,8 +108,16 @@ operator&(ApplyFlags const& lhs, class ApplyView : public ReadView { -public: +private: + /** Add an entry to a directory using the specified insert strategy */ + boost::optional + dirAdd ( + bool preserveOrder, + Keylet const& directory, + uint256 const& key, + std::function const&)> const& describe); +public: ApplyView () = default; /** Returns the tx apply flags. @@ -217,6 +226,113 @@ public: std::uint32_t cur, std::uint32_t next) {}; + /** Append an entry to a directory + + Entries in the directory will be stored in order of insertion, i.e. new + entries will always be added at the tail end of the last page. + + @param directory the base of the directory + @param key the entry to insert + @param describe callback to add required entries to a new page + + @return a \c boost::optional which, if insertion was successful, + will contain the page number in which the item was stored. + + @note this function may create a page (including a root page), if no + page with space is available. This function will only fail if the + page counter exceeds the protocol-defined maximum number of + allowable pages. + */ + /** @{ */ + boost::optional + dirAppend ( + Keylet const& directory, + uint256 const& key, + std::function const&)> const& describe) + { + return dirAdd (true, directory, key, describe); + } + + boost::optional + dirAppend ( + Keylet const& directory, + Keylet const& key, + std::function const&)> const& describe) + { + return dirAppend (directory, key.key, describe); + } + /** @} */ + + /** Insert an entry to a directory + + Entries in the directory will be stored in a semi-random order, but + each page will be maintained in sorted order. + + @param directory the base of the directory + @param key the entry to insert + @param describe callback to add required entries to a new page + + @return a \c boost::optional which, if insertion was successful, + will contain the page number in which the item was stored. + + @note this function may create a page (including a root page), if no + page with space is available.this function will only fail if the + page counter exceeds the protocol-defined maximum number of + allowable pages. + */ + /** @{ */ + boost::optional + dirInsert ( + Keylet const& directory, + uint256 const& key, + std::function const&)> const& describe) + { + return dirAdd (false, directory, key, describe); + } + + boost::optional + dirInsert ( + Keylet const& directory, + Keylet const& key, + std::function const&)> const& describe) + { + return dirInsert (directory, key.key, describe); + } + /** @} */ + + /** Remove an entry from a directory + + @param directory the base of the directory + @param page the page number for this page + @param key the entry to remove + @param keepRoot if deleting the last entry, don't + delete the root page (i.e. the directory itself). + + @return \c true if the entry was found and deleted and + \c false otherwise. + + @note This function will remove zero or more pages from the directory; + the root page will not be deleted even if it is empty, unless + \p keepRoot is not set and the directory is empty. + */ + /** @{ */ + bool + dirRemove ( + Keylet const& directory, + std::uint64_t page, + uint256 const& key, + bool keepRoot); + + bool + dirRemove ( + Keylet const& directory, + std::uint64_t page, + Keylet const& key, + bool keepRoot) + { + return dirRemove (directory, page, key.key, keepRoot); + } + /** @} */ }; } // ripple diff --git a/src/ripple/ledger/View.h b/src/ripple/ledger/View.h index 47dc641270..c249c34ada 100644 --- a/src/ripple/ledger/View.h +++ b/src/ripple/ledger/View.h @@ -223,36 +223,21 @@ dirNext (ApplyView& view, std::function describeOwnerDir(AccountID const& account); -// <-- uNodeDir: For deletion, present to make dirDelete efficient. -// --> uRootIndex: The index of the base of the directory. Nodes are based off of this. -// --> uLedgerIndex: Value to add to directory. -// Only append. This allow for things that watch append only structure to just monitor from the last node on ward. -// Within a node with no deletions order of elements is sequential. Otherwise, order of elements is random. - -/** Add an entry to directory, creating the directory if necessary - - @param uNodeDir node of entry - makes deletion efficient - @param uRootIndex The index of the base of the directory. - Nodes are based off of this. - @param uLedgerIndex Value to add to directory. - - @return a pair containing a code indicating success or - failure, and if successful, a boolean indicating - whether the directory was just created. -*/ -std::pair +// deprecated +boost::optional dirAdd (ApplyView& view, - std::uint64_t& uNodeDir, // Node of entry. Keylet const& uRootIndex, uint256 const& uLedgerIndex, + bool strictOrder, std::function fDescriber, beast::Journal j); +// deprecated TER dirDelete (ApplyView& view, const bool bKeepRoot, - const std::uint64_t& uNodeDir, // Node item is mentioned in. - uint256 const& uRootIndex, + std::uint64_t uNodeDir, // Node item is mentioned in. + Keylet const& uRootIndex, uint256 const& uLedgerIndex, // Item being deleted const bool bStable, const bool bSoft, diff --git a/src/ripple/ledger/impl/ApplyView.cpp b/src/ripple/ledger/impl/ApplyView.cpp new file mode 100644 index 0000000000..d985c3f0d0 --- /dev/null +++ b/src/ripple/ledger/impl/ApplyView.cpp @@ -0,0 +1,275 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include + +namespace ripple { + +boost::optional +ApplyView::dirAdd ( + bool preserveOrder, + Keylet const& directory, + uint256 const& key, + std::function const&)> const& describe) +{ + auto root = peek(directory); + + if (! root) + { + // No root, make it. + root = std::make_shared(directory); + root->setFieldH256 (sfRootIndex, directory.key); + describe (root); + + STVector256 v; + v.push_back (key); + root->setFieldV256 (sfIndexes, v); + + insert (root); + return std::uint64_t{0}; + } + + std::uint64_t page = root->getFieldU64(sfIndexPrevious); + + auto node = root; + + if (page) + { + node = peek (keylet::page(directory, page)); + if (!node) + LogicError ("Directory chain: root back-pointer broken."); + } + + auto indexes = node->getFieldV256(sfIndexes); + + // If there's space, we use it: + if (indexes.size () < dirNodeMaxEntries) + { + if (preserveOrder) + { + if (std::find(indexes.begin(), indexes.end(), key) != indexes.end()) + LogicError ("dirInsert: double insertion"); + + indexes.push_back(key); + } + else + { + // We can't be sure if this page is already sorted because + // it may be a legacy page we haven't yet touched. Take + // the time to sort it. + std::sort (indexes.begin(), indexes.end()); + + auto pos = std::lower_bound(indexes.begin(), indexes.end(), key); + + if (pos != indexes.end() && key == *pos) + LogicError ("dirInsert: double insertion"); + + indexes.insert (pos, key); + } + + node->setFieldV256 (sfIndexes, indexes); + update(node); + return page; + } + + // Check whether we're out of pages. + if (++page >= dirNodeMaxPages) + return boost::none; + + // We are about to create a new node; we'll link it to + // the chain first: + node->setFieldU64 (sfIndexNext, page); + update(node); + + root->setFieldU64 (sfIndexPrevious, page); + update(root); + + // Insert the new key: + indexes.clear(); + indexes.push_back (key); + + node = std::make_shared(keylet::page(directory, page)); + node->setFieldH256 (sfRootIndex, directory.key); + node->setFieldV256 (sfIndexes, indexes); + + // Save some space by not specifying the value 0 since + // it's the default. + if (page != 1) + node->setFieldU64 (sfIndexPrevious, page - 1); + describe (node); + insert (node); + + return page; +} + +bool +ApplyView::dirRemove ( + Keylet const& directory, + std::uint64_t page, + uint256 const& key, + bool keepRoot) +{ + auto node = peek(keylet::page(directory, page)); + + if (!node) + return false; + + std::uint64_t constexpr rootPage = 0; + + { + auto entries = node->getFieldV256(sfIndexes); + + auto it = std::find(entries.begin(), entries.end(), key); + + if (entries.end () == it) + return false; + + // We always preserve the relative order when we remove. + entries.erase(it); + + node->setFieldV256(sfIndexes, entries); + update(node); + + if (!entries.empty()) + return true; + } + + // The current page is now empty; check if it can be + // deleted, and, if so, whether the entire directory + // can now be removed. + auto prevPage = node->getFieldU64(sfIndexPrevious); + auto nextPage = node->getFieldU64(sfIndexNext); + + // The first page is the directory's root node and is + // treated specially: it can never be deleted even if + // it is empty, unless we plan on removing the entire + // directory. + if (page == rootPage) + { + if (nextPage == page && prevPage != page) + LogicError ("Directory chain: fwd link broken"); + + if (prevPage == page && nextPage != page) + LogicError ("Directory chain: rev link broken"); + + // Older versions of the code would, in some cases, + // allow the last page to be empty. Remove such + // pages if we stumble on them: + if (nextPage == prevPage && nextPage != page) + { + auto last = peek(keylet::page(directory, nextPage)); + if (!last) + LogicError ("Directory chain: fwd link broken."); + + if (last->getFieldV256 (sfIndexes).empty()) + { + // Update the first page's linked list and + // mark it as updated. + node->setFieldU64 (sfIndexNext, page); + node->setFieldU64 (sfIndexPrevious, page); + update(node); + + // And erase the empty last page: + erase(last); + + // Make sure our local values reflect the + // updated information: + nextPage = page; + prevPage = page; + } + } + + if (keepRoot) + return true; + + // If there's no other pages, erase the root: + if (nextPage == page && prevPage == page) + erase(node); + + return true; + } + + // This can never happen for nodes other than the root: + if (nextPage == page) + LogicError ("Directory chain: fwd link broken"); + + if (prevPage == page) + LogicError ("Directory chain: rev link broken"); + + // This node isn't the root, so it can either be in the + // middle of the list, or at the end. Unlink it first + // and then check if that leaves the list with only a + // root: + auto prev = peek(keylet::page(directory, prevPage)); + if (!prev) + LogicError ("Directory chain: fwd link broken."); + // Fix previous to point to its new next. + prev->setFieldU64(sfIndexNext, nextPage); + update (prev); + + auto next = peek(keylet::page(directory, nextPage)); + if (!next) + LogicError ("Directory chain: rev link broken."); + // Fix next to point to its new previous. + next->setFieldU64(sfIndexPrevious, prevPage); + update(next); + + // The page is no longer linked. Delete it. + erase(node); + + // Check whether the next page is the last page and, if + // so, whether it's empty. If it is, delete it. + if (nextPage != rootPage && + next->getFieldU64 (sfIndexNext) == rootPage && + next->getFieldV256 (sfIndexes).empty()) + { + // Since next doesn't point to the root, it + // can't be pointing to prev. + erase(next); + + // The previous page is now the last page: + prev->setFieldU64(sfIndexNext, rootPage); + update (prev); + + // And the root points to the the last page: + auto root = peek(keylet::page(directory, rootPage)); + if (!root) + LogicError ("Directory chain: root link broken."); + root->setFieldU64(sfIndexPrevious, prevPage); + update (root); + + nextPage = rootPage; + } + + // If we're not keeping the root, then check to see if + // it's left empty. If so, delete it as well. + if (!keepRoot && nextPage == rootPage && prevPage == rootPage) + { + if (prev->getFieldV256 (sfIndexes).empty()) + erase(prev); + } + + return true; +} + +} // ripple diff --git a/src/ripple/ledger/impl/View.cpp b/src/ripple/ledger/impl/View.cpp index 97bffb5c9a..219402c352 100644 --- a/src/ripple/ledger/impl/View.cpp +++ b/src/ripple/ledger/impl/View.cpp @@ -19,12 +19,15 @@ #include #include +#include #include #include #include #include #include +#include #include +#include #include #include #include @@ -100,14 +103,6 @@ bool fix1449 (NetClock::time_point const closeTime) return closeTime > fix1449Time(); } -// VFALCO NOTE A copy of the other one for now -/** Maximum number of entries in a directory page - A change would be protocol-breaking. -*/ -#ifndef DIR_NODE_MAX -#define DIR_NODE_MAX 32 -#endif - //------------------------------------------------------------------------------ // // Observers @@ -810,19 +805,28 @@ describeOwnerDir(AccountID const& account) }; } -std::pair +boost::optional dirAdd (ApplyView& view, - std::uint64_t& uNodeDir, Keylet const& dir, uint256 const& uLedgerIndex, + bool strictOrder, std::function fDescriber, beast::Journal j) { + if (view.rules().enabled(featureSortedDirectories)) + { + if (strictOrder) + return view.dirAppend(dir, uLedgerIndex, fDescriber); + else + return view.dirInsert(dir, uLedgerIndex, fDescriber); + } + JLOG (j.trace()) << "dirAdd:" << " dir=" << to_string (dir.key) << " uLedgerIndex=" << to_string (uLedgerIndex); auto sleRoot = view.peek(dir); + std::uint64_t uNodeDir = 0; if (! sleRoot) { @@ -840,9 +844,7 @@ dirAdd (ApplyView& view, "dirAdd: created root " << to_string (dir.key) << " for entry " << to_string (uLedgerIndex); - uNodeDir = 0; - - return { tesSUCCESS, true }; + return uNodeDir; } SLE::pointer sleNode; @@ -864,7 +866,7 @@ dirAdd (ApplyView& view, svIndexes = sleNode->getFieldV256 (sfIndexes); - if (DIR_NODE_MAX != svIndexes.size ()) + if (dirNodeMaxEntries != svIndexes.size ()) { // Add to current node. view.update(sleNode); @@ -872,7 +874,7 @@ dirAdd (ApplyView& view, // Add to new node. else if (!++uNodeDir) { - return { tecDIR_FULL, false }; + return boost::none; } else { @@ -908,28 +910,36 @@ dirAdd (ApplyView& view, JLOG (j.trace()) << "dirAdd: appending: Node: " << strHex (uNodeDir); - return { tesSUCCESS, false }; + return uNodeDir; } // Ledger must be in a state for this to work. TER dirDelete (ApplyView& view, const bool bKeepRoot, // --> True, if we never completely clean up, after we overflow the root node. - const std::uint64_t& uNodeDir, // --> Node containing entry. - uint256 const& uRootIndex, // --> The index of the base of the directory. Nodes are based off of this. + std::uint64_t uNodeDir, // --> Node containing entry. + Keylet const& root, // --> The index of the base of the directory. Nodes are based off of this. uint256 const& uLedgerIndex, // --> Value to remove from directory. const bool bStable, // --> True, not to change relative order of entries. const bool bSoft, // --> True, uNodeDir is not hard and fast (pass uNodeDir=0). beast::Journal j) { + if (view.rules().enabled(featureSortedDirectories)) + { + if (view.dirRemove(root, uNodeDir, uLedgerIndex, bKeepRoot)) + return tesSUCCESS; + + return tefBAD_LEDGER; + } + std::uint64_t uNodeCur = uNodeDir; SLE::pointer sleNode = - view.peek(keylet::page(uRootIndex, uNodeCur)); + view.peek(keylet::page(root, uNodeCur)); if (!sleNode) { JLOG (j.warn()) << "dirDelete: no such node:" << - " uRootIndex=" << to_string (uRootIndex) << + " root=" << to_string (root.key) << " uNodeDir=" << strHex (uNodeDir) << " uLedgerIndex=" << to_string (uLedgerIndex); @@ -943,7 +953,7 @@ dirDelete (ApplyView& view, // Go the extra mile. Even if node doesn't exist, try the next node. return dirDelete (view, bKeepRoot, - uNodeDir + 1, uRootIndex, uLedgerIndex, bStable, true, j); + uNodeDir + 1, root, uLedgerIndex, bStable, true, j); } else { @@ -968,7 +978,7 @@ dirDelete (ApplyView& view, { // Go the extra mile. Even if entry not in node, try the next node. return dirDelete (view, bKeepRoot, uNodeDir + 1, - uRootIndex, uLedgerIndex, bStable, true, j); + root, uLedgerIndex, bStable, true, j); } return tefBAD_LEDGER; @@ -1022,7 +1032,7 @@ dirDelete (ApplyView& view, else { // Have only a root node and a last node. - auto sleLast = view.peek(keylet::page(uRootIndex, uNodeNext)); + auto sleLast = view.peek(keylet::page(root, uNodeNext)); assert (sleLast); @@ -1044,9 +1054,8 @@ dirDelete (ApplyView& view, { // Not root and not last node. Can delete node. - auto slePrevious = - view.peek(keylet::page(uRootIndex, uNodePrevious)); - auto sleNext = view.peek(keylet::page(uRootIndex, uNodeNext)); + auto slePrevious = view.peek(keylet::page(root, uNodePrevious)); + auto sleNext = view.peek(keylet::page(root, uNodeNext)); assert (slePrevious); if (!slePrevious) { @@ -1079,8 +1088,7 @@ dirDelete (ApplyView& view, else { // Last and only node besides the root. - auto sleRoot = view.peek (keylet::page(uRootIndex)); - + auto sleRoot = view.peek(root); assert (sleRoot); if (sleRoot->getFieldV256 (sfIndexes).empty ()) @@ -1129,86 +1137,77 @@ trustCreate (ApplyView& view, ltRIPPLE_STATE, uIndex); view.insert (sleRippleState); - std::uint64_t uLowNode; - std::uint64_t uHighNode; + auto lowNode = dirAdd (view, keylet::ownerDir (uLowAccountID), + sleRippleState->key(), false, describeOwnerDir (uLowAccountID), j); - TER terResult; + if (!lowNode) + return tecDIR_FULL; - std::tie (terResult, std::ignore) = dirAdd (view, - uLowNode, keylet::ownerDir (uLowAccountID), - sleRippleState->key(), - describeOwnerDir (uLowAccountID), j); + auto highNode = dirAdd (view, keylet::ownerDir (uHighAccountID), + sleRippleState->key(), false, describeOwnerDir (uHighAccountID), j); - if (tesSUCCESS == terResult) + if (!highNode) + return tecDIR_FULL; + + const bool bSetDst = saLimit.getIssuer () == uDstAccountID; + const bool bSetHigh = bSrcHigh ^ bSetDst; + + assert (sleAccount->getAccountID (sfAccount) == + (bSetHigh ? uHighAccountID : uLowAccountID)); + auto slePeer = view.peek (keylet::account( + bSetHigh ? uLowAccountID : uHighAccountID)); + assert (slePeer); + + // Remember deletion hints. + sleRippleState->setFieldU64 (sfLowNode, *lowNode); + sleRippleState->setFieldU64 (sfHighNode, *highNode); + + sleRippleState->setFieldAmount ( + bSetHigh ? sfHighLimit : sfLowLimit, saLimit); + sleRippleState->setFieldAmount ( + bSetHigh ? sfLowLimit : sfHighLimit, + STAmount ({saBalance.getCurrency (), + bSetDst ? uSrcAccountID : uDstAccountID})); + + if (uQualityIn) + sleRippleState->setFieldU32 ( + bSetHigh ? sfHighQualityIn : sfLowQualityIn, uQualityIn); + + if (uQualityOut) + sleRippleState->setFieldU32 ( + bSetHigh ? sfHighQualityOut : sfLowQualityOut, uQualityOut); + + std::uint32_t uFlags = bSetHigh ? lsfHighReserve : lsfLowReserve; + + if (bAuth) { - std::tie (terResult, std::ignore) = dirAdd (view, - uHighNode, keylet::ownerDir (uHighAccountID), - sleRippleState->key(), - describeOwnerDir (uHighAccountID), j); + uFlags |= (bSetHigh ? lsfHighAuth : lsfLowAuth); + } + if (bNoRipple) + { + uFlags |= (bSetHigh ? lsfHighNoRipple : lsfLowNoRipple); + } + if (bFreeze) + { + uFlags |= (!bSetHigh ? lsfLowFreeze : lsfHighFreeze); } - if (tesSUCCESS == terResult) + if ((slePeer->getFlags() & lsfDefaultRipple) == 0) { - const bool bSetDst = saLimit.getIssuer () == uDstAccountID; - const bool bSetHigh = bSrcHigh ^ bSetDst; - - assert (sleAccount->getAccountID (sfAccount) == - (bSetHigh ? uHighAccountID : uLowAccountID)); - auto slePeer = view.peek (keylet::account( - bSetHigh ? uLowAccountID : uHighAccountID)); - assert (slePeer); - - // Remember deletion hints. - sleRippleState->setFieldU64 (sfLowNode, uLowNode); - sleRippleState->setFieldU64 (sfHighNode, uHighNode); - - sleRippleState->setFieldAmount ( - bSetHigh ? sfHighLimit : sfLowLimit, saLimit); - sleRippleState->setFieldAmount ( - bSetHigh ? sfLowLimit : sfHighLimit, - STAmount ({saBalance.getCurrency (), - bSetDst ? uSrcAccountID : uDstAccountID})); - - if (uQualityIn) - sleRippleState->setFieldU32 ( - bSetHigh ? sfHighQualityIn : sfLowQualityIn, uQualityIn); - - if (uQualityOut) - sleRippleState->setFieldU32 ( - bSetHigh ? sfHighQualityOut : sfLowQualityOut, uQualityOut); - - std::uint32_t uFlags = bSetHigh ? lsfHighReserve : lsfLowReserve; - - if (bAuth) - { - uFlags |= (bSetHigh ? lsfHighAuth : lsfLowAuth); - } - if (bNoRipple) - { - uFlags |= (bSetHigh ? lsfHighNoRipple : lsfLowNoRipple); - } - if (bFreeze) - { - uFlags |= (!bSetHigh ? lsfLowFreeze : lsfHighFreeze); - } - - if ((slePeer->getFlags() & lsfDefaultRipple) == 0) - { - // The other side's default is no rippling - uFlags |= (bSetHigh ? lsfLowNoRipple : lsfHighNoRipple); - } - - sleRippleState->setFieldU32 (sfFlags, uFlags); - adjustOwnerCount(view, sleAccount, 1, j); - - // ONLY: Create ripple balance. - sleRippleState->setFieldAmount (sfBalance, bSetHigh ? -saBalance : saBalance); - - view.creditHook (uSrcAccountID, - uDstAccountID, saBalance, saBalance.zeroed()); + // The other side's default is no rippling + uFlags |= (bSetHigh ? lsfLowNoRipple : lsfHighNoRipple); } - return terResult; + sleRippleState->setFieldU32 (sfFlags, uFlags); + adjustOwnerCount(view, sleAccount, 1, j); + + // ONLY: Create ripple balance. + sleRippleState->setFieldAmount (sfBalance, bSetHigh ? -saBalance : saBalance); + + view.creditHook (uSrcAccountID, + uDstAccountID, saBalance, saBalance.zeroed()); + + return tesSUCCESS; } TER @@ -1230,7 +1229,7 @@ trustDelete (ApplyView& view, terResult = dirDelete(view, false, uLowNode, - getOwnerDirIndex (uLowAccountID), + keylet::ownerDir (uLowAccountID), sleRippleState->key(), false, !bLowNode, @@ -1243,7 +1242,7 @@ trustDelete (ApplyView& view, terResult = dirDelete (view, false, uHighNode, - getOwnerDirIndex (uHighAccountID), + keylet::ownerDir (uHighAccountID), sleRippleState->key(), false, !bHighNode, @@ -1268,14 +1267,12 @@ offerDelete (ApplyView& view, // Detect legacy directories. bool bOwnerNode = sle->isFieldPresent (sfOwnerNode); - std::uint64_t uOwnerNode = sle->getFieldU64 (sfOwnerNode); uint256 uDirectory = sle->getFieldH256 (sfBookDirectory); - std::uint64_t uBookNode = sle->getFieldU64 (sfBookNode); - TER terResult = dirDelete (view, false, uOwnerNode, - getOwnerDirIndex (owner), offerIndex, false, !bOwnerNode, j); - TER terResult2 = dirDelete (view, false, uBookNode, - uDirectory, offerIndex, true, false, j); + TER terResult = dirDelete (view, false, sle->getFieldU64 (sfOwnerNode), + keylet::ownerDir (owner), offerIndex, false, !bOwnerNode, j); + TER terResult2 = dirDelete (view, false, sle->getFieldU64 (sfBookNode), + keylet::page (uDirectory), offerIndex, true, false, j); if (tesSUCCESS == terResult) adjustOwnerCount(view, view.peek( diff --git a/src/ripple/net/AutoSocket.h b/src/ripple/net/AutoSocket.h index d80af11e46..bec9ccea47 100644 --- a/src/ripple/net/AutoSocket.h +++ b/src/ripple/net/AutoSocket.h @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include @@ -185,8 +184,8 @@ public: std::bind ( &AutoSocket::handle_autodetect, this, cbFunc, - beast::asio::placeholders::error, - beast::asio::placeholders::bytes_transferred)); + std::placeholders::_1, + std::placeholders::_2)); } } diff --git a/src/ripple/net/InfoSub.h b/src/ripple/net/InfoSub.h index ee52f621ea..60d85d53ea 100644 --- a/src/ripple/net/InfoSub.h +++ b/src/ripple/net/InfoSub.h @@ -115,6 +115,7 @@ public: // virtual pointer findRpcSub (std::string const& strUrl) = 0; virtual pointer addRpcSub (std::string const& strUrl, ref rspEntry) = 0; + virtual bool tryRemoveRpcSub (std::string const& strUrl) = 0; }; public: diff --git a/src/ripple/net/impl/HTTPClient.cpp b/src/ripple/net/impl/HTTPClient.cpp index 61fb76dbb6..8adfba5ccc 100644 --- a/src/ripple/net/impl/HTTPClient.cpp +++ b/src/ripple/net/impl/HTTPClient.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include @@ -196,7 +195,7 @@ public: std::bind ( &HTTPClientImp::handleDeadline, shared_from_this (), - beast::asio::placeholders::error)); + std::placeholders::_1)); } if (!mShutdown) @@ -207,8 +206,8 @@ public: std::bind ( &HTTPClientImp::handleResolve, shared_from_this (), - beast::asio::placeholders::error, - beast::asio::placeholders::iterator)); + std::placeholders::_1, + std::placeholders::_2)); } if (mShutdown) @@ -246,7 +245,7 @@ public: mSocket.async_shutdown (std::bind ( &HTTPClientImp::handleShutdown, shared_from_this (), - beast::asio::placeholders::error)); + std::placeholders::_1)); } } @@ -285,7 +284,7 @@ public: std::bind ( &HTTPClientImp::handleConnect, shared_from_this (), - beast::asio::placeholders::error)); + std::placeholders::_1)); } } @@ -325,7 +324,7 @@ public: std::bind ( &HTTPClientImp::handleRequest, shared_from_this (), - beast::asio::placeholders::error)); + std::placeholders::_1)); } else { @@ -354,8 +353,8 @@ public: mRequest, std::bind (&HTTPClientImp::handleWrite, shared_from_this (), - beast::asio::placeholders::error, - beast::asio::placeholders::bytes_transferred)); + std::placeholders::_1, + std::placeholders::_2)); } } @@ -379,8 +378,8 @@ public: "\r\n\r\n", std::bind (&HTTPClientImp::handleHeader, shared_from_this (), - beast::asio::placeholders::error, - beast::asio::placeholders::bytes_transferred)); + std::placeholders::_1, + std::placeholders::_2)); } } @@ -430,8 +429,8 @@ public: boost::asio::transfer_all (), std::bind (&HTTPClientImp::handleData, shared_from_this (), - beast::asio::placeholders::error, - beast::asio::placeholders::bytes_transferred)); + std::placeholders::_1, + std::placeholders::_2)); } } diff --git a/src/ripple/net/impl/RPCCall.cpp b/src/ripple/net/impl/RPCCall.cpp index 7ae7596310..b185bfd418 100644 --- a/src/ripple/net/impl/RPCCall.cpp +++ b/src/ripple/net/impl/RPCCall.cpp @@ -35,7 +35,7 @@ #include #include #include -#include +#include #include #include #include @@ -423,9 +423,9 @@ private: // This may look reversed, but it's intentional: jss::vetoed // determines whether an amendment is vetoed - so "reject" means // that jss::vetoed is true. - if (beast::detail::ci_equal(action, "reject")) + if (beast::detail::iequals(action, "reject")) jvRequest[jss::vetoed] = Json::Value (true); - else if (beast::detail::ci_equal(action, "accept")) + else if (beast::detail::iequals(action, "accept")) jvRequest[jss::vetoed] = Json::Value (false); else return rpcError (rpcINVALID_PARAMS); diff --git a/src/ripple/net/impl/RPCSub.cpp b/src/ripple/net/impl/RPCSub.cpp index ac7b745015..a08d382941 100644 --- a/src/ripple/net/impl/RPCSub.cpp +++ b/src/ripple/net/impl/RPCSub.cpp @@ -93,11 +93,9 @@ public: if (!mSending) { // Start a sending thread. - mSending = true; - JLOG (j_.info()) << "RPCCall::fromNetwork start"; - m_jobQueue.addJob ( + mSending = m_jobQueue.addJob ( jtCLIENT, "RPCSub::sendThread", [this] (Job&) { sendThread(); }); diff --git a/src/ripple/nodestore/backend/MemoryFactory.cpp b/src/ripple/nodestore/backend/MemoryFactory.cpp index 9bb3c419a9..aaf704e5c3 100644 --- a/src/ripple/nodestore/backend/MemoryFactory.cpp +++ b/src/ripple/nodestore/backend/MemoryFactory.cpp @@ -21,7 +21,7 @@ #include #include #include -#include +#include #include #include #include @@ -40,7 +40,7 @@ class MemoryFactory : public Factory { private: std::mutex mutex_; - std::map map_; + std::map map_; public: MemoryFactory(); diff --git a/src/ripple/nodestore/impl/ManagerImp.cpp b/src/ripple/nodestore/impl/ManagerImp.cpp index a9826ba4c4..cbccc0b07a 100644 --- a/src/ripple/nodestore/impl/ManagerImp.cpp +++ b/src/ripple/nodestore/impl/ManagerImp.cpp @@ -128,7 +128,7 @@ ManagerImp::find (std::string const& name) auto const iter = std::find_if(list_.begin(), list_.end(), [&name](Factory* other) { - return beast::detail::ci_equal(name, other->getName()); + return beast::detail::iequals(name, other->getName()); } ); if (iter == list_.end()) return nullptr; diff --git a/src/ripple/nodestore/impl/codec.h b/src/ripple/nodestore/impl/codec.h index 4fe4bebb32..5f76ae6fed 100644 --- a/src/ripple/nodestore/impl/codec.h +++ b/src/ripple/nodestore/impl/codec.h @@ -29,6 +29,7 @@ #include #include #include +#include #include namespace ripple { @@ -61,14 +62,14 @@ snappy_decompress (void const* in, reinterpret_cast(in), in_size, &result.second)) Throw ( - "snappy decompress"); + "snappy decompress: GetUncompressedLength"); void* const out = bf(result.second); result.first = out; if (! snappy::RawUncompress( reinterpret_cast(in), in_size, reinterpret_cast(out))) Throw ( - "snappy decompress"); + "snappy decompress: RawUncompress"); return result; } @@ -86,7 +87,7 @@ lz4_decompress (void const* in, p, in_size, result.second); if (n == 0) Throw ( - "lz4 decompress"); + "lz4 decompress: n == 0"); void* const out = bf(result.second); result.first = out; if (LZ4_decompress_fast( @@ -94,7 +95,7 @@ lz4_decompress (void const* in, reinterpret_cast(out), result.second) + n != in_size) Throw ( - "lz4 decompress"); + "lz4 decompress: LZ4_decompress_fast"); return result; } @@ -177,7 +178,9 @@ nodeobject_decompress (void const* in, field::size; // Mask if (in_size < hs + 32) Throw ( - "nodeobject codec: short inner node"); + "nodeobject codec v1: short inner node size: " + + std::string("in_size = ") + std::to_string(in_size) + + " hs = " + std::to_string(hs)); istream is(p, in_size); std::uint16_t mask; read(is, mask); // Mask @@ -193,7 +196,7 @@ nodeobject_decompress (void const* in, static_cast(HashPrefix::innerNode)); if (mask == 0) Throw ( - "nodeobject codec: empty inner node"); + "nodeobject codec v1: empty inner node"); std::uint16_t bit = 0x8000; for (int i = 16; i--; bit >>= 1) { @@ -201,7 +204,9 @@ nodeobject_decompress (void const* in, { if (in_size < 32) Throw ( - "nodeobject codec: short inner node"); + "nodeobject codec v1: short inner node subsize: " + + std::string("in_size = ") + std::to_string(in_size) + + " i = " + std::to_string(i)); std::memcpy(os.data(32), is(32), 32); in_size -= 32; } @@ -212,14 +217,16 @@ nodeobject_decompress (void const* in, } if (in_size > 0) Throw ( - "nodeobject codec: long inner node"); + "nodeobject codec v1: long inner node, in_size = " + + std::to_string(in_size)); break; } case 3: // full v1 inner node { if (in_size != 16 * 32) // hashes Throw ( - "nodeobject codec: short full inner node"); + "nodeobject codec v1: short full inner node, in_size = " + + std::to_string(in_size)); istream is(p, in_size); result.second = 525; void* const out = bf(result.second); @@ -239,7 +246,9 @@ nodeobject_decompress (void const* in, field::size; // Mask size if (in_size < hs + 65) Throw ( - "nodeobject codec: short inner node"); + "nodeobject codec v2: short inner node size: " + + std::string("size = ") + std::to_string(in_size) + + " hs = " + std::to_string(hs)); istream is(p, in_size); std::uint16_t mask; read(is, mask); // Mask @@ -258,7 +267,7 @@ nodeobject_decompress (void const* in, static_cast(HashPrefix::innerNodeV2)); if (mask == 0) Throw ( - "nodeobject codec: empty inner node"); + "nodeobject codec v2: empty inner node"); std::uint16_t bit = 0x8000; for (int i = 16; i--; bit >>= 1) { @@ -266,7 +275,9 @@ nodeobject_decompress (void const* in, { if (in_size < 32) Throw ( - "nodeobject codec: short inner node"); + "nodeobject codec v2: short inner node subsize: " + + std::string("in_size = ") + std::to_string(in_size) + + " i = " + std::to_string(i)); std::memcpy(os.data(32), is(32), 32); in_size -= 32; } @@ -278,12 +289,15 @@ nodeobject_decompress (void const* in, write(os, depth); if (in_size < (depth+1)/2) Throw ( - "nodeobject codec: short inner node"); + "nodeobject codec v2: short inner node: " + + std::string("size = ") + std::to_string(in_size) + + " depth = " + std::to_string(depth)); std::memcpy(os.data((depth+1)/2), is((depth+1)/2), (depth+1)/2); in_size -= (depth+1)/2; if (in_size > 0) Throw ( - "nodeobject codec: long inner node"); + "nodeobject codec v2: long inner node, in_size = " + + std::to_string(in_size)); break; } case 6: // full v2 inner node @@ -295,7 +309,9 @@ nodeobject_decompress (void const* in, result.second = 525 + 1 + (depth+1)/2; if (in_size != 16 * 32 + (depth+1)/2) // hashes and common Throw ( - "nodeobject codec: short full inner node"); + "nodeobject codec v2: short full inner node: " + + std::string("size = ") + std::to_string(in_size) + + " depth = " + std::to_string(depth)); void* const out = bf(result.second); result.first = out; ostream os(out, result.second); diff --git a/src/ripple/overlay/PeerSet.h b/src/ripple/overlay/PeerSet.h index 1dea551102..0f20d667ec 100644 --- a/src/ripple/overlay/PeerSet.h +++ b/src/ripple/overlay/PeerSet.h @@ -21,11 +21,9 @@ #define RIPPLE_APP_PEERS_PEERSET_H_INCLUDED #include -#include -#include -#include #include #include +#include #include #include #include diff --git a/src/ripple/overlay/impl/ConnectAttempt.cpp b/src/ripple/overlay/impl/ConnectAttempt.cpp index ffc41bbee7..bdb05e15c1 100644 --- a/src/ripple/overlay/impl/ConnectAttempt.cpp +++ b/src/ripple/overlay/impl/ConnectAttempt.cpp @@ -18,14 +18,10 @@ //============================================================================== #include -#include #include #include -#include +#include #include -#include -#include -#include namespace ripple { @@ -81,7 +77,7 @@ ConnectAttempt::run() error_code ec; stream_.next_layer().async_connect (remote_endpoint_, strand_.wrap (std::bind (&ConnectAttempt::onConnect, - shared_from_this(), beast::asio::placeholders::error))); + shared_from_this(), std::placeholders::_1))); } //------------------------------------------------------------------------------ @@ -138,7 +134,7 @@ ConnectAttempt::setTimer() timer_.async_wait(strand_.wrap(std::bind( &ConnectAttempt::onTimer, shared_from_this(), - beast::asio::placeholders::error))); + std::placeholders::_1))); } void @@ -186,7 +182,7 @@ ConnectAttempt::onConnect (error_code ec) stream_.set_verify_mode (boost::asio::ssl::verify_none); stream_.async_handshake (boost::asio::ssl::stream_base::client, strand_.wrap (std::bind (&ConnectAttempt::onHandshake, - shared_from_this(), beast::asio::placeholders::error))); + shared_from_this(), std::placeholders::_1))); } void @@ -221,12 +217,12 @@ ConnectAttempt::onHandshake (error_code ec) overlay_.setup().public_ip, beast::IPAddressConversion::from_asio(remote_endpoint_), app_); - appendHello (req_.fields, hello); + appendHello (req_, hello); setTimer(); beast::http::async_write(stream_, req_, strand_.wrap (std::bind (&ConnectAttempt::onWrite, - shared_from_this(), beast::asio::placeholders::error))); + shared_from_this(), std::placeholders::_1))); } void @@ -241,7 +237,7 @@ ConnectAttempt::onWrite (error_code ec) return fail("onWrite", ec); beast::http::async_read(stream_, read_buf_, response_, strand_.wrap(std::bind(&ConnectAttempt::onRead, - shared_from_this(), beast::asio::placeholders::error))); + shared_from_this(), std::placeholders::_1))); } void @@ -260,7 +256,7 @@ ConnectAttempt::onRead (error_code ec) setTimer(); return stream_.async_shutdown(strand_.wrap(std::bind( &ConnectAttempt::onShutdown, shared_from_this(), - beast::asio::placeholders::error))); + std::placeholders::_1))); } if(ec) return fail("onRead", ec); @@ -290,26 +286,32 @@ ConnectAttempt::makeRequest (bool crawl, request_type { request_type m; - m.method = "GET"; - m.url = "/"; + m.method(beast::http::verb::get); + m.target("/"); m.version = 11; - m.fields.insert ("User-Agent", BuildInfo::getFullVersionString()); - m.fields.insert ("Upgrade", "RTXP/1.2"); + m.insert ("User-Agent", BuildInfo::getFullVersionString()); + m.insert ("Upgrade", "RTXP/1.2"); //std::string("RTXP/") + to_string (BuildInfo::getCurrentProtocol())); - m.fields.insert ("Connection", "Upgrade"); - m.fields.insert ("Connect-As", "Peer"); - m.fields.insert ("Crawl", crawl ? "public" : "private"); + m.insert ("Connection", "Upgrade"); + m.insert ("Connect-As", "Peer"); + m.insert ("Crawl", crawl ? "public" : "private"); return m; } void ConnectAttempt::processResponse() { - if (response_.status == 503) + if (response_.result() == beast::http::status::service_unavailable) { Json::Value json; Json::Reader r; - auto const success = r.parse(beast::to_string(response_.body.data()), json); + std::string s; + s.reserve(boost::asio::buffer_size(response_.body.data())); + for(auto const& buffer : response_.body.data()) + s.append( + boost::asio::buffer_cast(buffer), + boost::asio::buffer_size(buffer)); + auto const success = r.parse(s, json); if (success) { if (json.isObject() && json.isMember("peer-ips")) @@ -339,11 +341,11 @@ ConnectAttempt::processResponse() if (! OverlayImpl::isPeerUpgrade(response_)) { JLOG(journal_.info()) << - "HTTP Response: " << response_.status << " " << response_.reason; + "HTTP Response: " << response_.result() << " " << response_.reason(); return close(); } - auto hello = parseHello (false, response_.fields, journal_); + auto hello = parseHello (false, response_, journal_); if(! hello) return fail("processResponse: Bad TMHello"); diff --git a/src/ripple/overlay/impl/ConnectAttempt.h b/src/ripple/overlay/impl/ConnectAttempt.h index e450dd9a34..d22463e0fd 100644 --- a/src/ripple/overlay/impl/ConnectAttempt.h +++ b/src/ripple/overlay/impl/ConnectAttempt.h @@ -22,26 +22,7 @@ #include "ripple.pb.h" #include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include namespace ripple { @@ -59,7 +40,7 @@ private: beast::http::request; using response_type = - beast::http::response; + beast::http::response; Application& app_; std::uint32_t const id_; @@ -72,7 +53,7 @@ private: std::unique_ptr ssl_bundle_; beast::asio::ssl_bundle::socket_type& socket_; beast::asio::ssl_bundle::stream_type& stream_; - beast::streambuf read_buf_; + beast::multi_buffer read_buf_; response_type response_; PeerFinder::Slot::ptr slot_; request_type req_; diff --git a/src/ripple/overlay/impl/OverlayImpl.cpp b/src/ripple/overlay/impl/OverlayImpl.cpp index f850d74f68..12baa4e0a6 100644 --- a/src/ripple/overlay/impl/OverlayImpl.cpp +++ b/src/ripple/overlay/impl/OverlayImpl.cpp @@ -21,27 +21,16 @@ #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 -#include -#include -#include -#include #include @@ -102,7 +91,7 @@ OverlayImpl::Timer::run() timer_.expires_from_now (std::chrono::seconds(1)); timer_.async_wait(overlay_.strand_.wrap( std::bind(&Timer::on_timer, shared_from_this(), - beast::asio::placeholders::error))); + std::placeholders::_1))); } void @@ -127,7 +116,7 @@ OverlayImpl::Timer::on_timer (error_code ec) timer_.expires_from_now (std::chrono::seconds(1)); timer_.async_wait(overlay_.strand_.wrap(std::bind( &Timer::on_timer, shared_from_this(), - beast::asio::placeholders::error))); + std::placeholders::_1))); } //------------------------------------------------------------------------------ @@ -219,22 +208,22 @@ OverlayImpl::onHandoff (std::unique_ptr && ssl_bundle, { auto const types = beast::rfc2616::split_commas( - request.fields["Connect-As"]); + request["Connect-As"]); if (std::find_if(types.begin(), types.end(), [](std::string const& s) { - return beast::detail::ci_equal(s, "peer"); + return beast::detail::iequals(s, "peer"); }) == types.end()) { handoff.moved = false; handoff.response = makeRedirectResponse(slot, request, remote_endpoint.address()); - handoff.keep_alive = is_keep_alive(request); + handoff.keep_alive = beast::rfc2616::is_keep_alive(request); return handoff; } } - auto hello = parseHello (true, request.fields, journal); + auto hello = parseHello (true, request, journal); if(! hello) { m_peerFinder->on_closed(slot); @@ -285,7 +274,7 @@ OverlayImpl::onHandoff (std::unique_ptr && ssl_bundle, handoff.moved = false; handoff.response = makeRedirectResponse(slot, request, remote_endpoint.address()); - handoff.keep_alive = is_keep_alive(request); + handoff.keep_alive = beast::rfc2616::is_keep_alive(request); return handoff; } @@ -319,21 +308,7 @@ OverlayImpl::isPeerUpgrade(http_request_type const& request) if (! is_upgrade(request)) return false; auto const versions = parse_ProtocolVersions( - request.fields["Upgrade"]); - if (versions.size() == 0) - return false; - return true; -} - -bool -OverlayImpl::isPeerUpgrade(http_response_type const& response) -{ - if (! is_upgrade(response)) - return false; - if(response.status != 101) - return false; - auto const versions = parse_ProtocolVersions( - response.fields["Upgrade"]); + request["Upgrade"]); if (versions.size() == 0) return false; return true; @@ -353,11 +328,11 @@ OverlayImpl::makeRedirectResponse (PeerFinder::Slot::ptr const& slot, { beast::http::response msg; msg.version = request.version; - msg.status = 503; - msg.reason = "Service Unavailable"; - msg.fields.insert("Server", BuildInfo::getFullVersionString()); - msg.fields.insert("Remote-Address", remote_address.to_string()); - msg.fields.insert("Content-Type", "application/json"); + msg.result(beast::http::status::service_unavailable); + msg.insert("Server", BuildInfo::getFullVersionString()); + msg.insert("Remote-Address", remote_address); + msg.insert("Content-Type", "application/json"); + msg.insert(beast::http::field::connection, "close"); msg.body = Json::objectValue; { auto const result = m_peerFinder->redirect(slot); @@ -365,7 +340,7 @@ OverlayImpl::makeRedirectResponse (PeerFinder::Slot::ptr const& slot, for (auto const& _ : m_peerFinder->redirect(slot)) ips.append(_.address.to_string()); } - prepare(msg, beast::http::connection::close); + msg.prepare_payload(); return std::make_shared(msg); } @@ -377,12 +352,12 @@ OverlayImpl::makeErrorResponse (PeerFinder::Slot::ptr const& slot, { beast::http::response msg; msg.version = request.version; - msg.status = 400; - msg.reason = "Bad Request"; - msg.fields.insert("Server", BuildInfo::getFullVersionString()); - msg.fields.insert("Remote-Address", remote_address.to_string()); + msg.result(beast::http::status::bad_request); + msg.insert("Server", BuildInfo::getFullVersionString()); + msg.insert("Remote-Address", remote_address.to_string()); + msg.insert(beast::http::field::connection, "close"); msg.body = text; - prepare(msg, beast::http::connection::close); + msg.prepare_payload(); return std::make_shared(msg); } @@ -827,17 +802,17 @@ bool OverlayImpl::processRequest (http_request_type const& req, Handoff& handoff) { - if (req.url != "/crawl") + if (req.target() != "/crawl") return false; beast::http::response msg; msg.version = req.version; - msg.status = 200; - msg.reason = "OK"; - msg.fields.insert("Server", BuildInfo::getFullVersionString()); - msg.fields.insert("Content-Type", "application/json"); + msg.result(beast::http::status::ok); + msg.insert("Server", BuildInfo::getFullVersionString()); + msg.insert("Content-Type", "application/json"); + msg.insert("Connection", "close"); msg.body["overlay"] = crawl(); - prepare(msg, beast::http::connection::close); + msg.prepare_payload(); handoff.response = std::make_shared(msg); return true; } diff --git a/src/ripple/overlay/impl/OverlayImpl.h b/src/ripple/overlay/impl/OverlayImpl.h index bc1e327c2e..6791c20208 100644 --- a/src/ripple/overlay/impl/OverlayImpl.h +++ b/src/ripple/overlay/impl/OverlayImpl.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -248,9 +249,47 @@ public: bool isPeerUpgrade (http_request_type const& request); + template static bool - isPeerUpgrade (http_response_type const& response); + isPeerUpgrade (beast::http::response const& response) + { + if (! is_upgrade(response)) + return false; + if(response.result() != beast::http::status::switching_protocols) + return false; + auto const versions = parse_ProtocolVersions( + response["Upgrade"]); + if (versions.size() == 0) + return false; + return true; + } + + template + static + bool + is_upgrade(beast::http::header const& req) + { + if(req.version < 11) + return false; + if(req.method() != beast::http::verb::get) + return false; + if(! beast::http::token_list{req["Connection"]}.exists("upgrade")) + return false; + return true; + } + + template + static + bool + is_upgrade(beast::http::header const& req) + { + if(req.version < 11) + return false; + if(! beast::http::token_list{req["Connection"]}.exists("upgrade")) + return false; + return true; + } static std::string diff --git a/src/ripple/overlay/impl/PeerImp.cpp b/src/ripple/overlay/impl/PeerImp.cpp index f31fe60b57..f8d634c131 100644 --- a/src/ripple/overlay/impl/PeerImp.cpp +++ b/src/ripple/overlay/impl/PeerImp.cpp @@ -18,40 +18,26 @@ //============================================================================== #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 -#include -#include #include -#include -#include +#include +#include + #include -#include #include -#include #include #include @@ -91,7 +77,7 @@ PeerImp::PeerImp (Application& app, id_t id, endpoint_type remote_endpoint, , fee_ (Resource::feeLightPeer) , slot_ (slot) , request_(std::move(request)) - , headers_(request_.fields) + , headers_(request_) { } @@ -225,8 +211,8 @@ PeerImp::send (Message::pointer const& m) boost::asio::async_write (stream_, boost::asio::buffer( send_queue_.front()->getBuffer()), strand_.wrap(std::bind( &PeerImp::onWriteMessage, shared_from_this(), - beast::asio::placeholders::error, - beast::asio::placeholders::bytes_transferred))); + std::placeholders::_1, + std::placeholders::_2))); } void @@ -248,7 +234,7 @@ PeerImp::crawl() const auto const iter = headers_.find("Crawl"); if (iter == headers_.end()) return false; - return beast::detail::ci_equal(iter->second, "public"); + return beast::detail::iequals(iter->value(), "public"); } std::string @@ -482,7 +468,7 @@ PeerImp::gracefulClose() return; setTimer(); stream_.async_shutdown(strand_.wrap(std::bind(&PeerImp::onShutdown, - shared_from_this(), beast::asio::placeholders::error))); + shared_from_this(), std::placeholders::_1))); } void @@ -498,7 +484,7 @@ PeerImp::setTimer() return; } timer_.async_wait(strand_.wrap(std::bind(&PeerImp::onTimer, - shared_from_this(), beast::asio::placeholders::error))); + shared_from_this(), std::placeholders::_1))); } // convenience for ignoring the error code @@ -609,9 +595,9 @@ void PeerImp::doAccept() // TODO Apply headers to connection state. - beast::write (write_buffer_, makeResponse( + beast::ostream(write_buffer_) << makeResponse( ! overlay_.peerFinder().config().peerPrivate, - request_, remote_address_, *sharedValue)); + request_, remote_address_, *sharedValue); auto const protocol = BuildInfo::make_protocol(hello_.protoversion()); JLOG(journal_.info()) << "Protocol: " << to_string(protocol); @@ -657,17 +643,16 @@ PeerImp::makeResponse (bool crawl, uint256 const& sharedValue) { http_response_type resp; - resp.status = 101; - resp.reason = "Switching Protocols"; + resp.result(beast::http::status::switching_protocols); resp.version = req.version; - resp.fields.insert("Connection", "Upgrade"); - resp.fields.insert("Upgrade", "RTXP/1.2"); - resp.fields.insert("Connect-AS", "Peer"); - resp.fields.insert("Server", BuildInfo::getFullVersionString()); - resp.fields.insert("Crawl", crawl ? "public" : "private"); + resp.insert("Connection", "Upgrade"); + resp.insert("Upgrade", "RTXP/1.2"); + resp.insert("Connect-As", "Peer"); + resp.insert("Server", BuildInfo::getFullVersionString()); + resp.insert("Crawl", crawl ? "public" : "private"); protocol::TMHello hello = buildHello(sharedValue, overlay_.setup().public_ip, remote, app_); - appendHello(resp.fields, hello); + appendHello(resp, hello); return resp; } @@ -696,8 +681,8 @@ PeerImp::onWriteResponse (error_code ec, std::size_t bytes_transferred) stream_.async_write_some (write_buffer_.data(), strand_.wrap (std::bind (&PeerImp::onWriteResponse, - shared_from_this(), beast::asio::placeholders::error, - beast::asio::placeholders::bytes_transferred))); + shared_from_this(), std::placeholders::_1, + std::placeholders::_2))); } //------------------------------------------------------------------------------ @@ -771,8 +756,8 @@ PeerImp::onReadMessage (error_code ec, std::size_t bytes_transferred) // Timeout on writes only stream_.async_read_some (read_buffer_.prepare (Tuning::readBufferBytes), strand_.wrap (std::bind (&PeerImp::onReadMessage, - shared_from_this(), beast::asio::placeholders::error, - beast::asio::placeholders::bytes_transferred))); + shared_from_this(), std::placeholders::_1, + std::placeholders::_2))); } void @@ -801,15 +786,15 @@ PeerImp::onWriteMessage (error_code ec, std::size_t bytes_transferred) return boost::asio::async_write (stream_, boost::asio::buffer( send_queue_.front()->getBuffer()), strand_.wrap(std::bind( &PeerImp::onWriteMessage, shared_from_this(), - beast::asio::placeholders::error, - beast::asio::placeholders::bytes_transferred))); + std::placeholders::_1, + std::placeholders::_2))); } if (gracefulClose_) { return stream_.async_shutdown(strand_.wrap(std::bind( &PeerImp::onShutdown, shared_from_this(), - beast::asio::placeholders::error))); + std::placeholders::_1))); } } @@ -1095,7 +1080,7 @@ PeerImp::onMessage (std::shared_ptr const& m) flags |= SF_TRUSTED; } - if (! app_.getOPs().getValidationPublicKey().size()) + if (app_.getValidationPublicKey().empty()) { // For now, be paranoid and have each validator // check each transaction, regardless of source @@ -1257,8 +1242,8 @@ PeerImp::onMessage (std::shared_ptr const& m) return; } - if (app_.getOPs().getValidationPublicKey().size() && - publicKey == app_.getOPs().getValidationPublicKey()) + if (!app_.getValidationPublicKey().empty() && + publicKey == app_.getValidationPublicKey()) { JLOG(p_journal_.trace()) << "Proposal: self"; return; @@ -1284,7 +1269,7 @@ PeerImp::onMessage (std::shared_ptr const& m) JLOG(p_journal_.trace()) << "Proposal: " << (isTrusted ? "trusted" : "UNTRUSTED"); - auto proposal = std::make_shared ( + auto proposal = RCLCxPeerPos( publicKey, signature, suppression, RCLCxPeerPos::Proposal{prevLedger, set.proposeseq (), proposeHash, closeTime, app_.timeKeeper().closeTime(),calcNodeID(publicKey)}); @@ -1577,7 +1562,10 @@ PeerImp::onMessage (std::shared_ptr const& m) val->setSeen (closeTime); } - if (! app_.getValidations().current (val)) + if (! isCurrent(app_.getValidations().parms(), + app_.timeKeeper().closeTime(), + val->getSignTime(), + val->getSeenTime())) { JLOG(p_journal_.trace()) << "Validation: Not current"; fee_ = Resource::feeUnwantedData; @@ -1887,7 +1875,7 @@ PeerImp::checkTransaction (int flags, void PeerImp::checkPropose (Job& job, std::shared_ptr const& packet, - RCLCxPeerPos::pointer peerPos) + RCLCxPeerPos peerPos) { bool isTrusted = (job.getType () == jtPROPOSAL_t); @@ -1897,7 +1885,7 @@ PeerImp::checkPropose (Job& job, assert (packet); protocol::TMProposeSet& set = *packet; - if (! cluster() && !peerPos->checkSign ()) + if (! cluster() && !peerPos.checkSign ()) { JLOG(p_journal_.warn()) << "Proposal fails sig check"; @@ -1912,12 +1900,12 @@ PeerImp::checkPropose (Job& job, } else { - if (app_.getOPs().getConsensusLCL() == peerPos->proposal().prevLedger()) + if (app_.getOPs().getConsensusLCL() == peerPos.proposal().prevLedger()) { // relay untrusted proposal JLOG(p_journal_.trace()) << "relaying UNTRUSTED proposal"; - overlay_.relay(set, peerPos->getSuppressionID()); + overlay_.relay(set, peerPos.suppressionID()); } else { diff --git a/src/ripple/overlay/impl/PeerImp.h b/src/ripple/overlay/impl/PeerImp.h index 9b7ed91b9b..3585321bea 100644 --- a/src/ripple/overlay/impl/PeerImp.h +++ b/src/ripple/overlay/impl/PeerImp.h @@ -21,26 +21,16 @@ #define RIPPLE_OVERLAY_PEERIMP_H_INCLUDED #include -#include // deprecated -#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 @@ -151,11 +141,11 @@ private: Resource::Consumer usage_; Resource::Charge fee_; PeerFinder::Slot::ptr slot_; - beast::streambuf read_buffer_; + beast::multi_buffer read_buffer_; http_request_type request_; http_response_type response_; beast::http::fields const& headers_; - beast::streambuf write_buffer_; + beast::multi_buffer write_buffer_; std::queue send_queue_; bool gracefulClose_ = false; int large_sendq_ = 0; @@ -452,7 +442,7 @@ private: void checkPropose (Job& job, std::shared_ptr const& packet, - RCLCxPeerPos::pointer peerPos); + RCLCxPeerPos peerPos); void checkValidation (STValidation::pointer val, @@ -502,7 +492,7 @@ PeerImp::PeerImp (Application& app, std::unique_ptr&& s , fee_ (Resource::feeLightPeer) , slot_ (std::move(slot)) , response_(std::move(response)) - , headers_(response_.fields) + , headers_(response_) { read_buffer_.commit (boost::asio::buffer_copy(read_buffer_.prepare( boost::asio::buffer_size(buffers)), buffers)); diff --git a/src/ripple/overlay/impl/PeerSet.cpp b/src/ripple/overlay/impl/PeerSet.cpp index e89e2b2c32..f63a84ff4c 100644 --- a/src/ripple/overlay/impl/PeerSet.cpp +++ b/src/ripple/overlay/impl/PeerSet.cpp @@ -18,11 +18,10 @@ //============================================================================== #include -#include #include +#include #include #include -#include namespace ripple { diff --git a/src/ripple/overlay/impl/TMHello.cpp b/src/ripple/overlay/impl/TMHello.cpp index 9fae0b4b00..0ea6a5c31c 100644 --- a/src/ripple/overlay/impl/TMHello.cpp +++ b/src/ripple/overlay/impl/TMHello.cpp @@ -18,15 +18,12 @@ //============================================================================== #include +#include #include #include -#include #include #include -#include #include -#include -#include #include #include #include @@ -188,7 +185,7 @@ appendHello (beast::http::fields& h, } std::vector -parse_ProtocolVersions(boost::string_ref const& value) +parse_ProtocolVersions(beast::string_view const& value) { static boost::regex re ( "^" // start of line @@ -233,7 +230,7 @@ parseHello (bool request, beast::http::fields const& h, beast::Journal journal) auto const iter = h.find ("Upgrade"); if (iter == h.end()) return boost::none; - auto const versions = parse_ProtocolVersions(iter->second); + auto const versions = parse_ProtocolVersions(iter->value().to_string()); if (versions.empty()) return boost::none; hello.set_protoversion( @@ -250,10 +247,10 @@ parseHello (bool request, beast::http::fields const& h, beast::Journal journal) if (iter == h.end()) return boost::none; auto const pk = parseBase58( - TokenType::TOKEN_NODE_PUBLIC, iter->second); + TokenType::TOKEN_NODE_PUBLIC, iter->value().to_string()); if (!pk) return boost::none; - hello.set_nodepublic (iter->second); + hello.set_nodepublic (iter->value().to_string()); } { @@ -262,14 +259,14 @@ parseHello (bool request, beast::http::fields const& h, beast::Journal journal) if (iter == h.end()) return boost::none; // TODO Security Review - hello.set_nodeproof (beast::detail::base64_decode (iter->second)); + hello.set_nodeproof (beast::detail::base64_decode (iter->value().to_string())); } { auto const iter = h.find (request ? "User-Agent" : "Server"); if (iter != h.end()) - hello.set_fullversion (iter->second); + hello.set_fullversion (iter->value().to_string()); } { @@ -277,7 +274,7 @@ parseHello (bool request, beast::http::fields const& h, beast::Journal journal) if (iter != h.end()) { std::uint64_t nettime; - if (! beast::lexicalCastChecked(nettime, iter->second)) + if (! beast::lexicalCastChecked(nettime, iter->value().to_string())) return boost::none; hello.set_nettime (nettime); } @@ -288,7 +285,7 @@ parseHello (bool request, beast::http::fields const& h, beast::Journal journal) if (iter != h.end()) { LedgerIndex ledgerIndex; - if (! beast::lexicalCastChecked(ledgerIndex, iter->second)) + if (! beast::lexicalCastChecked(ledgerIndex, iter->value().to_string())) return boost::none; hello.set_ledgerindex (ledgerIndex); } @@ -297,13 +294,13 @@ parseHello (bool request, beast::http::fields const& h, beast::Journal journal) { auto const iter = h.find ("Closed-Ledger"); if (iter != h.end()) - hello.set_ledgerclosed (beast::detail::base64_decode (iter->second)); + hello.set_ledgerclosed (beast::detail::base64_decode (iter->value().to_string())); } { auto const iter = h.find ("Previous-Ledger"); if (iter != h.end()) - hello.set_ledgerprevious (beast::detail::base64_decode (iter->second)); + hello.set_ledgerprevious (beast::detail::base64_decode (iter->value().to_string())); } { @@ -313,7 +310,7 @@ parseHello (bool request, beast::http::fields const& h, beast::Journal journal) bool valid; beast::IP::Address address; std::tie (address, valid) = - beast::IP::Address::from_string (iter->second); + beast::IP::Address::from_string (iter->value().to_string()); if (!valid) return boost::none; if (address.is_v4()) @@ -328,7 +325,7 @@ parseHello (bool request, beast::http::fields const& h, beast::Journal journal) bool valid; beast::IP::Address address; std::tie (address, valid) = - beast::IP::Address::from_string (iter->second); + beast::IP::Address::from_string (iter->value().to_string()); if (!valid) return boost::none; if (address.is_v4()) diff --git a/src/ripple/overlay/impl/TMHello.h b/src/ripple/overlay/impl/TMHello.h index a4ab28175c..ed03f1543e 100644 --- a/src/ripple/overlay/impl/TMHello.h +++ b/src/ripple/overlay/impl/TMHello.h @@ -20,17 +20,15 @@ #ifndef RIPPLE_OVERLAY_TMHELLO_H_INCLUDED #define RIPPLE_OVERLAY_TMHELLO_H_INCLUDED +#include "ripple.pb.h" #include -#include -#include -#include -#include #include -#include +#include + +#include #include #include -#include -#include "ripple.pb.h" +#include namespace ripple { @@ -84,7 +82,7 @@ verifyHello (protocol::TMHello const& h, uint256 const& sharedValue, excluded from the result set. */ std::vector -parse_ProtocolVersions(boost::string_ref const& s); +parse_ProtocolVersions(beast::string_view const& s); } diff --git a/src/ripple/peerfinder/impl/Checker.h b/src/ripple/peerfinder/impl/Checker.h index c432fe1940..982979e161 100644 --- a/src/ripple/peerfinder/impl/Checker.h +++ b/src/ripple/peerfinder/impl/Checker.h @@ -21,7 +21,6 @@ #define RIPPLE_PEERFINDER_CHECKER_H_INCLUDED #include -#include #include #include #include @@ -215,7 +214,7 @@ Checker::async_connect ( } op->socket_.async_connect (beast::IPAddressConversion::to_asio_endpoint ( endpoint), std::bind (&basic_async_op::operator(), op, - beast::asio::placeholders::error)); + std::placeholders::_1)); } template diff --git a/src/ripple/protocol/ErrorCodes.h b/src/ripple/protocol/ErrorCodes.h index e9e31f3ac2..c758ace81c 100644 --- a/src/ripple/protocol/ErrorCodes.h +++ b/src/ripple/protocol/ErrorCodes.h @@ -51,6 +51,7 @@ enum error_code_i rpcHIGH_FEE, rpcNOT_ENABLED, rpcNOT_READY, + rpcAMENDMENT_BLOCKED, // Networking rpcNO_CLOSED, diff --git a/src/ripple/protocol/Feature.h b/src/ripple/protocol/Feature.h index c783f1f995..4a70a88a72 100644 --- a/src/ripple/protocol/Feature.h +++ b/src/ripple/protocol/Feature.h @@ -21,18 +21,127 @@ #define RIPPLE_PROTOCOL_FEATURE_H_INCLUDED #include +#include +#include +#include +#include #include +/** + * @page Feature How to add new features + * + * Steps required to add new features to the code: + * + * 1) add the new feature name to the featureNames array below + * 2) add a uint256 declaration for the feature to the bottom of this file + * 3) add a uint256 definition for the feature to the corresponding source + * file (Feature.cpp) + * 4) if the feature is going to be supported in the near future, add its + * sha512half value and name (matching exactly the featureName here) to the + * supportedAmendments in Amendments.cpp. + * + */ + namespace ripple { -/** Convert feature description to feature id. */ -/** @{ */ -uint256 -feature (std::string const& name); +namespace detail { + +class FeatureCollections +{ + static constexpr char const* const featureNames[] = + {"MultiSign", + "Tickets", + "TrustSetAuth", + "FeeEscalation", + "OwnerPaysFee", + "CompareFlowV1V2", + "SHAMapV2", + "PayChan", + "Flow", + "CompareTakerFlowCross", + "FlowCross", + "CryptoConditions", + "TickSize", + "fix1368", + "Escrow", + "CryptoConditionsSuite", + "fix1373", + "EnforceInvariants", + "SortedDirectories", + "fix1201", + "fix1512"}; + + std::vector features; + boost::container::flat_map featureToIndex; + boost::container::flat_map nameToFeature; + +public: + FeatureCollections(); + + static constexpr std::size_t numFeatures() + { + return sizeof (featureNames) / sizeof (featureNames[0]); + } + + boost::optional + getRegisteredFeature(std::string const& name) const; + + std::size_t + featureToBitsetIndex(uint256 const& f) const; + + uint256 const& + bitsetIndexToFeature(size_t i) const; +}; + +} // detail + +boost::optional +getRegisteredFeature (std::string const& name); + +using FeatureBitset = std::bitset; + +size_t +featureToBitsetIndex(uint256 const& f); uint256 -feature (const char* name); -/** @} */ +bitsetIndexToFeature(size_t i); + +template +void +foreachFeature(FeatureBitset bs, F&& f) +{ + for (size_t i = 0; i < bs.size(); ++i) + if (bs[i]) + f(bitsetIndexToFeature(i)); +} + +template +FeatureBitset +makeFeatureBitset(Col&& fs) +{ + FeatureBitset result; + for (auto const& f : fs) + result.set(featureToBitsetIndex(f)); + return result; +} + +template +FeatureBitset +addFeatures(FeatureBitset bs, Col&& fs) +{ + for (auto const& f : fs) + bs.set(featureToBitsetIndex(f)); + return bs; +} + +template +FeatureBitset +removeFeatures(FeatureBitset bs, Col&& fs) +{ + for (auto const& f : fs) + bs.reset(featureToBitsetIndex(f)); + return bs; +} extern uint256 const featureMultiSign; extern uint256 const featureTickets; @@ -52,6 +161,9 @@ extern uint256 const featureEscrow; extern uint256 const featureCryptoConditionsSuite; extern uint256 const fix1373; extern uint256 const featureEnforceInvariants; +extern uint256 const featureSortedDirectories; +extern uint256 const fix1201; +extern uint256 const fix1512; } // ripple diff --git a/src/ripple/protocol/Protocol.h b/src/ripple/protocol/Protocol.h index 799cf75fc4..e3d4ce81ca 100644 --- a/src/ripple/protocol/Protocol.h +++ b/src/ripple/protocol/Protocol.h @@ -27,24 +27,30 @@ namespace ripple { /** Protocol specific constants, types, and data. - This information is part of the Ripple protocol. Specifically, - it is required for peers to be able to communicate with each other. + This information is, implicitly, part of the Ripple + protocol. - @note Changing these will create a hard fork. - - @ingroup protocol - @defgroup protocol + @note Changing these values without adding code to the + server to detect "pre-change" and "post-change" + will result in a hard fork. */ -struct Protocol -{ - /** Smallest legal byte size of a transaction. - */ - static int const txMinSizeBytes = 32; +/** Smallest legal byte size of a transaction. */ +std::size_t constexpr txMinSizeBytes = 32; - /** Largest legal byte size of a transaction. - */ - static int const txMaxSizeBytes = 1024 * 1024; // 1048576 -}; +/** Largest legal byte size of a transaction. */ +std::size_t constexpr txMaxSizeBytes = 1024 * 1024; + +/** The maximum number of unfunded offers to delete at once */ +std::size_t constexpr unfundedOfferRemoveLimit = 1000; + +/** The maximum number of metadata entries allowed in one transaction */ +std::size_t constexpr oversizeMetaDataCap = 5200; + +/** The maximum number of entries per directory page */ +std::size_t constexpr dirNodeMaxEntries = 32; + +/** The maximum number of pages allowed in a directory */ +std::uint64_t constexpr dirNodeMaxPages = 262144; /** A ledger index. */ using LedgerIndex = std::uint32_t; diff --git a/src/ripple/protocol/PublicKey.h b/src/ripple/protocol/PublicKey.h index e4ec472168..a093658bf0 100644 --- a/src/ripple/protocol/PublicKey.h +++ b/src/ripple/protocol/PublicKey.h @@ -87,6 +87,12 @@ public: return size_; } + bool + empty() const noexcept + { + return size_ == 0; + } + Slice slice() const noexcept { diff --git a/src/ripple/protocol/SField.h b/src/ripple/protocol/SField.h index 9bfeb33cd1..83205b086a 100644 --- a/src/ripple/protocol/SField.h +++ b/src/ripple/protocol/SField.h @@ -416,6 +416,7 @@ extern SF_U256 const sfAmendment; extern SF_U256 const sfTicketID; extern SF_U256 const sfDigest; extern SF_U256 const sfPayChannel; +extern SF_U256 const sfConsensusHash; // currency amount (common) extern SF_Amount const sfAmount; diff --git a/src/ripple/protocol/STValidation.h b/src/ripple/protocol/STValidation.h index 84113e7030..53baabe568 100644 --- a/src/ripple/protocol/STValidation.h +++ b/src/ripple/protocol/STValidation.h @@ -100,26 +100,11 @@ public: // Signs the validation and returns the signing hash uint256 sign (SecretKey const& secretKey); - // The validation this replaced - uint256 const& getPreviousHash () - { - return mPreviousHash; - } - bool isPreviousHash (uint256 const& h) const - { - return mPreviousHash == h; - } - void setPreviousHash (uint256 const& h) - { - mPreviousHash = h; - } - private: static SOTemplate const& getFormat (); void setNode (); - uint256 mPreviousHash; NodeID mNodeID; bool mTrusted = false; NetClock::time_point mSeen = {}; diff --git a/src/ripple/protocol/STVector256.h b/src/ripple/protocol/STVector256.h index 24d1953097..193eebbd6e 100644 --- a/src/ripple/protocol/STVector256.h +++ b/src/ripple/protocol/STVector256.h @@ -144,6 +144,18 @@ public: return mValue; } + std::vector::iterator + insert(std::vector::const_iterator pos, uint256 const& value) + { + return mValue.insert(pos, value); + } + + std::vector::iterator + insert(std::vector::const_iterator pos, uint256&& value) + { + return mValue.insert(pos, std::move(value)); + } + void push_back (uint256 const& v) { diff --git a/src/ripple/protocol/impl/BuildInfo.cpp b/src/ripple/protocol/impl/BuildInfo.cpp index 1be2c702b0..994727eeec 100644 --- a/src/ripple/protocol/impl/BuildInfo.cpp +++ b/src/ripple/protocol/impl/BuildInfo.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -33,7 +34,7 @@ char const* const versionString = // The build version number. You must edit this for each release // and follow the format described at http://semver.org/ // - "0.70.2" + "0.80.0-rc2" #if defined(DEBUG) || defined(SANITIZER) "+" diff --git a/src/ripple/protocol/impl/ErrorCodes.cpp b/src/ripple/protocol/impl/ErrorCodes.cpp index 4a92538728..d0d1e154ac 100644 --- a/src/ripple/protocol/impl/ErrorCodes.cpp +++ b/src/ripple/protocol/impl/ErrorCodes.cpp @@ -53,6 +53,7 @@ public: add (rpcACT_EXISTS, "actExists", "Account already exists."); add (rpcACT_MALFORMED, "actMalformed", "Account malformed."); add (rpcACT_NOT_FOUND, "actNotFound", "Account not found."); + add (rpcAMENDMENT_BLOCKED, "amendmentBlocked", "Amendment blocked, need upgrade."); add (rpcATX_DEPRECATED, "deprecated", "Use the new API or specify a ledger range."); add (rpcBAD_BLOB, "badBlob", "Blob must be a non-empty hex string."); add (rpcBAD_FEATURE, "badFeature", "Feature unknown or invalid."); diff --git a/src/ripple/protocol/impl/Feature.cpp b/src/ripple/protocol/impl/Feature.cpp index a2a2959da4..dbda491a09 100644 --- a/src/ripple/protocol/impl/Feature.cpp +++ b/src/ripple/protocol/impl/Feature.cpp @@ -18,50 +18,103 @@ //============================================================================== #include -#include #include +#include +#include + #include namespace ripple { -static -uint256 -feature (char const* s, std::size_t n) +//------------------------------------------------------------------------------ + +constexpr char const* const detail::FeatureCollections::featureNames[]; + +detail::FeatureCollections::FeatureCollections() { - sha512_half_hasher h; - h(s, n); - return static_cast(h); + features.reserve(numFeatures()); + featureToIndex.reserve(numFeatures()); + nameToFeature.reserve(numFeatures()); + + for (std::size_t i = 0; i < numFeatures(); ++i) + { + auto const name = featureNames[i]; + sha512_half_hasher h; + h (name, std::strlen (name)); + auto const f = static_cast(h); + + features.push_back(f); + featureToIndex[f] = i; + nameToFeature[name] = f; + } } -uint256 -feature (std::string const& name) +boost::optional +detail::FeatureCollections::getRegisteredFeature(std::string const& name) const { - return feature(name.c_str(), name.size()); + auto const i = nameToFeature.find(name); + if (i == nameToFeature.end()) + return boost::none; + return i->second; } -uint256 -feature (const char* name) +size_t +detail::FeatureCollections::featureToBitsetIndex(uint256 const& f) const { - return feature(name, std::strlen(name)); + auto const i = featureToIndex.find(f); + if (i == featureToIndex.end()) + LogicError("Invalid Feature ID"); + return i->second; } -uint256 const featureMultiSign = feature("MultiSign"); -uint256 const featureTickets = feature("Tickets"); -uint256 const featureTrustSetAuth = feature("TrustSetAuth"); -uint256 const featureFeeEscalation = feature("FeeEscalation"); -uint256 const featureOwnerPaysFee = feature("OwnerPaysFee"); -uint256 const featureCompareFlowV1V2 = feature("CompareFlowV1V2"); -uint256 const featureSHAMapV2 = feature("SHAMapV2"); -uint256 const featurePayChan = feature("PayChan"); -uint256 const featureFlow = feature("Flow"); -uint256 const featureCompareTakerFlowCross = feature("CompareTakerFlowCross"); -uint256 const featureFlowCross = feature("FlowCross"); -uint256 const featureCryptoConditions = feature("CryptoConditions"); -uint256 const featureTickSize = feature("TickSize"); -uint256 const fix1368 = feature("fix1368"); -uint256 const featureEscrow = feature("Escrow"); -uint256 const featureCryptoConditionsSuite = feature("CryptoConditionsSuite"); -uint256 const fix1373 = feature("fix1373"); -uint256 const featureEnforceInvariants = feature("EnforceInvariants"); +uint256 const& +detail::FeatureCollections::bitsetIndexToFeature(size_t i) const +{ + if (i >= features.size()) + LogicError("Invalid FeatureBitset index"); + return features[i]; +} + +static detail::FeatureCollections const featureCollections; + +//------------------------------------------------------------------------------ + +boost::optional +getRegisteredFeature (std::string const& name) +{ + return featureCollections.getRegisteredFeature(name); +} + +size_t featureToBitsetIndex(uint256 const& f) +{ + return featureCollections.featureToBitsetIndex(f); +} + +uint256 bitsetIndexToFeature(size_t i) +{ + return featureCollections.bitsetIndexToFeature(i); +} + +uint256 const featureMultiSign = *getRegisteredFeature("MultiSign"); +uint256 const featureTickets = *getRegisteredFeature("Tickets"); +uint256 const featureTrustSetAuth = *getRegisteredFeature("TrustSetAuth"); +uint256 const featureFeeEscalation = *getRegisteredFeature("FeeEscalation"); +uint256 const featureOwnerPaysFee = *getRegisteredFeature("OwnerPaysFee"); +uint256 const featureCompareFlowV1V2 = *getRegisteredFeature("CompareFlowV1V2"); +uint256 const featureSHAMapV2 = *getRegisteredFeature("SHAMapV2"); +uint256 const featurePayChan = *getRegisteredFeature("PayChan"); +uint256 const featureFlow = *getRegisteredFeature("Flow"); +uint256 const featureCompareTakerFlowCross = *getRegisteredFeature("CompareTakerFlowCross"); +uint256 const featureFlowCross = *getRegisteredFeature("FlowCross"); +uint256 const featureCryptoConditions = *getRegisteredFeature("CryptoConditions"); +uint256 const featureTickSize = *getRegisteredFeature("TickSize"); +uint256 const fix1368 = *getRegisteredFeature("fix1368"); +uint256 const featureEscrow = *getRegisteredFeature("Escrow"); +uint256 const featureCryptoConditionsSuite = *getRegisteredFeature("CryptoConditionsSuite"); +uint256 const fix1373 = *getRegisteredFeature("fix1373"); +uint256 const featureEnforceInvariants = *getRegisteredFeature("EnforceInvariants"); +uint256 const featureSortedDirectories = *getRegisteredFeature("SortedDirectories"); +uint256 const fix1201 = *getRegisteredFeature("fix1201"); +uint256 const fix1512 = *getRegisteredFeature("fix1512"); } // ripple diff --git a/src/ripple/protocol/impl/SField.cpp b/src/ripple/protocol/impl/SField.cpp index 228d893ef2..691c1bb8da 100644 --- a/src/ripple/protocol/impl/SField.cpp +++ b/src/ripple/protocol/impl/SField.cpp @@ -169,6 +169,7 @@ SF_U256 const sfAmendment = make::one(&sfAmendment, STI_H SF_U256 const sfTicketID = make::one(&sfTicketID, STI_HASH256, 20, "TicketID"); SF_U256 const sfDigest = make::one(&sfDigest, STI_HASH256, 21, "Digest"); SF_U256 const sfPayChannel = make::one(&sfPayChannel, STI_HASH256, 22, "Channel"); +SF_U256 const sfConsensusHash = make::one(&sfConsensusHash, STI_HASH256, 23, "ConsensusHash"); // currency amount (common) SF_Amount const sfAmount = make::one(&sfAmount, STI_AMOUNT, 1, "Amount"); diff --git a/src/ripple/protocol/impl/STTx.cpp b/src/ripple/protocol/impl/STTx.cpp index 29d6394355..69780cd09d 100644 --- a/src/ripple/protocol/impl/STTx.cpp +++ b/src/ripple/protocol/impl/STTx.cpp @@ -72,7 +72,7 @@ STTx::STTx (SerialIter& sit) { int length = sit.getBytesLeft (); - if ((length < Protocol::txMinSizeBytes) || (length > Protocol::txMaxSizeBytes)) + if ((length < txMinSizeBytes) || (length > txMaxSizeBytes)) Throw ("Transaction length invalid"); set (sit); diff --git a/src/ripple/protocol/impl/STValidation.cpp b/src/ripple/protocol/impl/STValidation.cpp index 714a245d7d..c572de4f53 100644 --- a/src/ripple/protocol/impl/STValidation.cpp +++ b/src/ripple/protocol/impl/STValidation.cpp @@ -161,10 +161,11 @@ SOTemplate const& STValidation::getFormat () format.push_back (SOElement (sfSigningTime, SOE_REQUIRED)); format.push_back (SOElement (sfSigningPubKey, SOE_REQUIRED)); format.push_back (SOElement (sfSignature, SOE_OPTIONAL)); + format.push_back (SOElement (sfConsensusHash, SOE_OPTIONAL)); } }; - static FormatHolder holder; + static const FormatHolder holder; return holder.format; } diff --git a/src/ripple/protocol/impl/TER.cpp b/src/ripple/protocol/impl/TER.cpp index bd4618c4c6..1ce16fb5b4 100644 --- a/src/ripple/protocol/impl/TER.cpp +++ b/src/ripple/protocol/impl/TER.cpp @@ -126,7 +126,7 @@ transResults() { temBAD_SIGNATURE, { "temBAD_SIGNATURE", "Malformed: Bad signature." } }, { temBAD_SIGNER, { "temBAD_SIGNER", "Malformed: No signer may duplicate account or other signers." } }, { temBAD_SRC_ACCOUNT, { "temBAD_SRC_ACCOUNT", "Malformed: Bad source account." } }, - { temBAD_TRANSFER_RATE, { "temBAD_TRANSFER_RATE", "Malformed: Transfer rate must be >= 1.0" } }, + { temBAD_TRANSFER_RATE, { "temBAD_TRANSFER_RATE", "Malformed: Transfer rate must be >= 1.0 and <= 2.0" } }, { temBAD_WEIGHT, { "temBAD_WEIGHT", "Malformed: Weight must be a positive value." } }, { temDST_IS_SRC, { "temDST_IS_SRC", "Destination may not be source." } }, { temDST_NEEDED, { "temDST_NEEDED", "Destination not specified." } }, diff --git a/src/ripple/rpc/handlers/RipplePathFind.cpp b/src/ripple/rpc/handlers/RipplePathFind.cpp index 0e59886fea..48fab268f6 100644 --- a/src/ripple/rpc/handlers/RipplePathFind.cpp +++ b/src/ripple/rpc/handlers/RipplePathFind.cpp @@ -55,9 +55,93 @@ Json::Value doRipplePathFind (RPC::Context& context) PathRequest::pointer request; lpLedger = context.ledgerMaster.getClosedLedger(); + // It doesn't look like there's much odd happening here, but you should + // be aware this code runs in a JobQueue::Coro, which is a coroutine. + // And we may be flipping around between threads. Here's an overview: + // + // 1. We're running doRipplePathFind() due to a call to + // ripple_path_find. doRipplePathFind() is currently running + // inside of a JobQueue::Coro using a JobQueue thread. + // + // 2. doRipplePathFind's call to makeLegacyPathRequest() enqueues the + // path-finding request. That request will (probably) run at some + // indeterminate future time on a (probably different) JobQueue + // thread. + // + // 3. As a continuation from that path-finding JobQueue thread, the + // coroutine we're currently running in (!) is posted to the + // JobQueue. Because it is a continuation, that post won't + // happen until the path-finding request completes. + // + // 4. Once the continuation is enqueued, and we have reason to think + // the path-finding job is likely to run, then the coroutine we're + // running in yield()s. That means it surrenders its thread in + // the JobQueue. The coroutine is suspended, but ready to run, + // because it is kept resident by a shared_ptr in the + // path-finding continuation. + // + // 5. If all goes well then path-finding runs on a JobQueue thread + // and executes its continuation. The continuation posts this + // same coroutine (!) to the JobQueue. + // + // 6. When the JobQueue calls this coroutine, this coroutine resumes + // from the line below the coro->yield() and returns the + // path-finding result. + // + // With so many moving parts, what could go wrong? + // + // Just in terms of the JobQueue refusing to add jobs at shutdown + // there are two specific things that can go wrong. + // + // 1. The path-finding Job queued by makeLegacyPathRequest() might be + // rejected (because we're shutting down). + // + // Fortunately this problem can be addressed by looking at the + // return value of makeLegacyPathRequest(). If + // makeLegacyPathRequest() cannot get a thread to run the path-find + // on, then it returns an empty request. + // + // 2. The path-finding job might run, but the Coro::post() might be + // rejected by the JobQueue (because we're shutting down). + // + // We handle this case by resuming (not posting) the Coro. + // By resuming the Coro, we allow the Coro to run to completion + // on the current thread instead of requiring that it run on a + // new thread from the JobQueue. + // + // Both of these failure modes are hard to recreate in a unit test + // because they are so dependent on inter-thread timing. However + // the failure modes can be observed by synchronously (inside the + // rippled source code) shutting down the application. The code to + // do so looks like this: + // + // context.app.signalStop(); + // while (! context.app.getJobQueue().jobCounter().joined()) { } + // + // The first line starts the process of shutting down the app. + // The second line waits until no more jobs can be added to the + // JobQueue before letting the thread continue. + // + // May 2017 jvResult = context.app.getPathRequests().makeLegacyPathRequest ( - request, std::bind(&JobQueue::Coro::post, context.coro), - context.consumer, lpLedger, context.params); + request, + [&context] () { + // Copying the shared_ptr keeps the coroutine alive up + // through the return. Otherwise the storage under the + // captured reference could evaporate when we return from + // coroCopy->resume(). This is not strictly necessary, but + // will make maintenance easier. + std::shared_ptr coroCopy {context.coro}; + if (!coroCopy->post()) + { + // The post() failed, so we won't get a thread to let + // the Coro finish. We'll call Coro::resume() so the + // Coro can finish on our thread. Otherwise the + // application will hang on shutdown. + coroCopy->resume(); + } + }, + context.consumer, lpLedger, context.params); if (request) { context.coro->yield(); diff --git a/src/ripple/rpc/handlers/Subscribe.cpp b/src/ripple/rpc/handlers/Subscribe.cpp index a14c37f385..ecbf0b3092 100644 --- a/src/ripple/rpc/handlers/Subscribe.cpp +++ b/src/ripple/rpc/handlers/Subscribe.cpp @@ -70,12 +70,18 @@ Json::Value doSubscribe (RPC::Context& context) { JLOG (context.j.debug()) << "doSubscribe: building: " << strUrl; - - auto rspSub = make_RPCSub (context.app.getOPs (), - context.app.getIOService (), context.app.getJobQueue (), - strUrl, strUsername, strPassword, context.app.logs ()); - ispSub = context.netOps.addRpcSub ( - strUrl, std::dynamic_pointer_cast (rspSub)); + try + { + auto rspSub = make_RPCSub (context.app.getOPs (), + context.app.getIOService (), context.app.getJobQueue (), + strUrl, strUsername, strPassword, context.app.logs ()); + ispSub = context.netOps.addRpcSub ( + strUrl, std::dynamic_pointer_cast (rspSub)); + } + catch (std::runtime_error& ex) + { + return RPC::make_param_error (ex.what()); + } } else { @@ -222,8 +228,8 @@ Json::Value doSubscribe (RPC::Context& context) if (! taker_gets.isMember (jss::currency) || !to_currency ( book.out.currency, taker_gets[jss::currency].asString ())) { - JLOG (context.j.info()) << "Bad taker_pays currency."; - return rpcError (rpcSRC_CUR_MALFORMED); + JLOG (context.j.info()) << "Bad taker_gets currency."; + return rpcError (rpcDST_AMT_MALFORMED); } // Parse optional issuer. diff --git a/src/ripple/rpc/handlers/Unsubscribe.cpp b/src/ripple/rpc/handlers/Unsubscribe.cpp index 58c4cddf57..2ec10c7539 100644 --- a/src/ripple/rpc/handlers/Unsubscribe.cpp +++ b/src/ripple/rpc/handlers/Unsubscribe.cpp @@ -29,13 +29,12 @@ namespace ripple { -// FIXME: This leaks RPCSub objects for JSON-RPC. Shouldn't matter for anyone -// sane. Json::Value doUnsubscribe (RPC::Context& context) { InfoSub::pointer ispSub; Json::Value jvResult (Json::objectValue); + bool removeUrl {false}; if (! context.infoSub && ! context.params.isMember(jss::url)) { @@ -52,6 +51,7 @@ Json::Value doUnsubscribe (RPC::Context& context) ispSub = context.netOps.findRpcSub (strUrl); if (! ispSub) return jvResult; + removeUrl = true; } else { @@ -177,9 +177,9 @@ Json::Value doUnsubscribe (RPC::Context& context) || !to_currency (book.out.currency, taker_gets[jss::currency].asString ())) { - JLOG (context.j.info()) << "Bad taker_pays currency."; + JLOG (context.j.info()) << "Bad taker_gets currency."; - return rpcError (rpcSRC_CUR_MALFORMED); + return rpcError (rpcDST_AMT_MALFORMED); } // Parse optional issuer. else if (((taker_gets.isMember (jss::issuer)) @@ -213,6 +213,11 @@ Json::Value doUnsubscribe (RPC::Context& context) } } + if (removeUrl) + { + context.netOps.tryRemoveRpcSub(context.params[jss::url].asString ()); + } + return jvResult; } diff --git a/src/ripple/rpc/impl/RPCHandler.cpp b/src/ripple/rpc/impl/RPCHandler.cpp index 6b0ba38437..5151af425b 100644 --- a/src/ripple/rpc/impl/RPCHandler.cpp +++ b/src/ripple/rpc/impl/RPCHandler.cpp @@ -157,6 +157,13 @@ error_code_i fillHandler (Context& context, return rpcNO_NETWORK; } + if (context.app.getOPs().isAmendmentBlocked() && + (handler->condition_ & NEEDS_CURRENT_LEDGER || + handler->condition_ & NEEDS_CLOSED_LEDGER)) + { + return rpcAMENDMENT_BLOCKED; + } + if (!context.app.config().standalone() && handler->condition_ & NEEDS_CURRENT_LEDGER) { diff --git a/src/ripple/rpc/impl/ServerHandlerImp.cpp b/src/ripple/rpc/impl/ServerHandlerImp.cpp index 338201a60e..0e806c134b 100644 --- a/src/ripple/rpc/impl/ServerHandlerImp.cpp +++ b/src/ripple/rpc/impl/ServerHandlerImp.cpp @@ -52,26 +52,16 @@ namespace ripple { -// Returns `true` if the HTTP request is a Websockets Upgrade -// http://en.wikipedia.org/wiki/HTTP/1.1_Upgrade_header#Use_with_WebSockets -static -bool -isWebsocketUpgrade( - http_request_type const& request) -{ - if (is_upgrade(request)) - return beast::detail::ci_equal( - request.fields["Upgrade"], "websocket"); - return false; -} - static bool isStatusRequest( http_request_type const& request) { - return request.version >= 11 && request.url == "/" && - request.body.size() == 0 && request.method == "GET"; + return + request.version >= 11 && + request.target() == "/" && + request.body.size() == 0 && + request.method() == beast::http::verb::get; } static @@ -83,12 +73,12 @@ unauthorizedResponse( Handoff handoff; response msg; msg.version = request.version; - msg.status = 401; - msg.reason = "Unauthorized"; - msg.fields.insert("Server", BuildInfo::getFullVersionString()); - msg.fields.insert("Content-Type", "text/html"); + msg.result(beast::http::status::unauthorized); + msg.insert("Server", BuildInfo::getFullVersionString()); + msg.insert("Content-Type", "text/html"); + msg.insert("Connection", "close"); msg.body = "Invalid protocol."; - prepare(msg, beast::http::connection::close); + msg.prepare_payload(); handoff.response = std::make_shared(msg); return handoff; } @@ -189,7 +179,7 @@ ServerHandlerImp::onHandoff (Session& session, (session.port().protocol.count("wss") > 0) || (session.port().protocol.count("wss2") > 0); - if(isWebsocketUpgrade(request)) + if(beast::websocket::is_upgrade(request)) { if(is_ws) { @@ -229,7 +219,7 @@ ServerHandlerImp::onHandoff (Session& session, boost::asio::ip::tcp::endpoint remote_address) -> Handoff { - if(isWebsocketUpgrade(request)) + if(beast::websocket::is_upgrade(request)) { if (session.port().protocol.count("ws2") > 0 || session.port().protocol.count("ws") > 0) @@ -261,7 +251,7 @@ ServerHandlerImp::onHandoff (Session& session, static inline Json::Output makeOutput (Session& session) { - return [&](boost::string_ref const& b) + return [&](beast::string_view const& b) { session.write (b.data(), b.size()); }; @@ -275,10 +265,10 @@ build_map(beast::http::fields const& h) std::map c; for (auto const& e : h) { - auto key (e.first); + auto key (e.name_string().to_string()); // TODO Replace with safe C++14 version std::transform (key.begin(), key.end(), key.begin(), ::tolower); - c [key] = e.second; + c [key] = e.value().to_string(); } return c; } @@ -312,18 +302,27 @@ ServerHandlerImp::onRequest (Session& session) // Check user/password authorization if (! authorized ( - session.port(), build_map(session.request().fields))) + session.port(), build_map(session.request()))) { HTTPReply (403, "Forbidden", makeOutput (session), app_.journal ("RPC")); session.close (true); return; } - m_jobQueue.postCoro(jtCLIENT, "RPC-Client", - [this, detach = session.detach()](std::shared_ptr c) + std::shared_ptr detachedSession = session.detach(); + auto const postResult = m_jobQueue.postCoro(jtCLIENT, "RPC-Client", + [this, detachedSession](std::shared_ptr coro) { - processSession(detach, c); + processSession(detachedSession, coro); }); + if (postResult == nullptr) + { + // The coroutine was rejected, probably because we're shutting down. + HTTPReply(503, "Service Unavailable", + makeOutput(*detachedSession), app_.journal("RPC")); + detachedSession->close(true); + return; + } } void @@ -342,7 +341,7 @@ ServerHandlerImp::onWSMessage( jvResult[jss::type] = jss::error; jvResult[jss::error] = "jsonInvalid"; jvResult[jss::value] = buffers_to_string(buffers); - beast::streambuf sb; + beast::multi_buffer sb; Json::stream(jvResult, [&sb](auto const p, auto const n) { @@ -360,21 +359,26 @@ ServerHandlerImp::onWSMessage( JLOG(m_journal.trace()) << "Websocket received '" << jv << "'"; - m_jobQueue.postCoro(jtCLIENT, "WS-Client", - [this, session = std::move(session), - jv = std::move(jv)](auto const& c) + auto const postResult = m_jobQueue.postCoro(jtCLIENT, "WS-Client", + [this, session, jv = std::move(jv)] + (std::shared_ptr const& coro) { auto const jr = - this->processSession(session, c, jv); + this->processSession(session, coro, jv); auto const s = to_string(jr); auto const n = s.length(); - beast::streambuf sb(n); + beast::multi_buffer sb(n); sb.commit(boost::asio::buffer_copy( sb.prepare(n), boost::asio::buffer(s.c_str(), n))); session->send(std::make_shared< StreambufWSMsg>(std::move(sb))); session->complete(); }); + if (postResult == nullptr) + { + // The coroutine was rejected, probably because we're shutting down. + session->close(); + } } void @@ -511,23 +515,23 @@ ServerHandlerImp::processSession (std::shared_ptr const& session, [&] { auto const iter = - session->request().fields.find( + session->request().find( "X-Forwarded-For"); - if(iter != session->request().fields.end()) - return iter->second; + if(iter != session->request().end()) + return iter->value().to_string(); return std::string{}; }(), [&] { auto const iter = - session->request().fields.find( + session->request().find( "X-User"); - if(iter != session->request().fields.end()) - return iter->second; + if(iter != session->request().end()) + return iter->value().to_string(); return std::string{}; }()); - if(is_keep_alive(session->request())) + if(beast::rfc2616::is_keep_alive(session->request())) session->complete(); else session->close (true); @@ -597,7 +601,7 @@ ServerHandlerImp::processRequest (Port const& port, return; } - if (! method) + if (method.isNull()) { usage.charge(Resource::feeInvalidRPC); HTTPReply (400, "Null method", output, rpcJ); @@ -738,8 +742,7 @@ ServerHandlerImp::statusResponse( std::string reason; if (app_.serverOkay(reason)) { - msg.status = 200; - msg.reason = "OK"; + msg.result(beast::http::status::ok); msg.body = "" + systemName() + " Test page for rippled

" + systemName() + " Test

This page shows rippled http(s) " @@ -747,15 +750,15 @@ ServerHandlerImp::statusResponse( } else { - msg.status = 500; - msg.reason = "Internal Server Error"; + msg.result(beast::http::status::internal_server_error); msg.body = "Server cannot accept clients: " + reason + ""; } msg.version = request.version; - msg.fields.insert("Server", BuildInfo::getFullVersionString()); - msg.fields.insert("Content-Type", "text/html"); - prepare(msg, beast::http::connection::close); + msg.insert("Server", BuildInfo::getFullVersionString()); + msg.insert("Content-Type", "text/html"); + msg.insert("Connection", "close"); + msg.prepare_payload(); handoff.response = std::make_shared(msg); return handoff; } @@ -833,6 +836,7 @@ to_Port(ParsedPort const& parsed, std::ostream& log) p.ssl_ciphers = parsed.ssl_ciphers; p.pmd_options = parsed.pmd_options; p.ws_queue_limit = parsed.ws_queue_limit; + p.limit = parsed.limit; return p; } diff --git a/src/ripple/rpc/impl/WSInfoSub.h b/src/ripple/rpc/impl/WSInfoSub.h index e27677b0d9..c53a334716 100644 --- a/src/ripple/rpc/impl/WSInfoSub.h +++ b/src/ripple/rpc/impl/WSInfoSub.h @@ -42,17 +42,17 @@ public: : InfoSub(source) , ws_(ws) { - auto const& h = ws->request().fields; + auto const& h = ws->request(); auto it = h.find("X-User"); if (it != h.end() && isIdentified( ws->port(), beast::IPAddressConversion::from_asio( - ws->remote_endpoint()).address(), it->second)) + ws->remote_endpoint()).address(), it->value().to_string())) { - user_ = it->second; + user_ = it->value().to_string(); it = h.find("X-Forwarded-For"); if (it != h.end()) - fwdfor_ = it->second; + fwdfor_ = it->value().to_string(); } } @@ -74,7 +74,7 @@ public: auto sp = ws_.lock(); if(! sp) return; - beast::streambuf sb; + beast::multi_buffer sb; stream(jv, [&](void const* data, std::size_t n) { diff --git a/src/ripple/rpc/json_body.h b/src/ripple/rpc/json_body.h index 9a92324c42..6dc1e87869 100644 --- a/src/ripple/rpc/json_body.h +++ b/src/ripple/rpc/json_body.h @@ -21,7 +21,7 @@ #define RIPPLE_RPC_JSON_BODY_H #include -#include +#include #include namespace ripple { @@ -31,21 +31,28 @@ struct json_body { using value_type = Json::Value; - class writer + class reader { - beast::streambuf sb_; + using dynamic_buffer_type = beast::multi_buffer; + + dynamic_buffer_type buffer_; public: + using const_buffers_type = + typename dynamic_buffer_type::const_buffers_type; + + using is_deferred = std::false_type; + template explicit - writer(beast::http::message< - isRequest, json_body, Fields> const& m) noexcept + reader(beast::http::message< + isRequest, json_body, Fields> const& m) { stream(m.body, [&](void const* data, std::size_t n) { - sb_.commit(boost::asio::buffer_copy( - sb_.prepare(n), boost::asio::buffer(data, n))); + buffer_.commit(boost::asio::buffer_copy( + buffer_.prepare(n), boost::asio::buffer(data, n))); }); } @@ -54,18 +61,15 @@ struct json_body { } - std::uint64_t - content_length() const noexcept + boost::optional> + get(beast::error_code& ec) { - return sb_.size(); + return {{buffer_.data(), false}}; } - template - bool - write(beast::error_code&, Writef&& wf) noexcept + void + finish(beast::error_code&) { - wf(sb_.data()); - return true; } }; }; diff --git a/src/ripple/server/Handoff.h b/src/ripple/server/Handoff.h index 474d679558..48220545ff 100644 --- a/src/ripple/server/Handoff.h +++ b/src/ripple/server/Handoff.h @@ -22,16 +22,16 @@ #include #include -#include +#include #include namespace ripple { using http_request_type = - beast::http::request; + beast::http::request; using http_response_type = - beast::http::response; + beast::http::response; /** Used to indicate the result of a server connection handoff. */ struct Handoff diff --git a/src/ripple/server/Port.h b/src/ripple/server/Port.h index 527d26554b..e911ebc7ac 100644 --- a/src/ripple/server/Port.h +++ b/src/ripple/server/Port.h @@ -22,7 +22,7 @@ #include #include -#include +#include #include #include #include @@ -40,7 +40,7 @@ struct Port std::string name; boost::asio::ip::address ip; std::uint16_t port = 0; - std::set protocol; + std::set protocol; std::vector admin_ip; std::vector secure_gateway_ip; std::string user; @@ -79,7 +79,7 @@ operator<< (std::ostream& os, Port const& p); struct ParsedPort { std::string name; - std::set protocol; + std::set protocol; std::string user; std::string password; std::string admin_user; diff --git a/src/ripple/server/SimpleWriter.h b/src/ripple/server/SimpleWriter.h index a05d8cfbe7..9d17dbd1cd 100644 --- a/src/ripple/server/SimpleWriter.h +++ b/src/ripple/server/SimpleWriter.h @@ -21,8 +21,8 @@ #define RIPPLE_SERVER_SIMPLEWRITER_H_INCLUDED #include -#include -#include +#include +#include #include #include #include @@ -32,7 +32,7 @@ namespace ripple { /// Deprecated: Writer that serializes a HTTP/1 message class SimpleWriter : public Writer { - beast::streambuf sb_; + beast::multi_buffer sb_; public: template @@ -40,7 +40,7 @@ public: SimpleWriter(beast::http::message< isRequest, Body, Headers> const& msg) { - beast::write(sb_, msg); + beast::ostream(sb_) << msg; } bool diff --git a/src/ripple/server/WSSession.h b/src/ripple/server/WSSession.h index 254d86d643..b89e5d3cc4 100644 --- a/src/ripple/server/WSSession.h +++ b/src/ripple/server/WSSession.h @@ -23,7 +23,7 @@ #include #include #include -#include +#include #include #include #include @@ -98,7 +98,7 @@ public: n_ = sb_.size(); done = true; } - auto const pb = beast::prepare_buffers(n_, sb_.data()); + auto const pb = beast::buffer_prefix(n_, sb_.data()); std::vector vb ( std::distance(pb.begin(), pb.end())); std::copy(pb.begin(), pb.end(), std::back_inserter(vb)); diff --git a/src/ripple/server/impl/BaseHTTPPeer.h b/src/ripple/server/impl/BaseHTTPPeer.h index f9c212d867..90ad13a006 100644 --- a/src/ripple/server/impl/BaseHTTPPeer.h +++ b/src/ripple/server/impl/BaseHTTPPeer.h @@ -24,12 +24,11 @@ #include #include #include -#include #include // for is_short_read? #include #include -#include -#include +#include +#include #include #include #include @@ -288,7 +287,7 @@ start_timer() return fail(ec, "start_timer"); timer_.async_wait(strand_.wrap(std::bind( &BaseHTTPPeer::on_timer, impl().shared_from_this(), - beast::asio::placeholders::error))); + std::placeholders::_1))); } // Convenience for discarding the error code @@ -328,6 +327,8 @@ do_read(yield_context do_yield) beast::http::async_read(impl().stream_, read_buf_, message_, do_yield[ec]); cancel_timer(); + if(ec == beast::http::error::end_of_stream) + return do_close(); if(ec) return fail(ec, "http::read"); do_request(); @@ -358,11 +359,10 @@ on_write(error_code const& ec, for(auto const& b : wq2_) v.emplace_back(b.data.get(), b.bytes); start_timer(); - using namespace beast::asio; return boost::asio::async_write(impl().stream_, v, strand_.wrap(std::bind(&BaseHTTPPeer::on_write, - impl().shared_from_this(), placeholders::error, - placeholders::bytes_transferred))); + impl().shared_from_this(), std::placeholders::_1, + std::placeholders::_2))); } if(! complete_) return; diff --git a/src/ripple/server/impl/BaseWSPeer.h b/src/ripple/server/impl/BaseWSPeer.h index 5e9946837b..a4a2d3861a 100644 --- a/src/ripple/server/impl/BaseWSPeer.h +++ b/src/ripple/server/impl/BaseWSPeer.h @@ -25,7 +25,7 @@ #include #include #include -#include +#include #include #include @@ -48,9 +48,8 @@ private: friend class BasePeer; http_request_type request_; - beast::websocket::opcode op_; - beast::streambuf rb_; - beast::streambuf wb_; + beast::multi_buffer rb_; + beast::multi_buffer wb_; std::list> wq_; bool do_close_ = false; beast::websocket::close_reason cr_; @@ -105,25 +104,6 @@ public: complete() override; protected: - struct identity - { - template - void - operator()(beast::http::message& req) const - { - req.fields.replace("User-Agent", - BuildInfo::getFullVersionString()); - } - - template - void - operator()(beast::http::message& resp) const - { - resp.fields.replace("Server", - BuildInfo::getFullVersionString()); - } - }; - Impl& impl() { @@ -161,8 +141,8 @@ protected: on_ping(error_code const& ec); void - on_ping_pong(bool is_pong, - beast::websocket::ping_data const& payload); + on_ping_pong(beast::websocket::frame_type kind, + beast::string_view payload); void on_timer(error_code ec); @@ -199,17 +179,20 @@ run() if(! strand_.running_in_this_thread()) return strand_.post(std::bind( &BaseWSPeer::run, impl().shared_from_this())); - impl().ws_.set_option(beast::websocket::decorate(identity{})); impl().ws_.set_option(port().pmd_options); - impl().ws_.set_option(beast::websocket::ping_callback{ + impl().ws_.control_callback( std::bind(&BaseWSPeer::on_ping_pong, this, - std::placeholders::_1, std::placeholders::_2)}); - using namespace beast::asio; + std::placeholders::_1, std::placeholders::_2)); start_timer(); close_on_timer_ = true; - impl().ws_.async_accept(request_, strand_.wrap(std::bind( - &BaseWSPeer::on_ws_handshake, impl().shared_from_this(), - placeholders::error))); + impl().ws_.async_accept_ex(request_, + [](auto & res) + { + res.set(beast::http::field::server, + BuildInfo::getFullVersionString()); + }, + strand_.wrap(std::bind(&BaseWSPeer::on_ws_handshake, + impl().shared_from_this(), std::placeholders::_1))); } template @@ -227,7 +210,7 @@ send(std::shared_ptr w) { JLOG(this->j_.info()) << "closing slow client"; - cr_.code = static_cast(4000); + cr_.code = static_cast(4000); cr_.reason = "Client is too slow."; wq_.erase(std::next(wq_.begin()), wq_.end()); close(); @@ -250,7 +233,7 @@ close() if(wq_.empty()) impl().ws_.async_close({}, strand_.wrap(std::bind( &BaseWSPeer::on_close, impl().shared_from_this(), - beast::asio::placeholders::error))); + std::placeholders::_1))); } template @@ -294,7 +277,6 @@ on_write(error_code const& ec) if(ec) return fail(ec, "write"); auto& w = *wq_.front(); - using namespace beast::asio; auto const result = w.prepare(65536, std::bind(&BaseWSPeer::do_write, impl().shared_from_this())); @@ -305,12 +287,12 @@ on_write(error_code const& ec) impl().ws_.async_write_frame( result.first, result.second, strand_.wrap(std::bind( &BaseWSPeer::on_write, impl().shared_from_this(), - placeholders::error))); + std::placeholders::_1))); else impl().ws_.async_write_frame( result.first, result.second, strand_.wrap(std::bind( &BaseWSPeer::on_write_fin, impl().shared_from_this(), - placeholders::error))); + std::placeholders::_1))); } template @@ -324,7 +306,7 @@ on_write_fin(error_code const& ec) if(do_close_) impl().ws_.async_close(cr_, strand_.wrap(std::bind( &BaseWSPeer::on_close, impl().shared_from_this(), - beast::asio::placeholders::error))); + std::placeholders::_1))); else if(! wq_.empty()) on_write({}); } @@ -337,10 +319,9 @@ do_read() if(! strand_.running_in_this_thread()) return strand_.post(std::bind( &BaseWSPeer::do_read, impl().shared_from_this())); - using namespace beast::asio; - impl().ws_.async_read(op_, rb_, strand_.wrap( + impl().ws_.async_read(rb_, strand_.wrap( std::bind(&BaseWSPeer::on_read, - impl().shared_from_this(), placeholders::error))); + impl().shared_from_this(), std::placeholders::_1))); } template @@ -385,7 +366,7 @@ start_timer() return fail(ec, "start_timer"); timer_.async_wait(strand_.wrap(std::bind( &BaseWSPeer::on_timer, impl().shared_from_this(), - beast::asio::placeholders::error))); + std::placeholders::_1))); } // Convenience for discarding the error code @@ -414,12 +395,13 @@ on_ping(error_code const& ec) template void BaseWSPeer:: -on_ping_pong(bool is_pong, - beast::websocket::ping_data const& payload) +on_ping_pong(beast::websocket::frame_type kind, + beast::string_view payload) { - if(is_pong) + if(kind == beast::websocket::frame_type::pong) { - if(payload == payload_) + beast::string_view p(payload_.begin()); + if(payload == p) { close_on_timer_ = false; JLOG(this->j_.trace()) << diff --git a/src/ripple/server/impl/Door.h b/src/ripple/server/impl/Door.h index 3d9020fbc8..322c3e1f9b 100644 --- a/src/ripple/server/impl/Door.h +++ b/src/ripple/server/impl/Door.h @@ -26,8 +26,7 @@ #include #include #include -#include -#include +#include #include #include #include @@ -225,7 +224,7 @@ do_detect(boost::asio::yield_context do_yield) { bool ssl; error_code ec; - beast::streambuf buf(16); + beast::multi_buffer buf(16); timer_.expires_from_now(std::chrono::seconds(15)); std::tie(ec, ssl) = detect_ssl(socket_, buf, do_yield); error_code unused; diff --git a/src/ripple/server/impl/PlainHTTPPeer.h b/src/ripple/server/impl/PlainHTTPPeer.h index ec3e34579d..735511398b 100644 --- a/src/ripple/server/impl/PlainHTTPPeer.h +++ b/src/ripple/server/impl/PlainHTTPPeer.h @@ -20,6 +20,7 @@ #ifndef RIPPLE_SERVER_PLAINHTTPPEER_H_INCLUDED #define RIPPLE_SERVER_PLAINHTTPPEER_H_INCLUDED +#include #include #include #include @@ -132,7 +133,7 @@ do_request() } // Perform half-close when Connection: close and not SSL - if (! is_keep_alive(this->message_)) + if (! beast::rfc2616::is_keep_alive(this->message_)) stream_.shutdown(socket_type::shutdown_receive, ec); if (ec) return this->fail(ec, "request"); diff --git a/src/ripple/server/impl/Port.cpp b/src/ripple/server/impl/Port.cpp index 1d85885871..b3ef671a57 100644 --- a/src/ripple/server/impl/Port.cpp +++ b/src/ripple/server/impl/Port.cpp @@ -188,7 +188,7 @@ parse_Port (ParsedPort& port, Section const& section, std::ostream& log) { auto const lim = get (section, "limit", "unlimited"); - if (!beast::detail::ci_equal (lim, "unlimited")) + if (!beast::detail::iequals (lim, "unlimited")) { try { diff --git a/src/ripple/server/impl/SSLWSPeer.h b/src/ripple/server/impl/SSLWSPeer.h index 383ebfd284..1bf9afe49f 100644 --- a/src/ripple/server/impl/SSLWSPeer.h +++ b/src/ripple/server/impl/SSLWSPeer.h @@ -23,7 +23,6 @@ #include #include #include -#include #include #include diff --git a/src/ripple/unity/app_consensus.cpp b/src/ripple/unity/app_consensus.cpp index 794f351925..e716d88d00 100644 --- a/src/ripple/unity/app_consensus.cpp +++ b/src/ripple/unity/app_consensus.cpp @@ -20,3 +20,5 @@ #include #include +#include + diff --git a/src/ripple/unity/app_ledger.cpp b/src/ripple/unity/app_ledger.cpp index eec781b82d..d38e6d02fd 100644 --- a/src/ripple/unity/app_ledger.cpp +++ b/src/ripple/unity/app_ledger.cpp @@ -28,14 +28,3 @@ #include #include #include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include diff --git a/src/ripple/unity/app_ledger_impl.cpp b/src/ripple/unity/app_ledger_impl.cpp new file mode 100644 index 0000000000..1ea9d02fb7 --- /dev/null +++ b/src/ripple/unity/app_ledger_impl.cpp @@ -0,0 +1,31 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/src/ripple/unity/app_main.cpp b/src/ripple/unity/app_main1.cpp similarity index 88% rename from src/ripple/unity/app_main.cpp rename to src/ripple/unity/app_main1.cpp index be3c746d32..24610ce220 100644 --- a/src/ripple/unity/app_main.cpp +++ b/src/ripple/unity/app_main1.cpp @@ -19,12 +19,8 @@ #include -#include #include #include +#include #include -#include -#include -#include #include -#include diff --git a/src/ripple/unity/app_main2.cpp b/src/ripple/unity/app_main2.cpp new file mode 100644 index 0000000000..35e73a2142 --- /dev/null +++ b/src/ripple/unity/app_main2.cpp @@ -0,0 +1,25 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include + +#include +#include +#include +#include diff --git a/src/ripple/unity/app_misc.cpp b/src/ripple/unity/app_misc.cpp index ae6522f2d9..4b56e546a7 100644 --- a/src/ripple/unity/app_misc.cpp +++ b/src/ripple/unity/app_misc.cpp @@ -24,13 +24,3 @@ #include #include #include -#include - -#include -#include -#include -#include -#include -#include -#include -#include diff --git a/src/ripple/unity/app_misc_impl.cpp b/src/ripple/unity/app_misc_impl.cpp new file mode 100644 index 0000000000..303e442051 --- /dev/null +++ b/src/ripple/unity/app_misc_impl.cpp @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/src/ripple/unity/basics.cpp b/src/ripple/unity/basics.cpp index e24eab78d9..e15ed5faf9 100644 --- a/src/ripple/unity/basics.cpp +++ b/src/ripple/unity/basics.cpp @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include diff --git a/src/ripple/unity/consensus.cpp b/src/ripple/unity/consensus.cpp index 74a8b8aafe..8554a327ce 100644 --- a/src/ripple/unity/consensus.cpp +++ b/src/ripple/unity/consensus.cpp @@ -18,4 +18,4 @@ //============================================================================== #include -#include +#include diff --git a/src/ripple/unity/core.cpp b/src/ripple/unity/core.cpp index b7e5f88464..139331f8bc 100644 --- a/src/ripple/unity/core.cpp +++ b/src/ripple/unity/core.cpp @@ -21,7 +21,6 @@ #include #include -#include #include #include #include diff --git a/src/ripple/unity/ledger.cpp b/src/ripple/unity/ledger.cpp index 82ab45c058..dea59e7f97 100644 --- a/src/ripple/unity/ledger.cpp +++ b/src/ripple/unity/ledger.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include diff --git a/src/ripple/unity/overlay1.cpp b/src/ripple/unity/overlay1.cpp new file mode 100644 index 0000000000..9c3c068206 --- /dev/null +++ b/src/ripple/unity/overlay1.cpp @@ -0,0 +1,25 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include + +#include +#include +#include +#include diff --git a/src/ripple/unity/overlay.cpp b/src/ripple/unity/overlay2.cpp similarity index 87% rename from src/ripple/unity/overlay.cpp rename to src/ripple/unity/overlay2.cpp index 59026ed9ce..3cb1fc6f84 100644 --- a/src/ripple/unity/overlay.cpp +++ b/src/ripple/unity/overlay2.cpp @@ -19,10 +19,6 @@ #include -#include -#include -#include -#include #include #include #include diff --git a/src/ripple/unity/rpcx.cpp b/src/ripple/unity/rpcx1.cpp similarity index 66% rename from src/ripple/unity/rpcx.cpp rename to src/ripple/unity/rpcx1.cpp index 5db0e2e8dc..a023be8335 100644 --- a/src/ripple/unity/rpcx.cpp +++ b/src/ripple/unity/rpcx1.cpp @@ -23,13 +23,9 @@ #include #include - #include - -#include -#include - #include + #include #include #include @@ -62,34 +58,3 @@ #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 - -#include -#include -#include -#include -#include -#include diff --git a/src/ripple/unity/rpcx2.cpp b/src/ripple/unity/rpcx2.cpp new file mode 100644 index 0000000000..3a1a89c2a9 --- /dev/null +++ b/src/ripple/unity/rpcx2.cpp @@ -0,0 +1,61 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include + +// This has to be included early to prevent an obscure MSVC compile error +#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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/src/test/app/AmendmentTable_test.cpp b/src/test/app/AmendmentTable_test.cpp index 1ff93dec32..e2bc0bc151 100644 --- a/src/test/app/AmendmentTable_test.cpp +++ b/src/test/app/AmendmentTable_test.cpp @@ -378,7 +378,7 @@ public: auto const roundTime = weekTime (week); // Build validations - ValidationSet validations; + std::vector validations; validations.reserve (validators.size ()); int i = 0; @@ -402,7 +402,7 @@ public: v->setFieldV256 (sfAmendments, field); v->setTrusted(); - validations [val] = v; + validations.emplace_back(v); } ourVotes = table.doValidation (enabled); @@ -739,8 +739,24 @@ public: testSupportedAmendments () { for (auto const& amend : detail::supportedAmendments ()) - BEAST_EXPECT(amend.substr (0, 64) == - to_string (feature (amend.substr (65)))); + { + auto const f = getRegisteredFeature(amend.substr (65)); + BEAST_EXPECT(f && amend.substr (0, 64) == to_string (*f)); + } + } + + void testHasUnsupported () + { + testcase ("hasUnsupportedEnabled"); + + auto table = makeTable(1); + BEAST_EXPECT(! table->hasUnsupportedEnabled()); + + std::set enabled; + std::for_each(m_set4.begin(), m_set4.end(), + [&enabled](auto const &s){ enabled.insert(amendmentId(s)); }); + table->doValidatedLedger(1, enabled); + BEAST_EXPECT(table->hasUnsupportedEnabled()); } void run () @@ -755,6 +771,7 @@ public: testDetectMajority (); testLostMajority (); testSupportedAmendments (); + testHasUnsupported (); } }; diff --git a/src/test/app/CrossingLimits_test.cpp b/src/test/app/CrossingLimits_test.cpp index a79cd9f15b..e0085ffff0 100644 --- a/src/test/app/CrossingLimits_test.cpp +++ b/src/test/app/CrossingLimits_test.cpp @@ -47,7 +47,7 @@ public: testStepLimit(std::initializer_list fs) { using namespace jtx; - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); auto const xrpMax = XRP(100000000000); auto const gw = Account("gateway"); auto const USD = gw["USD"]; @@ -81,7 +81,7 @@ public: testCrossingLimit(std::initializer_list fs) { using namespace jtx; - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); auto const xrpMax = XRP(100000000000); auto const gw = Account("gateway"); auto const USD = gw["USD"]; @@ -110,7 +110,7 @@ public: testStepAndCrossingLimit(std::initializer_list fs) { using namespace jtx; - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); auto const xrpMax = XRP(100000000000); auto const gw = Account("gateway"); auto const USD = gw["USD"]; diff --git a/src/test/app/DeliverMin_test.cpp b/src/test/app/DeliverMin_test.cpp index 11e3531165..29ca1225ca 100644 --- a/src/test/app/DeliverMin_test.cpp +++ b/src/test/app/DeliverMin_test.cpp @@ -38,7 +38,7 @@ public: auto const USD = gw["USD"]; { - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); env.fund(XRP(10000), "alice", "bob", "carol", gw); env.trust(USD(100), "alice", "bob", "carol"); env(pay("alice", "bob", USD(10)), delivermin(USD(10)), ter(temBAD_AMOUNT)); @@ -61,7 +61,7 @@ public: } { - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); env.fund(XRP(10000), "alice", "bob", gw); env.trust(USD(1000), "alice", "bob"); env(pay(gw, "bob", USD(100))); @@ -73,7 +73,7 @@ public: } { - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); env.fund(XRP(10000), "alice", "bob", "carol", gw); env.trust(USD(1000), "bob", "carol"); env(pay(gw, "bob", USD(200))); @@ -91,7 +91,7 @@ public: } { - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); env.fund(XRP(10000), "alice", "bob", "carol", "dan", gw); env.trust(USD(1000), "bob", "carol", "dan"); env(pay(gw, "bob", USD(100))); diff --git a/src/test/app/Discrepancy_test.cpp b/src/test/app/Discrepancy_test.cpp index ee1d7d429e..54927dfc0f 100644 --- a/src/test/app/Discrepancy_test.cpp +++ b/src/test/app/Discrepancy_test.cpp @@ -41,7 +41,7 @@ class Discrepancy_test : public beast::unit_test::suite { testcase ("Discrepancy test : XRP Discrepancy"); using namespace test::jtx; - Env env {*this, features(fs)}; + Env env {*this, with_features(fs)}; Account A1 {"A1"}; Account A2 {"A2"}; diff --git a/src/test/app/Escrow_test.cpp b/src/test/app/Escrow_test.cpp index 2693baba68..adb861a771 100644 --- a/src/test/app/Escrow_test.cpp +++ b/src/test/app/Escrow_test.cpp @@ -193,7 +193,7 @@ struct Escrow_test : public beast::unit_test::suite using namespace std::chrono; { // Escrow not enabled - Env env(*this); + Env env(*this, no_features); env.fund(XRP(5000), "alice", "bob"); env(lockup("alice", "bob", XRP(1000), env.now() + 1s), ter(temDISABLED)); env(finish("bob", "alice", 1), ter(temDISABLED)); @@ -201,7 +201,7 @@ struct Escrow_test : public beast::unit_test::suite } { // Escrow enabled - Env env(*this, features(featureEscrow)); + Env env(*this, with_features(featureEscrow)); env.fund(XRP(5000), "alice", "bob"); env(lockup("alice", "bob", XRP(1000), env.now() + 1s)); env.close(); @@ -223,7 +223,7 @@ struct Escrow_test : public beast::unit_test::suite using namespace jtx; using namespace std::chrono; - Env env(*this, features(featureEscrow)); + Env env(*this, with_features(featureEscrow)); auto const alice = Account("alice"); env.fund(XRP(5000), alice, "bob"); @@ -247,7 +247,7 @@ struct Escrow_test : public beast::unit_test::suite using namespace jtx; using namespace std::chrono; - Env env(*this, features(featureEscrow)); + Env env(*this, with_features(featureEscrow)); env.fund(XRP(5000), "alice", "bob"); env.close(); @@ -350,7 +350,7 @@ struct Escrow_test : public beast::unit_test::suite using namespace std::chrono; { // Unconditional - Env env(*this, features(featureEscrow)); + Env env(*this, with_features(featureEscrow)); env.fund(XRP(5000), "alice", "bob"); auto const seq = env.seq("alice"); env(lockup("alice", "alice", XRP(1000), env.now() + 1s)); @@ -365,7 +365,7 @@ struct Escrow_test : public beast::unit_test::suite } { // Conditional - Env env(*this, features(featureEscrow)); + Env env(*this, with_features(featureEscrow)); env.fund(XRP(5000), "alice", "bob"); auto const seq = env.seq("alice"); env(lockup("alice", "alice", XRP(1000), makeSlice(cb2), env.now() + 1s)); @@ -398,7 +398,7 @@ struct Escrow_test : public beast::unit_test::suite { // Test cryptoconditions Env env(*this, - features(featureEscrow)); + with_features(featureEscrow)); auto T = [&env](NetClock::duration const& d) { return env.now() + d; }; env.fund(XRP(5000), "alice", "bob", "carol"); @@ -446,7 +446,7 @@ struct Escrow_test : public beast::unit_test::suite { // Test cancel when condition is present Env env(*this, - features(featureEscrow)); + with_features(featureEscrow)); auto T = [&env](NetClock::duration const& d) { return env.now() + d; }; env.fund(XRP(5000), "alice", "bob", "carol"); @@ -463,7 +463,7 @@ struct Escrow_test : public beast::unit_test::suite } { - Env env(*this, features(featureEscrow)); + Env env(*this, with_features(featureEscrow)); auto T = [&env](NetClock::duration const& d) { return env.now() + d; }; env.fund(XRP(5000), "alice", "bob", "carol"); @@ -483,7 +483,7 @@ struct Escrow_test : public beast::unit_test::suite } { // Test long & short conditions during creation - Env env(*this, features(featureEscrow)); + Env env(*this, with_features(featureEscrow)); auto T = [&env](NetClock::duration const& d) { return env.now() + d; }; env.fund(XRP(5000), "alice", "bob", "carol"); @@ -524,7 +524,7 @@ struct Escrow_test : public beast::unit_test::suite { // Test long and short conditions & fulfillments during finish Env env(*this, - features(featureEscrow)); + with_features(featureEscrow)); auto T = [&env](NetClock::duration const& d) { return env.now() + d; }; env.fund(XRP(5000), "alice", "bob", "carol"); @@ -609,7 +609,7 @@ struct Escrow_test : public beast::unit_test::suite { // Test empty condition during creation and // empty condition & fulfillment during finish - Env env(*this, features(featureEscrow)); + Env env(*this, with_features(featureEscrow)); auto T = [&env](NetClock::duration const& d) { return env.now() + d; }; env.fund(XRP(5000), "alice", "bob", "carol"); @@ -649,7 +649,7 @@ struct Escrow_test : public beast::unit_test::suite { // Test a condition other than PreimageSha256, which // would require a separate amendment - Env env(*this, features(featureEscrow)); + Env env(*this, with_features(featureEscrow)); auto T = [&env](NetClock::duration const& d) { return env.now() + d; }; env.fund(XRP(5000), "alice", "bob", "carol"); @@ -677,7 +677,7 @@ struct Escrow_test : public beast::unit_test::suite using namespace jtx; using namespace std::chrono; - Env env(*this, features(featureEscrow)); + Env env(*this, with_features(featureEscrow)); env.fund(XRP(5000), "alice", "bob", "carol"); env(condpay("alice", "carol", XRP(1000), makeSlice(cb1), env.now() + 1s)); @@ -691,7 +691,7 @@ struct Escrow_test : public beast::unit_test::suite using namespace jtx; using namespace std::chrono; - Env env(*this, features(featureEscrow)); + Env env(*this, with_features(featureEscrow)); env.memoize("alice"); env.memoize("bob"); diff --git a/src/test/app/Flow_test.cpp b/src/test/app/Flow_test.cpp index 22f6109625..89393dfcc3 100644 --- a/src/test/app/Flow_test.cpp +++ b/src/test/app/Flow_test.cpp @@ -82,7 +82,7 @@ struct Flow_test : public beast::unit_test::suite auto const USD = gw["USD"]; { // Pay USD, trivial path - Env env (*this, features(fs)); + Env env (*this, with_features(fs)); env.fund (XRP (10000), alice, bob, gw); env.trust (USD (1000), alice, bob); @@ -92,7 +92,7 @@ struct Flow_test : public beast::unit_test::suite } { // XRP transfer - Env env (*this, features(fs)); + Env env (*this, with_features(fs)); env.fund (XRP (10000), alice, bob); env (pay (alice, bob, XRP (100))); @@ -101,7 +101,7 @@ struct Flow_test : public beast::unit_test::suite } { // Partial payments - Env env (*this, features(fs)); + Env env (*this, with_features(fs)); env.fund (XRP (10000), alice, bob, gw); env.trust (USD (1000), alice, bob); @@ -115,7 +115,7 @@ struct Flow_test : public beast::unit_test::suite } { // Pay by rippling through accounts, use path finder - Env env (*this, features(fs)); + Env env (*this, with_features(fs)); env.fund (XRP (10000), alice, bob, carol, dan); env.trust (USDA (10), bob); @@ -130,7 +130,7 @@ struct Flow_test : public beast::unit_test::suite { // Pay by rippling through accounts, specify path // and charge a transfer fee - Env env (*this, features(fs)); + Env env (*this, with_features(fs)); env.fund (XRP (10000), alice, bob, carol, dan); env.trust (USDA (10), bob); @@ -148,7 +148,7 @@ struct Flow_test : public beast::unit_test::suite { // Pay by rippling through accounts, specify path and transfer fee // Test that the transfer fee is not charged when alice issues - Env env (*this, features(fs)); + Env env (*this, with_features(fs)); env.fund (XRP (10000), alice, bob, carol, dan); env.trust (USDA (10), bob); @@ -164,7 +164,7 @@ struct Flow_test : public beast::unit_test::suite { // test best quality path is taken // Paths: A->B->D->E ; A->C->D->E - Env env (*this, features(fs)); + Env env (*this, with_features(fs)); env.fund (XRP (10000), alice, bob, carol, dan, erin); env.trust (USDA (10), bob, carol); @@ -185,7 +185,7 @@ struct Flow_test : public beast::unit_test::suite } { // Limit quality - Env env (*this, features(fs)); + Env env (*this, with_features(fs)); env.fund (XRP (10000), alice, bob, carol); env.trust (USDA (10), bob); @@ -222,7 +222,7 @@ struct Flow_test : public beast::unit_test::suite if (!hasFeature(featureFlow, fs) && bobDanQIn < 100 && bobAliceQOut < 100) continue; // Bug in flow v1 - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); env.fund(XRP(10000), alice, bob, carol, dan); env(trust(bob, USDD(100)), qualityInPercent(bobDanQIn)); env(trust(bob, USDA(100)), qualityOutPercent(bobAliceQOut)); @@ -245,7 +245,7 @@ struct Flow_test : public beast::unit_test::suite // bob -> alice -> carol; vary carolAliceQIn for (auto carolAliceQIn : {80, 100, 120}) { - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); env.fund(XRP(10000), alice, bob, carol); env(trust(bob, USDA(10))); env(trust(carol, USDA(10)), qualityInPercent(carolAliceQIn)); @@ -261,7 +261,7 @@ struct Flow_test : public beast::unit_test::suite // bob -> alice -> carol; bobAliceQOut varies. for (auto bobAliceQOut : {80, 100, 120}) { - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); env.fund(XRP(10000), alice, bob, carol); env(trust(bob, USDA(10)), qualityOutPercent(bobAliceQOut)); env(trust(carol, USDA(10))); @@ -290,7 +290,7 @@ struct Flow_test : public beast::unit_test::suite { // simple IOU/IOU offer - Env env (*this, features(fs)); + Env env (*this, with_features(fs)); env.fund (XRP (10000), alice, bob, carol, gw); env.trust (USD (1000), alice, bob, carol); @@ -311,7 +311,7 @@ struct Flow_test : public beast::unit_test::suite } { // simple IOU/XRP XRP/IOU offer - Env env (*this, features(fs)); + Env env (*this, with_features(fs)); env.fund (XRP (10000), alice, bob, carol, gw); env.trust (USD (1000), alice, bob, carol); @@ -335,7 +335,7 @@ struct Flow_test : public beast::unit_test::suite } { // simple XRP -> USD through offer and sendmax - Env env (*this, features(fs)); + Env env (*this, with_features(fs)); env.fund (XRP (10000), alice, bob, carol, gw); env.trust (USD (1000), alice, bob, carol); @@ -356,7 +356,7 @@ struct Flow_test : public beast::unit_test::suite } { // simple USD -> XRP through offer and sendmax - Env env (*this, features(fs)); + Env env (*this, with_features(fs)); env.fund (XRP (10000), alice, bob, carol, gw); env.trust (USD (1000), alice, bob, carol); @@ -377,7 +377,7 @@ struct Flow_test : public beast::unit_test::suite } { // test unfunded offers are removed when payment succeeds - Env env (*this, features(fs)); + Env env (*this, with_features(fs)); env.fund (XRP (10000), alice, bob, carol, gw); env.trust (USD (1000), alice, bob, carol); @@ -423,7 +423,7 @@ struct Flow_test : public beast::unit_test::suite // offer. When the payment fails `flow` should return the unfunded // offer. This test is intentionally similar to the one that removes // unfunded offers when the payment succeeds. - Env env (*this, features(fs)); + Env env (*this, with_features(fs)); env.fund (XRP (10000), alice, bob, carol, gw); env.trust (USD (1000), alice, bob, carol); @@ -498,7 +498,7 @@ struct Flow_test : public beast::unit_test::suite // Without limits, the 0.4 USD would produce 1000 EUR in the forward // pass. This test checks that the payment produces 1 EUR, as expected. - Env env (*this, features (fs)); + Env env (*this, with_features (fs)); auto const closeTime = STAmountSO::soTime2 + 100 * env.closed ()->info ().closeTimeResolution; @@ -541,7 +541,7 @@ struct Flow_test : public beast::unit_test::suite { // Simple payment through a gateway with a // transfer rate - Env env (*this, features(fs)); + Env env (*this, with_features(fs)); env.fund (XRP (10000), alice, bob, carol, gw); env(rate(gw, 1.25)); @@ -553,7 +553,7 @@ struct Flow_test : public beast::unit_test::suite } { // transfer rate is not charged when issuer is src or dst - Env env (*this, features(fs)); + Env env (*this, with_features(fs)); env.fund (XRP (10000), alice, bob, carol, gw); env(rate(gw, 1.25)); @@ -565,7 +565,7 @@ struct Flow_test : public beast::unit_test::suite } { // transfer fee on an offer - Env env (*this, features(fs)); + Env env (*this, with_features(fs)); env.fund (XRP (10000), alice, bob, carol, gw); env(rate(gw, 1.25)); @@ -583,7 +583,7 @@ struct Flow_test : public beast::unit_test::suite { // Transfer fee two consecutive offers - Env env (*this, features(fs)); + Env env (*this, with_features(fs)); env.fund (XRP (10000), alice, bob, carol, gw); env(rate(gw, 1.25)); @@ -606,7 +606,7 @@ struct Flow_test : public beast::unit_test::suite { // First pass through a strand redeems, second pass issues, no offers // limiting step is not an endpoint - Env env (*this, features(fs)); + Env env (*this, with_features(fs)); auto const USDA = alice["USD"]; auto const USDB = bob["USD"]; @@ -626,7 +626,7 @@ struct Flow_test : public beast::unit_test::suite { // First pass through a strand redeems, second pass issues, through an offer // limiting step is not an endpoint - Env env (*this, features(fs)); + Env env (*this, with_features(fs)); auto const USDA = alice["USD"]; auto const USDB = bob["USD"]; Account const dan ("dan"); @@ -653,7 +653,7 @@ struct Flow_test : public beast::unit_test::suite { // Offer where the owner is also the issuer, owner pays fee - Env env (*this, features(fs)); + Env env (*this, with_features(fs)); env.fund (XRP (10000), alice, bob, gw); env(rate(gw, 1.25)); @@ -668,7 +668,7 @@ struct Flow_test : public beast::unit_test::suite if (!hasFeature(featureOwnerPaysFee, fs)) { // Offer where the owner is also the issuer, sender pays fee - Env env (*this, features(fs)); + Env env (*this, with_features(fs)); env.fund (XRP (10000), alice, bob, gw); env(rate(gw, 1.25)); @@ -696,7 +696,7 @@ struct Flow_test : public beast::unit_test::suite Account const bob ("bob"); Account const carol ("carol"); - Env env (*this, features (fs)); + Env env (*this, with_features (fs)); auto const closeTime = fix1141Time() + 100 * env.closed ()->info ().closeTimeResolution; @@ -747,9 +747,10 @@ struct Flow_test : public beast::unit_test::suite auto const timeDelta = Env{*this}.closed ()->info ().closeTimeResolution; - for(auto const& d: {-timeDelta*100, +timeDelta*100}){ - auto const closeTime = fix1141Time () + d; - Env env (*this); + for (auto const& d : {-100 * timeDelta, +100 * timeDelta}) + { + auto const closeTime = fix1141Time () + d ; + Env env (*this, no_features); env.close (closeTime); env.fund (XRP(10000), alice, bob, carol, gw); @@ -783,7 +784,7 @@ struct Flow_test : public beast::unit_test::suite { std::vector> result; forEachItem (*env.current (), account, - [&env, &result](std::shared_ptr const& sle) + [&result](std::shared_ptr const& sle) { if (sle->getType() == ltOFFER) result.push_back (sle); @@ -809,7 +810,7 @@ struct Flow_test : public beast::unit_test::suite auto const USD = gw1["USD"]; auto const EUR = gw2["EUR"]; - Env env (*this, features (fs)); + Env env (*this, with_features (fs)); auto const closeTime = fix1141Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -883,7 +884,7 @@ struct Flow_test : public beast::unit_test::suite auto const USD = gw1["USD"]; auto const EUR = gw2["EUR"]; - Env env (*this, features (fs)); + Env env (*this, with_features (fs)); auto const closeTime = fix1141Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -951,7 +952,7 @@ struct Flow_test : public beast::unit_test::suite using namespace jtx; - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); // Need new behavior from `accountHolds` auto const closeTime = fix1141Time() + @@ -982,7 +983,7 @@ struct Flow_test : public beast::unit_test::suite using namespace jtx; { // Test reverse - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); auto closeTime = fix1298Time(); if (withFix) closeTime += env.closed()->info().closeTimeResolution; @@ -1014,7 +1015,7 @@ struct Flow_test : public beast::unit_test::suite } { // Test forward - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); auto closeTime = fix1298Time(); if (withFix) closeTime += env.closed()->info().closeTimeResolution; @@ -1054,7 +1055,7 @@ struct Flow_test : public beast::unit_test::suite testcase("ReexecuteDirectStep"); using namespace jtx; - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -1110,9 +1111,9 @@ struct Flow_test : public beast::unit_test::suite testcase("ripd1443"); using namespace jtx; - Env env(*this, features(featureFlow)); + Env env(*this, with_features(featureFlow)); auto const timeDelta = env.closed ()->info ().closeTimeResolution; - auto const d = withFix ? timeDelta*100 : -timeDelta*100; + auto const d = withFix ? 100*timeDelta : -100*timeDelta; auto closeTime = fix1443Time() + d; env.close(closeTime); @@ -1163,9 +1164,9 @@ struct Flow_test : public beast::unit_test::suite testcase("ripd1449"); using namespace jtx; - Env env(*this, features(featureFlow)); + Env env(*this, with_features(featureFlow)); auto const timeDelta = env.closed ()->info ().closeTimeResolution; - auto const d = withFix ? timeDelta*100 : -timeDelta*100; + auto const d = withFix ? 100*timeDelta : -100*timeDelta; auto closeTime = fix1449Time() + d; env.close(closeTime); @@ -1209,7 +1210,7 @@ struct Flow_test : public beast::unit_test::suite using namespace jtx; - Env env(*this, features (fs)); + Env env(*this, with_features (fs)); auto const ann = Account("ann"); auto const gw = Account("gateway"); @@ -1243,7 +1244,7 @@ struct Flow_test : public beast::unit_test::suite auto const alice = Account("alice"); - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); env.fund(XRP(10000), alice); diff --git a/src/test/app/Freeze_test.cpp b/src/test/app/Freeze_test.cpp index 4aa07b89ed..c79816c0fd 100644 --- a/src/test/app/Freeze_test.cpp +++ b/src/test/app/Freeze_test.cpp @@ -58,7 +58,7 @@ class Freeze_test : public beast::unit_test::suite testcase("RippleState Freeze"); using namespace test::jtx; - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); Account G1 {"G1"}; Account alice {"alice"}; @@ -212,7 +212,7 @@ class Freeze_test : public beast::unit_test::suite testcase("Global Freeze"); using namespace test::jtx; - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); Account G1 {"G1"}; Account A1 {"A1"}; @@ -370,7 +370,7 @@ class Freeze_test : public beast::unit_test::suite testcase("No Freeze"); using namespace test::jtx; - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); Account G1 {"G1"}; Account A1 {"A1"}; @@ -424,7 +424,7 @@ class Freeze_test : public beast::unit_test::suite testcase("Offers for Frozen Trust Lines"); using namespace test::jtx; - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); Account G1 {"G1"}; Account A2 {"A2"}; diff --git a/src/test/app/MultiSign_test.cpp b/src/test/app/MultiSign_test.cpp index ef3649cbe8..23fdebaf69 100644 --- a/src/test/app/MultiSign_test.cpp +++ b/src/test/app/MultiSign_test.cpp @@ -39,7 +39,7 @@ public: void test_noReserve() { using namespace jtx; - Env env(*this, features(featureMultiSign)); + Env env(*this, with_features(featureMultiSign)); Account const alice {"alice", KeyType::secp256k1}; // Pay alice enough to meet the initial reserve, but not enough to @@ -87,7 +87,7 @@ public: void test_signerListSet() { using namespace jtx; - Env env(*this, features(featureMultiSign)); + Env env(*this, with_features(featureMultiSign)); Account const alice {"alice", KeyType::ed25519}; env.fund(XRP(1000), alice); @@ -132,7 +132,7 @@ public: void test_phantomSigners() { using namespace jtx; - Env env(*this, features(featureMultiSign)); + Env env(*this, with_features(featureMultiSign)); Account const alice {"alice", KeyType::ed25519}; env.fund(XRP(1000), alice); env.close(); @@ -191,7 +191,7 @@ public: test_enablement() { using namespace jtx; - Env env(*this); + Env env(*this, no_features); Account const alice {"alice", KeyType::ed25519}; env.fund(XRP(1000), alice); env.close(); @@ -233,7 +233,7 @@ public: void test_fee () { using namespace jtx; - Env env(*this, features(featureMultiSign)); + Env env(*this, with_features(featureMultiSign)); Account const alice {"alice", KeyType::ed25519}; env.fund(XRP(1000), alice); env.close(); @@ -283,7 +283,7 @@ public: void test_misorderedSigners() { using namespace jtx; - Env env(*this, features(featureMultiSign)); + Env env(*this, with_features(featureMultiSign)); Account const alice {"alice", KeyType::ed25519}; env.fund(XRP(1000), alice); env.close(); @@ -305,7 +305,7 @@ public: void test_masterSigners() { using namespace jtx; - Env env(*this, features(featureMultiSign)); + Env env(*this, with_features(featureMultiSign)); Account const alice {"alice", KeyType::ed25519}; Account const becky {"becky", KeyType::secp256k1}; Account const cheri {"cheri", KeyType::ed25519}; @@ -357,7 +357,7 @@ public: void test_regularSigners() { using namespace jtx; - Env env(*this, features(featureMultiSign)); + Env env(*this, with_features(featureMultiSign)); Account const alice {"alice", KeyType::secp256k1}; Account const becky {"becky", KeyType::ed25519}; Account const cheri {"cheri", KeyType::secp256k1}; @@ -415,7 +415,7 @@ public: void test_regularSignersUsingSubmitMulti() { using namespace jtx; - Env env(*this, features(featureMultiSign)); + Env env(*this, with_features(featureMultiSign)); Account const alice {"alice", KeyType::secp256k1}; Account const becky {"becky", KeyType::ed25519}; Account const cheri {"cheri", KeyType::secp256k1}; @@ -618,7 +618,7 @@ public: void test_heterogeneousSigners() { using namespace jtx; - Env env(*this, features(featureMultiSign)); + Env env(*this, with_features(featureMultiSign)); Account const alice {"alice", KeyType::secp256k1}; Account const becky {"becky", KeyType::ed25519}; Account const cheri {"cheri", KeyType::secp256k1}; @@ -733,7 +733,7 @@ public: void test_keyDisable() { using namespace jtx; - Env env(*this, features(featureMultiSign)); + Env env(*this, with_features(featureMultiSign)); Account const alice {"alice", KeyType::ed25519}; env.fund(XRP(1000), alice); @@ -808,7 +808,7 @@ public: void test_regKey() { using namespace jtx; - Env env(*this, features(featureMultiSign)); + Env env(*this, with_features(featureMultiSign)); Account const alice {"alice", KeyType::secp256k1}; env.fund(XRP(1000), alice); @@ -840,7 +840,7 @@ public: void test_txTypes() { using namespace jtx; - Env env(*this, features(featureMultiSign)); + Env env(*this, with_features(featureMultiSign)); Account const alice {"alice", KeyType::secp256k1}; Account const becky {"becky", KeyType::ed25519}; Account const zelda {"zelda", KeyType::secp256k1}; @@ -924,7 +924,7 @@ public: // Verify that the text returned for signature failures is correct. using namespace jtx; - Env env(*this, features(featureMultiSign)); + Env env(*this, with_features(featureMultiSign)); // lambda that submits an STTx and returns the resulting JSON. auto submitSTTx = [&env] (STTx const& stx) @@ -1058,7 +1058,7 @@ public: void test_noMultiSigners() { using namespace jtx; - Env env {*this, features(featureMultiSign)}; + Env env {*this, with_features(featureMultiSign)}; Account const alice {"alice", KeyType::ed25519}; Account const becky {"becky", KeyType::secp256k1}; env.fund(XRP(1000), alice, becky); diff --git a/src/test/app/Offer_test.cpp b/src/test/app/Offer_test.cpp index d0d676d184..003b4c06e1 100644 --- a/src/test/app/Offer_test.cpp +++ b/src/test/app/Offer_test.cpp @@ -30,6 +30,14 @@ namespace test { class Offer_test : public beast::unit_test::suite { + static bool hasFeature(uint256 const& feat, std::initializer_list args) + { + for(auto const& f : args) + if (f == feat) + return true; + return false; + } + XRPAmount reserve(jtx::Env& env, std::uint32_t count) { return env.current()->fees().accountReserve (count); @@ -112,7 +120,7 @@ public: // not used for the payment. using namespace jtx; - Env env {*this, features(fs)}; + Env env {*this, with_features(fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -163,7 +171,7 @@ public: testcase ("Removing Canceled Offers"); using namespace jtx; - Env env {*this, features(fs)}; + Env env {*this, with_features(fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -233,7 +241,7 @@ public: auto const USD = gw["USD"]; auto const EUR = gw["EUR"]; - Env env {*this, features(fs)}; + Env env {*this, with_features(fs)}; env.fund (XRP (10000), alice, bob, carol, gw); env.trust (USD (1000), alice, bob, carol); @@ -245,11 +253,10 @@ public: for (int i=0;i<101;++i) env (offer (carol, USD (1), EUR (2))); - for (auto timeDelta : { - - env.closed()->info().closeTimeResolution, - env.closed()->info().closeTimeResolution} ) + for (auto d : {-1, 1}) { - auto const closeTime = STAmountSO::soTime + timeDelta; + auto const closeTime = STAmountSO::soTime + + d * env.closed()->info().closeTimeResolution; env.close (closeTime); *stAmountCalcSwitchover = closeTime > STAmountSO::soTime; // Will fail without the underflow fix @@ -295,7 +302,7 @@ public: if (!withFix && fs.size()) continue; - Env env {*this, features(fs)}; + Env env {*this, with_features(fs)}; auto closeTime = [&] { @@ -376,7 +383,7 @@ public: { // No ripple with an implied account step after an offer - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -405,7 +412,7 @@ public: } { // Make sure payment works with default flags - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -460,7 +467,7 @@ public: // No crossing: { - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -485,7 +492,7 @@ public: // Partial cross: { - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -519,7 +526,7 @@ public: // if an offer were added. Attempt to sell IOUs to // buy XRP. If it fully crosses, we succeed. { - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -561,7 +568,7 @@ public: { std::vector> result; forEachItem (*env.current (), account, - [&env, &result](std::shared_ptr const& sle) + [&result](std::shared_ptr const& sle) { if (sle->getType() == ltOFFER) result.push_back (sle); @@ -585,7 +592,7 @@ public: // Fill or Kill - unless we fully cross, just charge // a fee and not place the offer on the books: { - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -630,7 +637,7 @@ public: // Immediate or Cancel - cross as much as possible // and add nothing on the books: { - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -686,7 +693,7 @@ public: // tfPassive -- place the offer without crossing it. { - Env env (*this, features (fs)); + Env env (*this, with_features (fs)); auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -743,7 +750,7 @@ public: // tfPassive -- cross only offers of better quality. { - Env env (*this, features (fs)); + Env env (*this, with_features (fs)); auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -795,7 +802,7 @@ public: auto const alice = Account {"alice"}; auto const USD = gw["USD"]; - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -899,7 +906,7 @@ public: Json::StaticString const key ("Expiration"); - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -972,7 +979,7 @@ public: auto const usdOffer = USD (1000); auto const xrpOffer = XRP (1000); - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -1038,7 +1045,7 @@ public: auto const USD = gw["USD"]; auto const BTC = gw["BTC"]; - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -1096,8 +1103,7 @@ public: { auto acctOffers = offersOnAccount (env, account_to_test); BEAST_EXPECT(acctOffers.size() == - (std::find (fs.begin(), fs.end(), featureFlowCross) == - fs.end() ? 1 : 0)); + (hasFeature (featureFlowCross, fs) ? 0 : 1)); for (auto const& offerPtr : acctOffers) { auto const& offer = *offerPtr; @@ -1154,7 +1160,7 @@ public: using namespace jtx; - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -1215,8 +1221,7 @@ public: // small_amount and transfer no XRP. The new offer crossing transfers // a single drop, rather than no drops. auto const crossingDelta = - std::find (fs.begin(), fs.end(), featureFlowCross) == - fs.end() ? drops (0) : drops (1); + (hasFeature (featureFlowCross, fs) ? drops (1) : drops (0)); jrr = ledgerEntryState (env, alice, gw, "USD"); BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "50"); @@ -1242,7 +1247,7 @@ public: using namespace jtx; - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -1302,7 +1307,7 @@ public: using namespace jtx; - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -1352,7 +1357,7 @@ public: using namespace jtx; - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -1385,7 +1390,7 @@ public: using namespace jtx; - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -1419,7 +1424,7 @@ public: using namespace jtx; - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -1484,7 +1489,7 @@ public: using namespace jtx; - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -1517,7 +1522,7 @@ public: using namespace jtx; - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -1609,7 +1614,7 @@ public: using namespace jtx; - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -1653,7 +1658,7 @@ public: using namespace jtx; - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -1705,7 +1710,7 @@ public: using namespace jtx; - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -1775,7 +1780,7 @@ public: using namespace jtx; - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -1833,7 +1838,7 @@ public: using namespace jtx; - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -1873,7 +1878,7 @@ public: using namespace jtx; - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -1919,7 +1924,7 @@ public: using namespace jtx; - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -1967,7 +1972,7 @@ public: using namespace jtx; - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -2076,7 +2081,7 @@ public: auto const gw = Account("gateway"); auto const USD = gw["USD"]; - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -2238,7 +2243,7 @@ public: auto const usdOffer = USD(1000); auto const xrpOffer = XRP(1000); - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -2323,7 +2328,7 @@ public: auto const usdOffer = USD(1000); auto const eurOffer = EUR(1000); - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -2420,7 +2425,7 @@ public: auto const usdOffer = USD(1000); auto const eurOffer = EUR(1000); - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -2518,7 +2523,7 @@ public: auto const gw = Account("gateway"); auto const USD = gw["USD"]; - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -2695,7 +2700,7 @@ public: auto const bob = Account("bob"); auto const USD = gw["USD"]; - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -2775,7 +2780,7 @@ public: auto const gw1 = Account("gateway1"); auto const USD = gw1["USD"]; - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -3100,7 +3105,7 @@ public: auto const gw = Account("gateway"); auto const USD = gw["USD"]; - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -3163,7 +3168,7 @@ public: auto const USD = gw1["USD"]; auto const EUR = gw2["EUR"]; - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -3284,7 +3289,7 @@ public: // correctly now. using namespace jtx; - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -3336,9 +3341,7 @@ public: // The problem was identified when featureOwnerPaysFee was enabled, // so make sure that gets included. - std::vector fsPlus (fs); - fsPlus.push_back (featureOwnerPaysFee); - Env env {*this, features (fsPlus)}; + Env env {*this, with_features(fs) | with_features(featureOwnerPaysFee)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -3388,8 +3391,7 @@ public: // Determine which TEC code we expect. TER const tecExpect = - std::find (fs.begin(), fs.end(), featureFlow) == fs.end() - ? tecPATH_DRY : temBAD_PATH; + hasFeature(featureFlow, fs) ? temBAD_PATH : tecPATH_DRY; // This payment caused the assert. env (pay (ann, ann, D_BUX(30)), @@ -3417,7 +3419,7 @@ public: using namespace jtx; - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -3472,7 +3474,7 @@ public: using namespace jtx; - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -3512,7 +3514,7 @@ public: using namespace jtx; - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -3561,7 +3563,7 @@ public: using namespace jtx; - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -3615,7 +3617,7 @@ public: using namespace jtx; - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time () + 100 * env.closed ()->info ().closeTimeResolution; @@ -3674,7 +3676,7 @@ public: using namespace jtx; - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time() + 100 * env.closed()->info().closeTimeResolution; @@ -3757,7 +3759,7 @@ public: using namespace jtx; - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time() + 100 * env.closed()->info().closeTimeResolution; @@ -3909,7 +3911,7 @@ public: using namespace jtx; - Env env {*this, features (fs)}; + Env env {*this, with_features (fs)}; auto const closeTime = fix1449Time() + 100 * env.closed()->info().closeTimeResolution; @@ -3964,8 +3966,7 @@ public: // Pick the right tests. auto const& tests = - std::find (fs.begin(), fs.end(), featureFlowCross) == - fs.end() ? takerTests : flowTests; + hasFeature(featureFlowCross, fs) ? flowTests : takerTests; for (auto const& t : tests) { @@ -4035,28 +4036,83 @@ public: void testRequireAuth (std::initializer_list fs) { - // Only test FlowCross. Results are different with Taker. - if (std::find (fs.begin(), fs.end(), featureFlowCross) == fs.end()) - return; - testcase ("lsfRequireAuth"); + + using namespace jtx; + + Env env {*this, with_features (fs)}; + auto const closeTime = + fix1449Time() + + 100 * env.closed()->info().closeTimeResolution; + env.close (closeTime); + + auto const gw = Account("gw"); + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gwUSD = gw["USD"]; + auto const aliceUSD = alice["USD"]; + auto const bobUSD = bob["USD"]; + + env.fund (XRP(400000), gw, alice, bob); + env.close(); + + // GW requires authorization for holders of its IOUs + env(fset (gw, asfRequireAuth)); + env.close(); + + // Properly set trust and have gw authorize bob and alice + env (trust (gw, bobUSD(100)), txflags (tfSetfAuth)); + env (trust (bob, gwUSD(100))); + env (trust (gw, aliceUSD(100)), txflags (tfSetfAuth)); + env (trust (alice, gwUSD(100))); + // Alice is able to place the offer since the GW has authorized her + env (offer (alice, gwUSD(40), XRP(4000))); + env.close(); + + env.require (offers (alice, 1)); + env.require (balance (alice, gwUSD(0))); + + env (pay(gw, bob, gwUSD(50))); + env.close(); + + env.require (balance (bob, gwUSD(50))); + + // Bob's offer should cross Alice's + env (offer (bob, XRP(4000), gwUSD(40))); + env.close(); + + env.require (offers (alice, 0)); + env.require (balance (alice, gwUSD(40))); + + env.require (offers (bob, 0)); + env.require (balance (bob, gwUSD(10))); + } + + void testMissingAuth (std::initializer_list fs) + { + testcase ("Missing Auth"); // 1. alice creates an offer to acquire USD/gw, an asset for which // she does not have a trust line. At some point in the future, // gw adds lsfRequireAuth. Then, later, alice's offer is crossed. - // alice's offer is deleted, not consumed, since alice is not - // authorized to hold USD/gw. + // a. With Taker alice's unauthorized offer is consumed. + // b. With FlowCross alice's offer is deleted, not consumed, + // since alice is not authorized to hold USD/gw. // // 2. alice tries to create an offer for USD/gw, now that gw has // lsfRequireAuth set. This time the offer create fails because // alice is not authorized to hold USD/gw. // - // 3. Now gw creates a trust line authorizing alice to own USD/gw. - // At this point alice successfully creates and crosses an offer - // for USD/gw. + // 3. Next, gw creates a trust line to alice, but does not set + // tfSetfAuth on that trust line. alice attempts to create an + // offer and again fails. + // + // 4. Finally, gw sets tsfSetAuth on the trust line authorizing + // alice to own USD/gw. At this point alice successfully + // creates and crosses an offer for USD/gw. using namespace jtx; - Env env {*this, fs}; + Env env {*this, with_features(fs)}; auto const closeTime = fix1449Time() + 100 * env.closed()->info().closeTimeResolution; @@ -4090,16 +4146,35 @@ public: env.require (balance (bob, gwUSD(50))); // gw now requires authorization and bob has gwUSD(50). Let's see if - // bob can cross alice's offer. bob's offer shouldn't cross and - // alice's unauthorized offer should be deleted. + // bob can cross alice's offer. + // + // o With Taker bob's offer should cross alice's. + // o With FlowCross bob's offer shouldn't cross and alice's + // unauthorized offer should be deleted. env (offer (bob, XRP(4000), gwUSD(40))); env.close(); + std::uint32_t const bobOfferSeq = env.seq (bob) - 1; + + bool const flowCross = hasFeature (featureFlowCross, fs); env.require (offers (alice, 0)); - env.require (balance (alice, gwUSD(none))); + if (flowCross) + { + // alice's unauthorized offer is deleted & bob's offer not crossed. + env.require (balance (alice, gwUSD(none))); + env.require (offers (bob, 1)); + env.require (balance (bob, gwUSD(50))); + } + else + { + // alice's offer crosses bob's + env.require (balance (alice, gwUSD(40))); + env.require (offers (bob, 0)); + env.require (balance (bob, gwUSD(10))); - env.require (offers (bob, 1)); - env.require (balance (bob, gwUSD(50))); + // The rest of the test verifies FlowCross behavior. + return; + } // See if alice can create an offer without authorization. alice // should not be able to create the offer and bob's offer should be @@ -4113,6 +4188,25 @@ public: env.require (offers (bob, 1)); env.require (balance (bob, gwUSD(50))); + // Set up a trust line for alice, but don't authorize it. alice + // should still not be able to create an offer for USD/gw. + env (trust (gw, aliceUSD(100))); + env.close(); + + env (offer (alice, gwUSD(40), XRP(4000)), ter(tecNO_AUTH)); + env.close(); + + env.require (offers (alice, 0)); + env.require (balance (alice, gwUSD(0))); + + env.require (offers (bob, 1)); + env.require (balance (bob, gwUSD(50))); + + // Delete bob's offer so alice can create an offer without crossing. + env (offer_cancel (bob, bobOfferSeq)); + env.close(); + env.require (offers (bob, 0)); + // Finally, set up an authorized trust line for alice. Now alice's // offer should succeed. Note that, since this is an offer rather // than a payment, alice does not need to set a trust line limit. @@ -4122,6 +4216,12 @@ public: env (offer (alice, gwUSD(40), XRP(4000))); env.close(); + env.require (offers (alice, 1)); + + // Now bob creates his offer again. alice's offer should cross. + env (offer (bob, XRP(4000), gwUSD(40))); + env.close(); + env.require (offers (alice, 0)); env.require (balance (alice, gwUSD(40))); @@ -4129,6 +4229,174 @@ public: env.require (balance (bob, gwUSD(10))); } + void testRCSmoketest(std::initializer_list fs) + { + testcase("RippleConnect Smoketest payment flow"); + using namespace jtx; + + Env env {*this, with_features (fs)}; + auto const closeTime = + fix1449Time() + + 100 * env.closed()->info().closeTimeResolution; + env.close (closeTime); + + // This test mimics the payment flow used in the Ripple Connect + // smoke test. The players: + // A USD gateway with hot and cold wallets + // A EUR gateway with hot and cold walllets + // A MM gateway that will provide offers from USD->EUR and EUR->USD + // A path from hot US to cold EUR is found and then used to send + // USD for EUR that goes through the market maker + + auto const hotUS = Account("hotUS"); + auto const coldUS = Account("coldUS"); + auto const hotEU = Account("hotEU"); + auto const coldEU = Account("coldEU"); + auto const mm = Account("mm"); + + auto const USD = coldUS["USD"]; + auto const EUR = coldEU["EUR"]; + + env.fund (XRP(100000), hotUS, coldUS, hotEU, coldEU, mm); + env.close(); + + // Cold wallets require trust but will ripple by default + for (auto const& cold : {coldUS, coldEU}) + { + env(fset (cold, asfRequireAuth)); + env(fset (cold, asfDefaultRipple)); + } + env.close(); + + // Each hot wallet trusts the related cold wallet for a large amount + env (trust(hotUS, USD(10000000)), txflags (tfSetNoRipple)); + env (trust(hotEU, EUR(10000000)), txflags (tfSetNoRipple)); + // Market maker trusts both cold wallets for a large amount + env (trust(mm, USD(10000000)), txflags (tfSetNoRipple)); + env (trust(mm, EUR(10000000)), txflags (tfSetNoRipple)); + env.close(); + + // Gateways authorize the trustlines of hot and market maker + env (trust (coldUS, USD(0), hotUS, tfSetfAuth)); + env (trust (coldEU, EUR(0), hotEU, tfSetfAuth)); + env (trust (coldUS, USD(0), mm, tfSetfAuth)); + env (trust (coldEU, EUR(0), mm, tfSetfAuth)); + env.close(); + + // Issue currency from cold wallets to hot and market maker + env (pay(coldUS, hotUS, USD(5000000))); + env (pay(coldEU, hotEU, EUR(5000000))); + env (pay(coldUS, mm, USD(5000000))); + env (pay(coldEU, mm, EUR(5000000))); + env.close(); + + // MM places offers + float const rate = 0.9f; // 0.9 USD = 1 EUR + env (offer(mm, EUR(4000000 * rate), USD(4000000)), + json(jss::Flags, tfSell)); + + float const reverseRate = 1.0f/rate * 1.00101f; + env (offer(mm, USD(4000000 * reverseRate), EUR(4000000)), + json(jss::Flags, tfSell)); + env.close(); + + // There should be a path available from hot US to cold EUR + { + Json::Value jvParams; + jvParams[jss::destination_account] = coldEU.human(); + jvParams[jss::destination_amount][jss::issuer] = coldEU.human(); + jvParams[jss::destination_amount][jss::currency] = "EUR"; + jvParams[jss::destination_amount][jss::value] = 10; + jvParams[jss::source_account] = hotUS.human(); + + Json::Value const jrr {env.rpc( + "json", "ripple_path_find", to_string(jvParams))[jss::result]}; + + BEAST_EXPECT(jrr[jss::status] == "success"); + BEAST_EXPECT( + jrr[jss::alternatives].isArray() && + jrr[jss::alternatives].size() > 0); + } + // Send the payment using the found path. + env (pay (hotUS, coldEU, EUR(10)), sendmax (USD(11.1223326))); + } + + void testSelfAuth (std::initializer_list fs) + { + testcase ("Self Auth"); + + using namespace jtx; + + Env env {*this, with_features (fs)}; + auto const closeTime = + fix1449Time() + + 100 * env.closed()->info().closeTimeResolution; + env.close (closeTime); + + auto const gw = Account("gw"); + auto const alice = Account("alice"); + auto const gwUSD = gw["USD"]; + auto const aliceUSD = alice["USD"]; + + env.fund (XRP(400000), gw, alice); + env.close(); + + // Test that gw can create an offer to buy gw's currency. + env (offer (gw, gwUSD(40), XRP(4000))); + env.close(); + std::uint32_t const gwOfferSeq = env.seq (gw) - 1; + env.require (offers (gw, 1)); + + // Since gw has an offer out, gw should not be able to set RequireAuth. + env(fset (gw, asfRequireAuth), ter (tecOWNERS)); + env.close(); + + // Cancel gw's offer so we can set RequireAuth. + env (offer_cancel (gw, gwOfferSeq)); + env.close(); + env.require (offers (gw, 0)); + + // gw now requires authorization for holders of its IOUs + env(fset (gw, asfRequireAuth)); + env.close(); + + // The test behaves differently with or without FlowCross. + bool const flowCross = + std::find (fs.begin(), fs.end(), featureFlowCross) != fs.end(); + + // Before FlowCross an account with lsfRequireAuth set could not + // create an offer to buy their own currency. After FlowCross + // they can. + env (offer (gw, gwUSD(40), XRP(4000)), + ter (flowCross ? tesSUCCESS : tecNO_LINE)); + env.close(); + + env.require (offers (gw, flowCross ? 1 : 0)); + + if (!flowCross) + // The rest of the test verifies FlowCross behavior. + return; + + // Set up an authorized trust line and pay alice gwUSD 50. + env (trust (gw, aliceUSD(100)), txflags (tfSetfAuth)); + env (trust (alice, gwUSD(100))); + env.close(); + + env (pay(gw, alice, gwUSD(50))); + env.close(); + + env.require (balance (alice, gwUSD(50))); + + // alice's offer should cross gw's + env (offer (alice, XRP(4000), gwUSD(40))); + env.close(); + + env.require (offers (alice, 0)); + env.require (balance (alice, gwUSD(10))); + + env.require (offers (gw, 0)); + } + void testTickSize (std::initializer_list fs) { testcase ("Tick Size"); @@ -4137,7 +4405,7 @@ public: // Try to set tick size without enabling feature { - Env env {*this, features(fs)}; + Env env {*this, with_features(fs)}; auto const gw = Account {"gateway"}; env.fund (XRP(10000), gw); @@ -4146,9 +4414,11 @@ public: env(txn, ter(temDISABLED)); } + auto const fsPlus = with_features(fs) | with_features(featureTickSize); + // Try to set tick size out of range { - Env env {*this, features(fs), features (featureTickSize)}; + Env env {*this, fsPlus}; auto const gw = Account {"gateway"}; env.fund (XRP(10000), gw); @@ -4181,7 +4451,7 @@ public: BEAST_EXPECT (! env.le(gw)->isFieldPresent (sfTickSize)); } - Env env {*this, features(fs), features (featureTickSize)}; + Env env {*this, fsPlus}; auto const gw = Account {"gateway"}; auto const alice = Account {"alice"}; auto const XTS = gw["XTS"]; @@ -4300,16 +4570,19 @@ public: testSelfPayXferFeeOffer (fs); testSelfPayUnlimitedFunds (fs); testRequireAuth (fs); + testMissingAuth (fs); + testRCSmoketest (fs); + testSelfAuth (fs); testTickSize (fs); }; -// The following test variants passed at one time in the past (and should -// still pass) but are commented out to conserve test time. -// testAll({ }); -// testAll({ featureFlowCross}); -// testAll({featureFlow }); - testAll({featureFlow, featureFlowCross}); - testAll({featureFlow, fix1373 }); - testAll({featureFlow, fix1373, featureFlowCross}); +// The first three test variants below passed at one time in the past (and +// should still pass) but are commented out to conserve test time. +// testAll(jtx::no_features ); +// testAll({ featureFlowCross }); +// testAll({featureFlow }); + testAll({featureFlow, featureFlowCross }); + testAll({featureFlow, fix1373 }); + testAll({featureFlow, fix1373, featureFlowCross }); } }; diff --git a/src/test/app/Path_test.cpp b/src/test/app/Path_test.cpp index fd6be84cb9..c119fa3b42 100644 --- a/src/test/app/Path_test.cpp +++ b/src/test/app/Path_test.cpp @@ -1025,7 +1025,8 @@ public: { testcase("Path Find: CNY"); using namespace jtx; - Env env(*this); + Env env{*this, all_features_except(featureFlow)}; + Account A1 {"A1"}; Account A2 {"A2"}; Account A3 {"A3"}; diff --git a/src/test/app/PayChan_test.cpp b/src/test/app/PayChan_test.cpp index cb2b7f70bb..f2f5dfc749 100644 --- a/src/test/app/PayChan_test.cpp +++ b/src/test/app/PayChan_test.cpp @@ -175,7 +175,7 @@ struct PayChan_test : public beast::unit_test::suite testcase ("simple"); using namespace jtx; using namespace std::literals::chrono_literals; - Env env (*this, features (featurePayChan)); + Env env (*this); auto const alice = Account ("alice"); auto const bob = Account ("bob"); auto USDA = alice["USD"]; @@ -242,7 +242,7 @@ struct PayChan_test : public beast::unit_test::suite auto const reqBal = chanBal + delta; auto const authAmt = reqBal + XRP (-100); assert (reqBal <= chanAmt); - env (claim (alice, chan, reqBal, authAmt), ter (tecNO_PERMISSION)); + env (claim (alice, chan, reqBal, authAmt), ter (temBAD_AMOUNT)); } { // No signature needed since the owner is claiming @@ -290,11 +290,10 @@ struct PayChan_test : public beast::unit_test::suite auto const sig = signClaimAuth (alice.pk (), alice.sk (), chan, authAmt); env (claim (bob, chan, reqAmt, authAmt, Slice (sig), alice.pk ()), - ter (tecNO_PERMISSION)); + ter (temBAD_AMOUNT)); BEAST_EXPECT (channelBalance (*env.current (), chan) == chanBal); BEAST_EXPECT (channelAmount (*env.current (), chan) == chanAmt); - auto const feeDrops = env.current ()->fees ().base; - BEAST_EXPECT (env.balance (bob) == preBob - feeDrops); + BEAST_EXPECT (env.balance (bob) == preBob); } // Dst tries to fund the channel @@ -347,7 +346,7 @@ struct PayChan_test : public beast::unit_test::suite auto const carol = Account ("carol"); { // If dst claims after cancel after, channel closes - Env env (*this, features (featurePayChan)); + Env env (*this); env.fund (XRP (10000), alice, bob); auto const pk = alice.pk (); auto const settleDelay = 100s; @@ -386,7 +385,7 @@ struct PayChan_test : public beast::unit_test::suite } { // Third party can close after cancel after - Env env (*this, features (featurePayChan)); + Env env (*this); env.fund (XRP (10000), alice, bob, carol); auto const pk = alice.pk (); auto const settleDelay = 100s; @@ -416,7 +415,7 @@ struct PayChan_test : public beast::unit_test::suite testcase ("expiration"); using namespace jtx; using namespace std::literals::chrono_literals; - Env env (*this, features (featurePayChan)); + Env env (*this); auto const alice = Account ("alice"); auto const bob = Account ("bob"); auto const carol = Account ("carol"); @@ -477,7 +476,7 @@ struct PayChan_test : public beast::unit_test::suite testcase ("settle delay"); using namespace jtx; using namespace std::literals::chrono_literals; - Env env (*this, features (featurePayChan)); + Env env (*this); auto const alice = Account ("alice"); auto const bob = Account ("bob"); env.fund (XRP (10000), alice, bob); @@ -537,7 +536,7 @@ struct PayChan_test : public beast::unit_test::suite testcase ("close dry"); using namespace jtx; using namespace std::literals::chrono_literals; - Env env (*this, features (featurePayChan)); + Env env (*this); auto const alice = Account ("alice"); auto const bob = Account ("bob"); env.fund (XRP (10000), alice, bob); @@ -573,7 +572,7 @@ struct PayChan_test : public beast::unit_test::suite testcase ("default amount"); using namespace jtx; using namespace std::literals::chrono_literals; - Env env (*this, features (featurePayChan)); + Env env (*this); auto const alice = Account ("alice"); auto const bob = Account ("bob"); env.fund (XRP (10000), alice, bob); @@ -632,7 +631,7 @@ struct PayChan_test : public beast::unit_test::suite using namespace std::literals::chrono_literals; { // Create a channel where dst disallows XRP - Env env (*this, features (featurePayChan)); + Env env (*this); auto const alice = Account ("alice"); auto const bob = Account ("bob"); env.fund (XRP (10000), alice, bob); @@ -648,7 +647,7 @@ struct PayChan_test : public beast::unit_test::suite { // Claim to a channel where dst disallows XRP // (channel is created before disallow xrp is set) - Env env (*this, features (featurePayChan)); + Env env (*this); auto const alice = Account ("alice"); auto const bob = Account ("bob"); env.fund (XRP (10000), alice, bob); @@ -674,7 +673,7 @@ struct PayChan_test : public beast::unit_test::suite using namespace jtx; using namespace std::literals::chrono_literals; // Create a channel where dst disallows XRP - Env env (*this, features (featurePayChan)); + Env env (*this); auto const alice = Account ("alice"); auto const bob = Account ("bob"); env.fund (XRP (10000), alice, bob); @@ -699,7 +698,7 @@ struct PayChan_test : public beast::unit_test::suite testcase ("Multiple channels to the same account"); using namespace jtx; using namespace std::literals::chrono_literals; - Env env (*this, features (featurePayChan)); + Env env (*this); auto const alice = Account ("alice"); auto const bob = Account ("bob"); env.fund (XRP (10000), alice, bob); @@ -721,7 +720,7 @@ struct PayChan_test : public beast::unit_test::suite testcase ("RPC"); using namespace jtx; using namespace std::literals::chrono_literals; - Env env (*this, features (featurePayChan)); + Env env (*this); auto const alice = Account ("alice"); auto const bob = Account ("bob"); env.fund (XRP (10000), alice, bob); @@ -791,7 +790,7 @@ struct PayChan_test : public beast::unit_test::suite testcase ("Optional Fields"); using namespace jtx; using namespace std::literals::chrono_literals; - Env env (*this, features (featurePayChan)); + Env env (*this); auto const alice = Account ("alice"); auto const bob = Account ("bob"); auto const carol = Account ("carol"); @@ -831,7 +830,7 @@ struct PayChan_test : public beast::unit_test::suite testcase ("malformed pk"); using namespace jtx; using namespace std::literals::chrono_literals; - Env env (*this, features (featurePayChan)); + Env env (*this); auto const alice = Account ("alice"); auto const bob = Account ("bob"); auto USDA = alice["USD"]; diff --git a/src/test/app/PayStrand_test.cpp b/src/test/app/PayStrand_test.cpp index 3eefff6286..083a700918 100644 --- a/src/test/app/PayStrand_test.cpp +++ b/src/test/app/PayStrand_test.cpp @@ -632,7 +632,7 @@ struct PayStrandAllPairs_test : public beast::unit_test::suite using RippleCalc = ::ripple::path::RippleCalc; ExistingElementPool eep; - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); auto const closeTime = fix1298Time() + 100 * env.closed()->info().closeTimeResolution; @@ -910,7 +910,7 @@ struct PayStrand_test : public beast::unit_test::suite }; { - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); env.fund(XRP(10000), alice, bob, gw); env.trust(USD(1000), alice, bob); env.trust(EUR(1000), alice, bob); @@ -951,7 +951,7 @@ struct PayStrand_test : public beast::unit_test::suite }; { - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); env.fund(XRP(10000), alice, bob, carol, gw); test(env, USD, boost::none, STPath(), terNO_LINE); @@ -1134,7 +1134,7 @@ struct PayStrand_test : public beast::unit_test::suite // cannot have more than one offer with the same output issue using namespace jtx; - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); env.fund(XRP(10000), alice, bob, carol, gw); env.trust(USD(10000), alice, bob, carol); @@ -1156,7 +1156,7 @@ struct PayStrand_test : public beast::unit_test::suite } { - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); env.fund(XRP(10000), alice, bob, noripple(gw)); env.trust(USD(1000), alice, bob); env(pay(gw, alice, USD(100))); @@ -1165,7 +1165,7 @@ struct PayStrand_test : public beast::unit_test::suite { // check global freeze - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); env.fund(XRP(10000), alice, bob, gw); env.trust(USD(1000), alice, bob); env(pay(gw, alice, USD(100))); @@ -1190,7 +1190,7 @@ struct PayStrand_test : public beast::unit_test::suite } { // Freeze between gw and alice - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); env.fund(XRP(10000), alice, bob, gw); env.trust(USD(1000), alice, bob); env(pay(gw, alice, USD(100))); @@ -1203,7 +1203,7 @@ struct PayStrand_test : public beast::unit_test::suite // check no auth // An account may require authorization to receive IOUs from an // issuer - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); env.fund(XRP(10000), alice, bob, gw); env(fset(gw, asfRequireAuth)); env.trust(USD(1000), alice, bob); @@ -1231,7 +1231,7 @@ struct PayStrand_test : public beast::unit_test::suite } { // Check path with sendMax and node with correct sendMax already set - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); env.fund(XRP(10000), alice, bob, gw); env.trust(USD(1000), alice, bob); env.trust(EUR(1000), alice, bob); @@ -1246,7 +1246,7 @@ struct PayStrand_test : public beast::unit_test::suite { // last step xrp from offer - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); env.fund(XRP(10000), alice, bob, gw); env.trust(USD(1000), alice, bob); env(pay(gw, alice, USD(100))); @@ -1287,7 +1287,7 @@ struct PayStrand_test : public beast::unit_test::suite if (hasFeature(fix1373, fs)) { - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); env.fund(XRP(10000), alice, bob, gw); env.trust(USD(1000), alice, bob); @@ -1319,7 +1319,7 @@ struct PayStrand_test : public beast::unit_test::suite } { - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); env.fund(XRP(10000), alice, bob, carol, gw); env.trust(USD(10000), alice, bob, carol); @@ -1337,7 +1337,7 @@ struct PayStrand_test : public beast::unit_test::suite } { - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); env.fund(XRP(10000), alice, bob, carol, gw); env.trust(USD(10000), alice, bob, carol); @@ -1371,7 +1371,7 @@ struct PayStrand_test : public beast::unit_test::suite auto const CNY = gw["CNY"]; { - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); env.fund(XRP(10000), alice, bob, carol, gw); env.trust(USD(10000), alice, bob, carol); @@ -1396,7 +1396,7 @@ struct PayStrand_test : public beast::unit_test::suite ter(expectedResult)); } { - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); env.fund(XRP(10000), alice, bob, carol, gw); env.trust(USD(10000), alice, bob, carol); @@ -1431,7 +1431,7 @@ struct PayStrand_test : public beast::unit_test::suite auto const gw = Account("gw"); auto const USD = gw["USD"]; - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); env.fund(XRP(10000), alice, bob, gw); STAmount sendMax{USD.issue(), 100, 1}; diff --git a/src/test/app/Regression_test.cpp b/src/test/app/Regression_test.cpp index debf65c509..7cd62d01fd 100644 --- a/src/test/app/Regression_test.cpp +++ b/src/test/app/Regression_test.cpp @@ -171,7 +171,7 @@ struct Regression_test : public beast::unit_test::suite .set("minimum_txn_in_ledger_standalone", "3"); return cfg; }), - features(featureFeeEscalation)); + with_features(featureFeeEscalation)); Env_ss envs(env); auto const alice = Account("alice"); diff --git a/src/test/app/SetAuth_test.cpp b/src/test/app/SetAuth_test.cpp index e9f49c92b6..25f8b149e9 100644 --- a/src/test/app/SetAuth_test.cpp +++ b/src/test/app/SetAuth_test.cpp @@ -52,13 +52,13 @@ struct SetAuth_test : public beast::unit_test::suite auto const gw = Account("gw"); auto const USD = gw["USD"]; { - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); env.fund(XRP(100000), "alice", gw); env(fset(gw, asfRequireAuth)); env(auth(gw, "alice", "USD"), ter(tecNO_LINE_REDUNDANT)); } { - Env env(*this, features(featureTrustSetAuth)); + Env env(*this, with_features(featureTrustSetAuth)); env.fund(XRP(100000), "alice", "bob", gw); env(fset(gw, asfRequireAuth)); env(auth(gw, "alice", "USD")); diff --git a/src/test/app/Ticket_test.cpp b/src/test/app/Ticket_test.cpp index 0b4104236f..3af7db0d7d 100644 --- a/src/test/app/Ticket_test.cpp +++ b/src/test/app/Ticket_test.cpp @@ -115,7 +115,7 @@ class Ticket_test : public beast::unit_test::suite testcase ("Feature Not Enabled"); using namespace test::jtx; - Env env {*this}; + Env env {*this, no_features}; env (ticket::create (env.master), ter(temDISABLED)); env (ticket::cancel (env.master, idOne), ter (temDISABLED)); @@ -126,7 +126,7 @@ class Ticket_test : public beast::unit_test::suite testcase ("Cancel Nonexistent"); using namespace test::jtx; - Env env {*this, features (featureTickets)}; + Env env {*this, with_features (featureTickets)}; env (ticket::cancel (env.master, idOne), ter (tecNO_ENTRY)); } @@ -135,7 +135,7 @@ class Ticket_test : public beast::unit_test::suite testcase ("Create/Cancel Ticket with Bad Fee, Fail Preflight"); using namespace test::jtx; - Env env {*this, features (featureTickets)}; + Env env {*this, with_features (featureTickets)}; env (ticket::create (env.master), fee (XRP (-1)), ter (temBAD_FEE)); env (ticket::cancel (env.master, idOne), fee (XRP (-1)), ter (temBAD_FEE)); @@ -146,7 +146,7 @@ class Ticket_test : public beast::unit_test::suite testcase ("Create Tickets with Nonexistent Accounts"); using namespace test::jtx; - Env env {*this, features (featureTickets)}; + Env env {*this, with_features (featureTickets)}; Account alice {"alice"}; env.memoize (alice); @@ -162,7 +162,7 @@ class Ticket_test : public beast::unit_test::suite testcase ("Create Tickets with Same Account and Target"); using namespace test::jtx; - Env env {*this, features (featureTickets)}; + Env env {*this, with_features (featureTickets)}; env (ticket::create (env.master, env.master)); auto cr = checkTicketMeta (env); @@ -183,7 +183,7 @@ class Ticket_test : public beast::unit_test::suite testcase ("Create Ticket and Then Cancel by Creator"); using namespace test::jtx; - Env env {*this, features (featureTickets)}; + Env env {*this, with_features (featureTickets)}; // create and verify env (ticket::create (env.master)); @@ -215,7 +215,7 @@ class Ticket_test : public beast::unit_test::suite testcase ("Create Ticket Insufficient Reserve"); using namespace test::jtx; - Env env {*this, features (featureTickets)}; + Env env {*this, with_features (featureTickets)}; Account alice {"alice"}; env.fund (env.current ()->fees ().accountReserve (0), alice); @@ -229,7 +229,7 @@ class Ticket_test : public beast::unit_test::suite testcase ("Create Ticket and Then Cancel by Target"); using namespace test::jtx; - Env env {*this, features (featureTickets)}; + Env env {*this, with_features (featureTickets)}; Account alice {"alice"}; env.fund (XRP (10000), alice); @@ -275,7 +275,7 @@ class Ticket_test : public beast::unit_test::suite testcase ("Create Ticket with Future Expiration"); using namespace test::jtx; - Env env {*this, features (featureTickets)}; + Env env {*this, with_features (featureTickets)}; // create and verify uint32_t expire = @@ -300,7 +300,7 @@ class Ticket_test : public beast::unit_test::suite testcase ("Create Ticket with Zero Expiration"); using namespace test::jtx; - Env env {*this, features (featureTickets)}; + Env env {*this, with_features (featureTickets)}; // create and verify env (ticket::create (env.master, 0u), ter (temBAD_EXPIRATION)); @@ -311,7 +311,7 @@ class Ticket_test : public beast::unit_test::suite testcase ("Create Ticket with Past Expiration"); using namespace test::jtx; - Env env {*this, features (featureTickets)}; + Env env {*this, with_features (featureTickets)}; env.timeKeeper ().adjustCloseTime (days {2}); env.close (); @@ -340,7 +340,7 @@ class Ticket_test : public beast::unit_test::suite testcase ("Create Ticket and Allow to Expire"); using namespace test::jtx; - Env env {*this, features (featureTickets)}; + Env env {*this, with_features (featureTickets)}; // create and verify uint32_t expire = diff --git a/src/test/app/TrustAndBalance_test.cpp b/src/test/app/TrustAndBalance_test.cpp index 57a5d85c01..7e81c3b350 100644 --- a/src/test/app/TrustAndBalance_test.cpp +++ b/src/test/app/TrustAndBalance_test.cpp @@ -51,7 +51,7 @@ class TrustAndBalance_test : public beast::unit_test::suite testcase ("Payment to Nonexistent Account"); using namespace test::jtx; - Env env {*this, features(fs)}; + Env env {*this, with_features(fs)}; env (pay (env.master, "alice", XRP(1)), ter(tecNO_DST_INSUF_XRP)); env.close(); } @@ -167,7 +167,7 @@ class TrustAndBalance_test : public beast::unit_test::suite testcase ("Direct Payment, Ripple"); using namespace test::jtx; - Env env {*this, features(fs)}; + Env env {*this, with_features(fs)}; Account alice {"alice"}; Account bob {"bob"}; @@ -210,7 +210,7 @@ class TrustAndBalance_test : public beast::unit_test::suite (subscribe ? "With " : "Without ") + " Subscribe"); using namespace test::jtx; - Env env {*this, features(fs)}; + Env env {*this, with_features(fs)}; auto wsc = test::makeWSClient(env.app().config()); Account gw {"gateway"}; Account alice {"alice"}; @@ -288,7 +288,7 @@ class TrustAndBalance_test : public beast::unit_test::suite testcase ("Payments With Paths and Fees"); using namespace test::jtx; - Env env {*this, features(fs)}; + Env env {*this, with_features(fs)}; Account gw {"gateway"}; Account alice {"alice"}; Account bob {"bob"}; @@ -336,7 +336,7 @@ class TrustAndBalance_test : public beast::unit_test::suite testcase ("Indirect Payment"); using namespace test::jtx; - Env env {*this, features(fs)}; + Env env {*this, with_features(fs)}; Account gw {"gateway"}; Account alice {"alice"}; Account bob {"bob"}; @@ -378,7 +378,7 @@ class TrustAndBalance_test : public beast::unit_test::suite (with_rate ? "With " : "Without ") + " Xfer Fee, "); using namespace test::jtx; - Env env {*this, features(fs)}; + Env env {*this, with_features(fs)}; Account gw {"gateway"}; Account amazon {"amazon"}; Account alice {"alice"}; @@ -443,7 +443,7 @@ class TrustAndBalance_test : public beast::unit_test::suite testcase ("Set Invoice ID on Payment"); using namespace test::jtx; - Env env {*this, features(fs)}; + Env env {*this, with_features(fs)}; Account alice {"alice"}; auto wsc = test::makeWSClient(env.app().config()); diff --git a/src/test/app/TxQ_test.cpp b/src/test/app/TxQ_test.cpp index 1d92bf0dba..9782d3d08a 100644 --- a/src/test/app/TxQ_test.cpp +++ b/src/test/app/TxQ_test.cpp @@ -169,7 +169,7 @@ public: using namespace std::chrono; Env env(*this, makeConfig({ {"minimum_txn_in_ledger_standalone", "3"} }), - features(featureFeeEscalation)); + with_features(featureFeeEscalation)); auto& txq = env.app().getTxQ(); auto alice = Account("alice"); @@ -356,7 +356,7 @@ public: using namespace std::chrono; Env env(*this, makeConfig({ { "minimum_txn_in_ledger_standalone", "2" } }), - features(featureFeeEscalation)); + with_features(featureFeeEscalation)); auto alice = Account("alice"); auto bob = Account("bob"); @@ -412,7 +412,7 @@ public: using namespace std::chrono; Env env(*this, makeConfig({ { "minimum_txn_in_ledger_standalone", "2" } }), - features(featureFeeEscalation)); + with_features(featureFeeEscalation)); auto alice = Account("alice"); auto bob = Account("bob"); @@ -520,7 +520,7 @@ public: using namespace std::chrono; Env env(*this, makeConfig({ { "minimum_txn_in_ledger_standalone", "2" } }), - features(featureFeeEscalation)); + with_features(featureFeeEscalation)); auto alice = Account("alice"); auto bob = Account("bob"); @@ -626,7 +626,7 @@ public: { using namespace jtx; - Env env(*this, makeConfig(), features(featureFeeEscalation)); + Env env(*this, makeConfig(), with_features(featureFeeEscalation)); auto alice = Account("alice"); auto bob = Account("bob"); @@ -651,7 +651,7 @@ public: using namespace jtx; Env env(*this, makeConfig({ { "minimum_txn_in_ledger_standalone", "2" } }), - features(featureFeeEscalation)); + with_features(featureFeeEscalation)); auto alice = Account("alice"); auto bob = Account("bob"); @@ -704,12 +704,11 @@ public: { using namespace jtx; - Env env( - *this, + Env env(*this, makeConfig( {{"minimum_txn_in_ledger_standalone", "3"}}, {{"account_reserve", "200"}, {"owner_reserve", "50"}}), - features(featureFeeEscalation)); + with_features(featureFeeEscalation)); auto alice = Account("alice"); auto bob = Account("bob"); @@ -937,7 +936,7 @@ public: using namespace std::chrono; Env env(*this, makeConfig({ { "minimum_txn_in_ledger_standalone", "4" } }), - features(featureFeeEscalation)); + with_features(featureFeeEscalation)); auto alice = Account("alice"); auto bob = Account("bob"); @@ -1073,7 +1072,7 @@ public: { using namespace jtx; - Env env(*this); + Env env(*this, no_features); auto alice = Account("alice"); @@ -1096,7 +1095,7 @@ public: using namespace jtx; Env env(*this, makeConfig({ { "minimum_txn_in_ledger_standalone", "1" } }), - features(featureFeeEscalation)); + with_features(featureFeeEscalation)); auto alice = Account("alice"); @@ -1139,7 +1138,7 @@ public: { {"minimum_txn_in_ledger_standalone", "2"}, {"target_txn_in_ledger", "4"}, {"maximum_txn_in_ledger", "5"} }), - features(featureFeeEscalation)); + with_features(featureFeeEscalation)); auto alice = Account("alice"); auto queued = ter(terQUEUED); @@ -1169,7 +1168,7 @@ public: makeConfig( {{"minimum_txn_in_ledger_standalone", "3"}}, {{"account_reserve", "200"}, {"owner_reserve", "50"}}), - features(featureFeeEscalation)); + with_features(featureFeeEscalation)); auto alice = Account("alice"); auto bob = Account("bob"); @@ -1259,7 +1258,7 @@ public: Env env(*this, makeConfig({ { "minimum_txn_in_ledger_standalone", "3" } }), - features(featureFeeEscalation), features(featureMultiSign)); + with_features(featureFeeEscalation, featureMultiSign)); auto alice = Account("alice"); auto bob = Account("bob"); @@ -1324,7 +1323,7 @@ public: Env env(*this, makeConfig({ { "minimum_txn_in_ledger_standalone", "3" } }), - features(featureFeeEscalation), features(featureTickets)); + with_features(featureFeeEscalation, featureTickets)); auto alice = Account("alice"); auto charlie = Account("charlie"); @@ -1569,7 +1568,7 @@ public: { using namespace jtx; using namespace std::chrono; - Env env(*this, features(featureTickets)); + Env env(*this, with_features(featureTickets)); auto const alice = Account("alice"); env.memoize(alice); env.memoize("bob"); @@ -1637,7 +1636,7 @@ public: { using namespace jtx; { - Env env(*this, features(featureFeeEscalation)); + Env env(*this, with_features(featureFeeEscalation)); auto fee = env.rpc("fee"); @@ -1694,7 +1693,7 @@ public: } { - Env env(*this); + Env env(*this, no_features); auto fee = env.rpc("fee"); @@ -1726,7 +1725,7 @@ public: Env env(*this, makeConfig({ { "minimum_txn_in_ledger_standalone", "1" }, {"ledgers_in_queue", "10"}, {"maximum_txn_per_account", "20"} }), - features(featureFeeEscalation)); + with_features(featureFeeEscalation)); // Alice will recreate the scenario. Bob will block. auto const alice = Account("alice"); @@ -1801,7 +1800,7 @@ public: testcase("Autofilled sequence should account for TxQ"); using namespace jtx; Env env(*this, makeConfig({ {"minimum_txn_in_ledger_standalone", "6"} }), - features(featureFeeEscalation)); + with_features(featureFeeEscalation)); Env_ss envs(env); auto const& txQ = env.app().getTxQ(); @@ -1931,7 +1930,7 @@ public: { using namespace jtx; Env env(*this, makeConfig({ { "minimum_txn_in_ledger_standalone", "3" } }), - features(featureFeeEscalation)); + with_features(featureFeeEscalation)); Env_ss envs(env); Account const alice{ "alice" }; @@ -2201,7 +2200,7 @@ public: { using namespace jtx; Env env(*this, makeConfig({ { "minimum_txn_in_ledger_standalone", "3" } }), - features(featureFeeEscalation)); + with_features(featureFeeEscalation)); Env_ss envs(env); Account const alice{ "alice" }; @@ -2424,7 +2423,7 @@ public: using namespace jtx; Env env(*this, makeConfig({ { "minimum_txn_in_ledger_standalone", "3" } }), - features(featureFeeEscalation)); + with_features(featureFeeEscalation)); Json::Value stream; stream[jss::streams] = Json::arrayValue; @@ -2593,7 +2592,7 @@ public: Env env(*this, makeConfig({ { "minimum_txn_in_ledger_standalone", "3" } }), - features(featureFeeEscalation)); + with_features(featureFeeEscalation)); auto alice = Account("alice"); auto bob = Account("bob"); diff --git a/src/test/app/ValidatorKeys_test.cpp b/src/test/app/ValidatorKeys_test.cpp new file mode 100644 index 0000000000..7fb8d32bfd --- /dev/null +++ b/src/test/app/ValidatorKeys_test.cpp @@ -0,0 +1,150 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright 2017 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include + +namespace ripple { +namespace test { + +class ValidatorKeys_test : public beast::unit_test::suite +{ + // Used with [validation_seed] + const std::string seed = "shUwVw52ofnCUX5m7kPTKzJdr4HEH"; + + // Used with [validation_token] + const std::string tokenSecretStr = + "paQmjZ37pKKPMrgadBLsuf9ab7Y7EUNzh27LQrZqoexpAs31nJi"; + + const std::vector tokenBlob = { + " " + "eyJ2YWxpZGF0aW9uX3NlY3JldF9rZXkiOiI5ZWQ0NWY4NjYyNDFjYzE4YTI3NDdiNT\n", + " \tQzODdjMDYyNTkwNzk3MmY0ZTcxOTAyMzFmYWE5Mzc0NTdmYTlkYWY2IiwibWFuaWZl " + " \n", + "\tc3QiOiJKQUFBQUFGeEllMUZ0d21pbXZHdEgyaUNjTUpxQzlnVkZLaWxHZncxL3ZDeE" + "\n", + "\t " + "hYWExwbGMyR25NaEFrRTFhZ3FYeEJ3RHdEYklENk9NU1l1TTBGREFscEFnTms4U0tG\t " + "\t\n", + "bjdNTzJmZGtjd1JRSWhBT25ndTlzQUtxWFlvdUorbDJWMFcrc0FPa1ZCK1pSUzZQU2\n", + "hsSkFmVXNYZkFpQnNWSkdlc2FhZE9KYy9hQVpva1MxdnltR21WcmxIUEtXWDNZeXd1\n", + "NmluOEhBU1FLUHVnQkQ2N2tNYVJGR3ZtcEFUSGxHS0pkdkRGbFdQWXk1QXFEZWRGdj\n", + "VUSmEydzBpMjFlcTNNWXl3TFZKWm5GT3I3QzBrdzJBaVR6U0NqSXpkaXRROD0ifQ==\n"}; + + const std::string tokenManifest = + "JAAAAAFxIe1FtwmimvGtH2iCcMJqC9gVFKilGfw1/vCxHXXLplc2GnMhAkE1agqXxBwD" + "wDbID6OMSYuM0FDAlpAgNk8SKFn7MO2fdkcwRQIhAOngu9sAKqXYouJ+l2V0W+sAOkVB" + "+ZRS6PShlJAfUsXfAiBsVJGesaadOJc/aAZokS1vymGmVrlHPKWX3Yywu6in8HASQKPu" + "gBD67kMaRFGvmpATHlGKJdvDFlWPYy5AqDedFv5TJa2w0i21eq3MYywLVJZnFOr7C0kw" + "2AiTzSCjIzditQ8="; + +public: + void + run() override + { + beast::Journal j; + + // Keys when using [validation_seed] + auto const seedSecretKey = + generateSecretKey(KeyType::secp256k1, *parseBase58(seed)); + auto const seedPublicKey = + derivePublicKey(KeyType::secp256k1, seedSecretKey); + + // Keys when using [validation_token] + auto const tokenSecretKey = *parseBase58( + TokenType::TOKEN_NODE_PRIVATE, tokenSecretStr); + + auto const tokenPublicKey = + derivePublicKey(KeyType::secp256k1, tokenSecretKey); + + { + // No config -> no key but valid + Config c; + ValidatorKeys k{c, j}; + BEAST_EXPECT(k.publicKey.size() == 0); + BEAST_EXPECT(k.manifest.empty()); + BEAST_EXPECT(!k.configInvalid()); + + } + { + // validation seed section -> empty manifest and valid seeds + Config c; + c.section(SECTION_VALIDATION_SEED).append(seed); + + ValidatorKeys k{c, j}; + BEAST_EXPECT(k.publicKey == seedPublicKey); + BEAST_EXPECT(k.secretKey == seedSecretKey); + BEAST_EXPECT(k.manifest.empty()); + BEAST_EXPECT(!k.configInvalid()); + } + + { + // validation seed bad seed -> invalid + Config c; + c.section(SECTION_VALIDATION_SEED).append("badseed"); + + ValidatorKeys k{c, j}; + BEAST_EXPECT(k.configInvalid()); + BEAST_EXPECT(k.publicKey.size() == 0); + BEAST_EXPECT(k.manifest.empty()); + } + + { + // validator token + Config c; + c.section(SECTION_VALIDATOR_TOKEN).append(tokenBlob); + ValidatorKeys k{c, j}; + + BEAST_EXPECT(k.publicKey == tokenPublicKey); + BEAST_EXPECT(k.secretKey == tokenSecretKey); + BEAST_EXPECT(k.manifest == tokenManifest); + BEAST_EXPECT(!k.configInvalid()); + } + { + // invalid validator token + Config c; + c.section(SECTION_VALIDATOR_TOKEN).append("badtoken"); + ValidatorKeys k{c, j}; + BEAST_EXPECT(k.configInvalid()); + BEAST_EXPECT(k.publicKey.size() == 0); + BEAST_EXPECT(k.manifest.empty()); + } + + { + // Cannot specify both + Config c; + c.section(SECTION_VALIDATION_SEED).append(seed); + c.section(SECTION_VALIDATOR_TOKEN).append(tokenBlob); + ValidatorKeys k{c, j}; + + BEAST_EXPECT(k.configInvalid()); + BEAST_EXPECT(k.publicKey.size() == 0); + BEAST_EXPECT(k.manifest.empty()); + } + + } +}; // namespace test + +BEAST_DEFINE_TESTSUITE(ValidatorKeys, app, ripple); + +} // namespace test +} // namespace ripple diff --git a/src/test/app/ValidatorList_test.cpp b/src/test/app/ValidatorList_test.cpp index 8fa257c656..01fa2d1163 100644 --- a/src/test/app/ValidatorList_test.cpp +++ b/src/test/app/ValidatorList_test.cpp @@ -124,21 +124,6 @@ private: } } - void - testCalculateQuorum () - { - testcase ("Calculate Quorum"); - - for(std::size_t i = 1; i < 20; ++i) - { - auto const quorum = ValidatorList::calculateQuorum(i); - if (i < 10) - BEAST_EXPECT(quorum >= (i/2 + 1)); - else - BEAST_EXPECT(quorum == std::ceil (i * 0.8)); - } - } - void testConfigLoad () { @@ -503,16 +488,19 @@ private: std::vector cfgPublishers; hash_set activeValidators; + // BFT: n >= 3f+1 + std::size_t const n = 40; + std::size_t const f = 13; { std::vector cfgKeys; - cfgKeys.reserve(20); + cfgKeys.reserve(n); - while (cfgKeys.size () != 20) + while (cfgKeys.size () != n) { auto const valKey = randomNode(); cfgKeys.push_back (toBase58( TokenType::TOKEN_NODE_PUBLIC, valKey)); - if (cfgKeys.size () <= 15) + if (cfgKeys.size () <= n - 5) activeValidators.emplace (valKey); } @@ -522,7 +510,8 @@ private: // onConsensusStart should make all available configured // validators trusted trustedKeys->onConsensusStart (activeValidators); - BEAST_EXPECT(trustedKeys->quorum () == 12); + // Add 1 to n because I'm not on a published list. + BEAST_EXPECT(trustedKeys->quorum () == n + 1 - f); std::size_t i = 0; for (auto const& val : cfgKeys) { @@ -538,6 +527,16 @@ private: else fail (); } + + { + // Quorum should be 80% with all listed validators active + hash_set activeValidators; + for (auto const valKey : cfgKeys) + activeValidators.emplace (*parseBase58( + TokenType::TOKEN_NODE_PUBLIC, valKey)); + trustedKeys->onConsensusStart (activeValidators); + BEAST_EXPECT(trustedKeys->quorum () == cfgKeys.size() * 4/5); + } } { // update with manifests @@ -571,7 +570,7 @@ private: manifests.applyManifest(std::move (*m1)) == ManifestDisposition::accepted); trustedKeys->onConsensusStart (activeValidators); - BEAST_EXPECT(trustedKeys->quorum () == 13); + BEAST_EXPECT(trustedKeys->quorum () == n + 2 - f); BEAST_EXPECT(trustedKeys->listed (masterPublic)); BEAST_EXPECT(trustedKeys->trusted (masterPublic)); BEAST_EXPECT(trustedKeys->listed (signingPublic1)); @@ -584,12 +583,11 @@ private: auto m2 = Manifest::make_Manifest (makeManifestString ( masterPublic, masterPrivate, signingPublic2, signingKeys2.second, 2)); - BEAST_EXPECT( manifests.applyManifest(std::move (*m2)) == ManifestDisposition::accepted); trustedKeys->onConsensusStart (activeValidators); - BEAST_EXPECT(trustedKeys->quorum () == 13); + BEAST_EXPECT(trustedKeys->quorum () == n + 2 - f); BEAST_EXPECT(trustedKeys->listed (masterPublic)); BEAST_EXPECT(trustedKeys->trusted (masterPublic)); BEAST_EXPECT(trustedKeys->listed (signingPublic2)); @@ -613,7 +611,7 @@ private: BEAST_EXPECT(manifests.getSigningKey (masterPublic) == masterPublic); BEAST_EXPECT(manifests.revoked (masterPublic)); trustedKeys->onConsensusStart (activeValidators); - BEAST_EXPECT(trustedKeys->quorum () == 12); + BEAST_EXPECT(trustedKeys->quorum () == n + 1 - f); BEAST_EXPECT(trustedKeys->listed (masterPublic)); BEAST_EXPECT(!trustedKeys->trusted (masterPublic)); BEAST_EXPECT(!trustedKeys->listed (signingPublicMax)); @@ -664,7 +662,7 @@ private: } { // Should use custom minimum quorum - std::size_t const minQuorum = 0; + std::size_t const minQuorum = 1; ManifestCache manifests; auto trustedKeys = std::make_unique ( manifests, manifests, env.timeKeeper(), beast::Journal (), minQuorum); @@ -700,7 +698,7 @@ private: localKey, cfgKeys, cfgPublishers)); trustedKeys->onConsensusStart (activeValidators); - BEAST_EXPECT(trustedKeys->quorum () == 3); + BEAST_EXPECT(trustedKeys->quorum () == 2); // local validator key is always trusted BEAST_EXPECT(trustedKeys->trusted (localKey)); @@ -770,7 +768,8 @@ private: emptyLocalKey, cfgKeys, cfgPublishers)); trustedKeys->onConsensusStart (activeValidators); BEAST_EXPECT(trustedKeys->quorum () == - ValidatorList::calculateQuorum(cfgKeys.size())); + (cfgKeys.size() <= 5) ? cfgKeys.size()/2 + 1 : + cfgKeys.size() * 2/3 + 1); for (auto const& key : activeValidators) BEAST_EXPECT(trustedKeys->trusted (key)); } @@ -799,16 +798,81 @@ private: localKey, cfgKeys, cfgPublishers)); trustedKeys->onConsensusStart (activeValidators); - // When running as an unlisted validator, - // the quorum is incremented by 1 for 3 or 5 trusted validators. - auto expectedQuorum = ValidatorList::calculateQuorum(cfgKeys.size()); - if (cfgKeys.size() == 3 || cfgKeys.size() == 5) - ++expectedQuorum; - BEAST_EXPECT(trustedKeys->quorum () == expectedQuorum); + BEAST_EXPECT(trustedKeys->quorum () == + (cfgKeys.size() <= 5) ? cfgKeys.size()/2 + 1 : + (cfgKeys.size() + 1) * 2/3 + 1); + for (auto const& key : activeValidators) BEAST_EXPECT(trustedKeys->trusted (key)); } } + { + // Trusted set should be trimmed with multiple validator lists + ManifestCache manifests; + auto trustedKeys = std::make_unique ( + manifests, manifests, env.timeKeeper(), beast::Journal ()); + + hash_set activeValidators; + + std::vector valKeys; + valKeys.reserve(n); + + while (valKeys.size () != n) + { + valKeys.push_back (randomNode()); + activeValidators.emplace (valKeys.back()); + } + + auto addPublishedList = [this, &env, &trustedKeys, &valKeys]() + { + auto const publisherSecret = randomSecretKey(); + auto const publisherPublic = + derivePublicKey(KeyType::ed25519, publisherSecret); + auto const pubSigningKeys = randomKeyPair(KeyType::secp256k1); + auto const manifest = beast::detail::base64_encode(makeManifestString ( + publisherPublic, publisherSecret, + pubSigningKeys.first, pubSigningKeys.second, 1)); + + std::vector cfgPublishers({ + strHex(publisherPublic)}); + PublicKey emptyLocalKey; + std::vector emptyCfgKeys; + + BEAST_EXPECT(trustedKeys->load ( + emptyLocalKey, emptyCfgKeys, cfgPublishers)); + + auto const version = 1; + auto const sequence = 1; + NetClock::time_point const expiration = + env.timeKeeper().now() + 3600s; + auto const blob = makeList ( + valKeys, sequence, expiration.time_since_epoch().count()); + auto const sig = signList (blob, pubSigningKeys); + + BEAST_EXPECT(ListDisposition::accepted == trustedKeys->applyList ( + manifest, blob, sig, version)); + }; + + // Apply multiple published lists + for (auto i = 0; i < 3; ++i) + addPublishedList(); + + trustedKeys->onConsensusStart (activeValidators); + + // Minimum quorum should be used + BEAST_EXPECT(trustedKeys->quorum () == (valKeys.size() * 2/3 + 1)); + + std::size_t nTrusted = 0; + for (auto const& key : activeValidators) + { + if (trustedKeys->trusted (key)) + ++nTrusted; + } + + // The number of trusted keys should be 125% of the minimum quorum + BEAST_EXPECT(nTrusted == + static_cast(trustedKeys->quorum () * 5 / 4)); + } } public: @@ -816,7 +880,6 @@ public: run() override { testGenesisQuorum (); - testCalculateQuorum (); testConfigLoad (); testApplyList (); testUpdate (); diff --git a/src/test/app/ValidatorSite_test.cpp b/src/test/app/ValidatorSite_test.cpp index 7a96691ae3..c72e50472a 100644 --- a/src/test/app/ValidatorSite_test.cpp +++ b/src/test/app/ValidatorSite_test.cpp @@ -17,7 +17,6 @@ */ //============================================================================== -#include #include #include #include @@ -91,7 +90,7 @@ public: acceptor_.listen(boost::asio::socket_base::max_connections); acceptor_.async_accept(sock_, std::bind(&http_sync_server::on_accept, this, - beast::asio::placeholders::error)); + std::placeholders::_1)); } ~http_sync_server() @@ -126,22 +125,23 @@ private: void on_accept(error_code ec) { - if(! acceptor_.is_open()) - return; - if(ec) + // ec must be checked before `acceptor_` or the member variable may be + // accessed after the destructor has completed + if(ec || !acceptor_.is_open()) return; + static int id_ = 0; std::thread{lambda{++id_, *this, std::move(sock_)}}.detach(); acceptor_.async_accept(sock_, std::bind(&http_sync_server::on_accept, this, - beast::asio::placeholders::error)); + std::placeholders::_1)); } void do_peer(int id, socket_type&& sock0) { socket_type sock(std::move(sock0)); - beast::streambuf sb; + beast::multi_buffer sb; error_code ec; for(;;) { @@ -149,44 +149,41 @@ private: beast::http::read(sock, sb, req, ec); if(ec) break; - auto path = req.url; + auto path = req.target().to_string(); if(path != "/validators") { resp_type res; - res.status = 404; - res.reason = "Not Found"; + res.result(beast::http::status::not_found); res.version = req.version; - res.fields.insert("Server", "http_sync_server"); - res.fields.insert("Content-Type", "text/html"); + res.insert("Server", "http_sync_server"); + res.insert("Content-Type", "text/html"); res.body = "The file '" + path + "' was not found"; - prepare(res); + res.prepare_payload(); write(sock, res, ec); if(ec) break; } resp_type res; - res.status = 200; - res.reason = "OK"; + res.result(beast::http::status::ok); res.version = req.version; - res.fields.insert("Server", "http_sync_server"); - res.fields.insert("Content-Type", "application/json"); + res.insert("Server", "http_sync_server"); + res.insert("Content-Type", "application/json"); res.body = list_; try { - prepare(res); + res.prepare_payload(); } catch(std::exception const& e) { res = {}; - res.status = 500; - res.reason = "Internal Error"; + res.result(beast::http::status::internal_server_error); res.version = req.version; - res.fields.insert("Server", "http_sync_server"); - res.fields.insert("Content-Type", "text/html"); + res.insert("Server", "http_sync_server"); + res.insert("Content-Type", "text/html"); res.body = std::string{"An internal error occurred"} + e.what(); - prepare(res); + res.prepare_payload(); } write(sock, res, ec); if(ec) diff --git a/src/test/basics/RangeSet_test.cpp b/src/test/basics/RangeSet_test.cpp index e433a49a32..ce12c4a240 100644 --- a/src/test/basics/RangeSet_test.cpp +++ b/src/test/basics/RangeSet_test.cpp @@ -21,14 +21,15 @@ #include #include -namespace ripple { - +namespace ripple +{ class RangeSet_test : public beast::unit_test::suite { public: - RangeSet createPredefinedSet () + void + testPrevMissing() { - RangeSet set; + testcase("prevMissing"); // Set will include: // [ 0, 5] @@ -36,59 +37,52 @@ public: // [20,25] // etc... - for (int i = 0; i < 10; ++i) - set.setRange (10 * i, 10 * i + 5); + RangeSet set; + for (std::uint32_t i = 0; i < 10; ++i) + set.insert(range(10 * i, 10 * i + 5)); - return set; - } - - void testMembership () - { - testcase ("membership"); - - RangeSet r1, r2; - - r1.setRange (1, 10); - r1.clearValue (5); - r1.setRange (11, 20); - - r2.setRange (1, 4); - r2.setRange (6, 10); - r2.setRange (10, 20); - - BEAST_EXPECT(!r1.hasValue (5)); - - BEAST_EXPECT(r2.hasValue (9)); - } - - void testPrevMissing () - { - testcase ("prevMissing"); - - RangeSet const set = createPredefinedSet (); - - for (int i = 0; i < 100; ++i) + for (std::uint32_t i = 1; i < 100; ++i) { - int const oneBelowRange = (10*(i/10))-1; + boost::optional expected; + // no prev missing in domain for i <= 6 + if (i > 6) + { + std::uint32_t const oneBelowRange = (10 * (i / 10)) - 1; - int const expectedPrevMissing = - ((i % 10) > 6) ? (i-1) : oneBelowRange; - - BEAST_EXPECT(set.prevMissing (i) == expectedPrevMissing); + expected = ((i % 10) > 6) ? (i - 1) : oneBelowRange; + } + BEAST_EXPECT(prevMissing(set, i) == expected); } } - void run () + void + testToString() { - testMembership (); + testcase("toString"); - testPrevMissing (); + RangeSet set; + BEAST_EXPECT(to_string(set) == "empty"); - // TODO: Traverse functions must be tested + set.insert(1); + BEAST_EXPECT(to_string(set) == "1"); + + set.insert(range(4u, 6u)); + BEAST_EXPECT(to_string(set) == "1,4-6"); + + set.insert(2); + BEAST_EXPECT(to_string(set) == "1-2,4-6"); + + set.erase(range(4u, 5u)); + BEAST_EXPECT(to_string(set) == "1-2,6"); + } + void + run() + { + testPrevMissing(); + testToString(); } }; -BEAST_DEFINE_TESTSUITE(RangeSet,ripple_basics,ripple); - -} // ripple +BEAST_DEFINE_TESTSUITE(RangeSet, ripple_basics, ripple); +} // namespace ripple diff --git a/src/test/basics/tagged_integer_test.cpp b/src/test/basics/tagged_integer_test.cpp new file mode 100644 index 0000000000..1a86b45ba1 --- /dev/null +++ b/src/test/basics/tagged_integer_test.cpp @@ -0,0 +1,239 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright 2014, Nikolaos D. Bougalis + + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include + +namespace ripple { +namespace test { + +class tagged_integer_test + : public beast::unit_test::suite +{ +private: + struct Tag1 { }; + struct Tag2 { }; + + // Static checks that types are not interoperable + + using TagUInt1 = tagged_integer ; + using TagUInt2 = tagged_integer ; + using TagUInt3 = tagged_integer ; + + // Check construction of tagged_integers + static_assert (std::is_constructible::value, + "TagUInt1 should be constructible using a std::uint32_t"); + + static_assert (!std::is_constructible::value, + "TagUInt1 should not be constructible using a std::uint64_t"); + + static_assert (std::is_constructible::value, + "TagUInt3 should be constructible using a std::uint32_t"); + + static_assert (std::is_constructible::value, + "TagUInt3 should be constructible using a std::uint64_t"); + + // Check assignment of tagged_integers + static_assert (!std::is_assignable::value, + "TagUInt1 should not be assignable with a std::uint32_t"); + + static_assert (!std::is_assignable::value, + "TagUInt1 should not be assignable with a std::uint64_t"); + + static_assert (!std::is_assignable::value, + "TagUInt3 should not be assignable with a std::uint32_t"); + + static_assert (!std::is_assignable::value, + "TagUInt3 should not be assignable with a std::uint64_t"); + + static_assert (std::is_assignable::value, + "TagUInt1 should be assignable with a TagUInt1"); + + static_assert (!std::is_assignable::value, + "TagUInt1 should not be assignable with a TagUInt2"); + + static_assert (std::is_assignable::value, + "TagUInt3 should be assignable with a TagUInt1"); + + static_assert (!std::is_assignable::value, + "TagUInt1 should not be assignable with a TagUInt3"); + + static_assert (!std::is_assignable::value, + "TagUInt3 should not be assignable with a TagUInt1"); + + // Check convertibility of tagged_integers + static_assert (!std::is_convertible::value, + "std::uint32_t should not be convertible to a TagUInt1"); + + static_assert (!std::is_convertible::value, + "std::uint32_t should not be convertible to a TagUInt3"); + + static_assert (!std::is_convertible::value, + "std::uint64_t should not be convertible to a TagUInt3"); + + static_assert (!std::is_convertible::value, + "std::uint64_t should not be convertible to a TagUInt2"); + + static_assert (!std::is_convertible::value, + "TagUInt1 should not be convertible to TagUInt2"); + + static_assert (!std::is_convertible::value, + "TagUInt1 should not be convertible to TagUInt3"); + + static_assert (!std::is_convertible::value, + "TagUInt2 should not be convertible to a TagUInt3"); + + +public: + void run () + { + using TagInt = tagged_integer; + + { + testcase ("Comparison Operators"); + + TagInt const zero(0); + TagInt const one(1); + + BEAST_EXPECT(one == one); + BEAST_EXPECT(!(one == zero)); + + BEAST_EXPECT(one != zero); + BEAST_EXPECT(!(one != one)); + + BEAST_EXPECT(zero < one); + BEAST_EXPECT(!(one < zero)); + + BEAST_EXPECT(one > zero); + BEAST_EXPECT(!(zero > one)); + + BEAST_EXPECT(one >= one); + BEAST_EXPECT(one >= zero); + BEAST_EXPECT(!(zero >= one)); + + BEAST_EXPECT(zero <= one); + BEAST_EXPECT(zero <= zero); + BEAST_EXPECT(!(one <= zero)); + } + + { + testcase ("Increment/Decrement Operators"); + TagInt const zero(0); + TagInt const one(1); + TagInt a{0}; + ++a; + BEAST_EXPECT(a == one); + --a; + BEAST_EXPECT(a == zero); + a++; + BEAST_EXPECT(a == one); + a--; + BEAST_EXPECT(a == zero); + } + + + { + testcase ("Arithmetic Operators"); + TagInt a{-2}; + BEAST_EXPECT(+a == TagInt{-2}); + BEAST_EXPECT(-a == TagInt{2}); + BEAST_EXPECT(TagInt{-3} + TagInt{4} == TagInt{1}); + BEAST_EXPECT(TagInt{-3} - TagInt{4} == TagInt{-7}); + BEAST_EXPECT(TagInt{-3} * TagInt{4} == TagInt{-12}); + BEAST_EXPECT(TagInt{8}/TagInt{4} == TagInt{2}); + BEAST_EXPECT(TagInt{7} %TagInt{4} == TagInt{3}); + + BEAST_EXPECT(~TagInt{8} == TagInt{~TagInt::value_type{8}}); + BEAST_EXPECT((TagInt{6} & TagInt{3}) == TagInt{2}); + BEAST_EXPECT((TagInt{6} | TagInt{3}) == TagInt{7}); + BEAST_EXPECT((TagInt{6} ^ TagInt{3}) == TagInt{5}); + + BEAST_EXPECT((TagInt{4} << TagInt{2}) == TagInt{16}); + BEAST_EXPECT((TagInt{16} >> TagInt{2}) == TagInt{4}); + } + { + testcase ("Assignment Operators"); + TagInt a{-2}; + TagInt b{0}; + b = a; + BEAST_EXPECT(b == TagInt{-2}); + + // -3 + 4 == 1 + a = TagInt{-3}; + a += TagInt{4}; + BEAST_EXPECT(a == TagInt{1}); + + // -3 - 4 == -7 + a = TagInt{-3}; + a -= TagInt{4}; + BEAST_EXPECT(a == TagInt{-7}); + + // -3 * 4 == -12 + a = TagInt{-3}; + a *= TagInt{4}; + BEAST_EXPECT(a == TagInt{-12}); + + // 8/4 == 2 + a = TagInt{8}; + a /= TagInt{4}; + BEAST_EXPECT(a == TagInt{2}); + + // 7 % 4 == 3 + a = TagInt{7}; + a %= TagInt{4}; + BEAST_EXPECT(a == TagInt{3}); + + // 6 & 3 == 2 + a = TagInt{6}; + a /= TagInt{3}; + BEAST_EXPECT(a == TagInt{2}); + + // 6 | 3 == 7 + a = TagInt{6}; + a |= TagInt{3}; + BEAST_EXPECT(a == TagInt{7}); + + // 6 ^ 3 == 5 + a = TagInt{6}; + a ^= TagInt{3}; + BEAST_EXPECT(a == TagInt{5}); + + // 4 << 2 == 16 + a = TagInt{4}; + a <<= TagInt{2}; + BEAST_EXPECT(a == TagInt{16}); + + // 16 >> 2 == 4 + a = TagInt{16}; + a >>= TagInt{2}; + BEAST_EXPECT(a == TagInt{4}); + } + + + + } +}; + +BEAST_DEFINE_TESTSUITE(tagged_integer,ripple_basics,ripple); + +} // test +} // ripple diff --git a/src/test/beast/aged_associative_container_test.cpp b/src/test/beast/aged_associative_container_test.cpp index 8c2354a2f5..0f6e5fe332 100644 --- a/src/test/beast/aged_associative_container_test.cpp +++ b/src/test/beast/aged_associative_container_test.cpp @@ -142,6 +142,13 @@ public: return true; } + template + bool operator!= (AllocT const& o) const + { + return !(*this == o); + } + + T* allocate (std::size_t n, T const* = 0) { return static_cast ( diff --git a/src/test/beast/beast_asio_error_test.cpp b/src/test/beast/beast_asio_error_test.cpp index 8ef99a0973..49160ffa6d 100644 --- a/src/test/beast/beast_asio_error_test.cpp +++ b/src/test/beast/beast_asio_error_test.cpp @@ -33,7 +33,12 @@ public: boost::system::error_code (335544539, boost::asio::error::get_ssl_category ()); std::string const s = beast::error_message_with_ssl(ec); + +#ifdef SSL_R_SHORT_READ BEAST_EXPECT(s == " (20,0,219) error:140000DB:SSL routines:SSL routines:short read"); +#else + BEAST_EXPECT(s == " (20,0,219) error:140000DB:SSL routines:SSL routines:reason(219)"); +#endif } } }; diff --git a/src/test/beast/beast_tagged_integer_test.cpp b/src/test/beast/beast_tagged_integer_test.cpp deleted file mode 100644 index 29c103dc2c..0000000000 --- a/src/test/beast/beast_tagged_integer_test.cpp +++ /dev/null @@ -1,154 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of Beast: https://github.com/vinniefalco/Beast - Copyright 2014, Nikolaos D. Bougalis - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ -//============================================================================== - -#if BEAST_INCLUDE_BEASTCONFIG -#include -#endif - -#include - -#include -#include - -namespace beast { - -class tagged_integer_test - : public unit_test::suite -{ -private: - struct Tag1 { }; - struct Tag2 { }; - - using TagInt1 = tagged_integer ; - using TagInt2 = tagged_integer ; - using TagInt3 = tagged_integer ; - - // Check construction of tagged_integers - static_assert (std::is_constructible::value, - "TagInt1 should be constructible using a std::uint32_t"); - - static_assert (!std::is_constructible::value, - "TagInt1 should not be constructible using a std::uint64_t"); - - static_assert (std::is_constructible::value, - "TagInt3 should be constructible using a std::uint32_t"); - - static_assert (std::is_constructible::value, - "TagInt3 should be constructible using a std::uint64_t"); - - // Check assignment of tagged_integers - static_assert (!std::is_assignable::value, - "TagInt1 should not be assignable with a std::uint32_t"); - - static_assert (!std::is_assignable::value, - "TagInt1 should not be assignable with a std::uint64_t"); - - static_assert (!std::is_assignable::value, - "TagInt3 should not be assignable with a std::uint32_t"); - - static_assert (!std::is_assignable::value, - "TagInt3 should not be assignable with a std::uint64_t"); - - static_assert (std::is_assignable::value, - "TagInt1 should be assignable with a TagInt1"); - - static_assert (!std::is_assignable::value, - "TagInt1 should not be assignable with a TagInt2"); - - static_assert (std::is_assignable::value, - "TagInt3 should be assignable with a TagInt1"); - - static_assert (!std::is_assignable::value, - "TagInt1 should not be assignable with a TagInt3"); - - static_assert (!std::is_assignable::value, - "TagInt3 should not be assignable with a TagInt1"); - - // Check convertibility of tagged_integers - static_assert (!std::is_convertible::value, - "std::uint32_t should not be convertible to a TagInt1"); - - static_assert (!std::is_convertible::value, - "std::uint32_t should not be convertible to a TagInt3"); - - static_assert (!std::is_convertible::value, - "std::uint64_t should not be convertible to a TagInt3"); - - static_assert (!std::is_convertible::value, - "std::uint64_t should not be convertible to a TagInt2"); - - static_assert (!std::is_convertible::value, - "TagInt1 should not be convertible to TagInt2"); - - static_assert (!std::is_convertible::value, - "TagInt1 should not be convertible to TagInt3"); - - static_assert (!std::is_convertible::value, - "TagInt2 should not be convertible to a TagInt3"); - -public: - void run () - { - TagInt1 const zero (0); - TagInt1 const one (1); - - testcase ("Comparison Operators"); - - expect (zero >= zero, "Should be greater than or equal"); - expect (zero == zero, "Should be equal"); - - expect (one > zero, "Should be greater"); - expect (one >= zero, "Should be greater than or equal"); - expect (one != zero, "Should not be equal"); - - unexpected (one < zero, "Should be greater"); - unexpected (one <= zero, "Should not be greater than or equal"); - unexpected (one == zero, "Should not be equal"); - - testcase ("Arithmetic Operators"); - - TagInt1 tmp; - - tmp = zero + 0u; - expect (tmp == zero, "Should be equal"); - - tmp = 1u + zero; - expect (tmp == one, "Should be equal"); - - expect(--tmp == zero, "Should be equal"); - expect(tmp++ == zero, "Should be equal"); - expect(tmp == one, "Should be equal"); - - expect(tmp-- == one, "Should be equal"); - expect(tmp == zero, "Should be equal"); - expect(++tmp == one, "Should be equal"); - - tmp = zero; - - tmp += 1u; - expect(tmp == one, "Should be equal"); - - tmp -= 1u; - expect(tmp == zero, "Should be equal"); - } -}; - -BEAST_DEFINE_TESTSUITE(tagged_integer,utility,beast); - -} // beast diff --git a/src/test/consensus/Consensus_test.cpp b/src/test/consensus/Consensus_test.cpp index 0bffc4bdaa..1221958ad8 100644 --- a/src/test/consensus/Consensus_test.cpp +++ b/src/test/consensus/Consensus_test.cpp @@ -31,13 +31,97 @@ namespace test { class Consensus_test : public beast::unit_test::suite { public: + void + testShouldCloseLedger() + { + using namespace std::chrono_literals; + + // Use default parameters + ConsensusParms p; + beast::Journal j; + + // Bizarre times forcibly close + BEAST_EXPECT( + shouldCloseLedger(true, 10, 10, 10, -10s, 10s, 1s, 1s, p, j)); + BEAST_EXPECT( + shouldCloseLedger(true, 10, 10, 10, 100h, 10s, 1s, 1s, p, j)); + BEAST_EXPECT( + shouldCloseLedger(true, 10, 10, 10, 10s, 100h, 1s, 1s, p, j)); + + // Rest of network has closed + BEAST_EXPECT( + shouldCloseLedger(true, 10, 3, 5, 10s, 10s, 10s, 10s, p, j)); + + // No transactions means wait until end of internval + BEAST_EXPECT( + !shouldCloseLedger(false, 10, 0, 0, 1s, 1s, 1s, 10s, p, j)); + BEAST_EXPECT( + shouldCloseLedger(false, 10, 0, 0, 1s, 10s, 1s, 10s, p, j)); + + // Enforce minimum ledger open time + BEAST_EXPECT( + !shouldCloseLedger(true, 10, 0, 0, 10s, 10s, 1s, 10s, p, j)); + + // Don't go too much faster than last time + BEAST_EXPECT( + !shouldCloseLedger(true, 10, 0, 0, 10s, 10s, 3s, 10s, p, j)); + + BEAST_EXPECT( + shouldCloseLedger(true, 10, 0, 0, 10s, 10s, 10s, 10s, p, j)); + } + + void + testCheckConsensus() + { + using namespace std::chrono_literals; + + // Use default parameterss + ConsensusParms p; + beast::Journal j; + + + // Not enough time has elapsed + BEAST_EXPECT( + ConsensusState::No == + checkConsensus(10, 2, 2, 0, 3s, 2s, p, true, j)); + + // If not enough peers have propsed, ensure + // more time for proposals + BEAST_EXPECT( + ConsensusState::No == + checkConsensus(10, 2, 2, 0, 3s, 4s, p, true, j)); + + // Enough time has elapsed and we all agree + BEAST_EXPECT( + ConsensusState::Yes == + checkConsensus(10, 2, 2, 0, 3s, 10s, p, true, j)); + + // Enough time has elapsed and we don't yet agree + BEAST_EXPECT( + ConsensusState::No == + checkConsensus(10, 2, 1, 0, 3s, 10s, p, true, j)); + + // Our peers have moved on + // Enough time has elapsed and we all agree + BEAST_EXPECT( + ConsensusState::MovedOn == + checkConsensus(10, 2, 1, 8, 3s, 10s, p, true, j)); + + // No peers makes it easy to agree + BEAST_EXPECT( + ConsensusState::Yes == + checkConsensus(0, 0, 0, 0, 3s, 10s, p, true, j)); + } + void testStandalone() { + using namespace std::chrono_literals; using namespace csf; + ConsensusParms parms; auto tg = TrustGraph::makeComplete(1); - Sim s(tg, topology(tg, fixed{LEDGER_GRANULARITY})); + Sim s(parms, tg, topology(tg, fixed{parms.ledgerGRANULARITY})); auto& p = s.peers[0]; @@ -63,10 +147,14 @@ public: using namespace csf; using namespace std::chrono; + ConsensusParms parms; auto tg = TrustGraph::makeComplete(5); Sim sim( + parms, tg, - topology(tg, fixed{round(0.2 * LEDGER_GRANULARITY)})); + topology( + tg, + fixed{round(0.2 * parms.ledgerGRANULARITY)})); // everyone submits their own ID as a TX and relay it to peers for (auto& p : sim.peers) @@ -87,29 +175,30 @@ public: } void - testSlowPeer() + testSlowPeers() { using namespace csf; using namespace std::chrono; - // Run two tests - // 1. The slow peer is participating in consensus - // 2. The slow peer is just observing + // Several tests of a complete trust graph with a subset of peers + // that have significantly longer network delays to the rest of the + // network - for (auto isParticipant : {true, false}) + // Test when a slow peer doesn't delay a consensus quorum (4/5 agree) { + ConsensusParms parms; auto tg = TrustGraph::makeComplete(5); - Sim sim(tg, topology(tg, [](PeerID i, PeerID j) { + // Peers 0 is slow, 1-4 are fast + // This choice is based on parms.minCONSENSUS_PCT of 80 + Sim sim(parms, tg, topology(tg, [&](PeerID i, PeerID j) { auto delayFactor = (i == 0 || j == 0) ? 1.1 : 0.2; return round( - delayFactor * LEDGER_GRANULARITY); + delayFactor * parms.ledgerGRANULARITY); })); - sim.peers[0].proposing_ = sim.peers[0].validating_ = isParticipant; - - // All peers submit their own ID as a transaction and relay it to - // peers + // All peers submit their own ID as a transaction and relay it + // to peers for (auto& p : sim.peers) { p.submit(Tx{p.id}); @@ -124,34 +213,11 @@ public: auto const& lgrID = p.prevLedgerID(); BEAST_EXPECT(lgrID.seq == 1); - // If peer 0 is participating - if (isParticipant) - { - BEAST_EXPECT(p.prevProposers() == sim.peers.size() - 1); - // Peer 0 closes first because it sees a quorum of agreeing - // positions from all other peers in one hop (1->0, 2->0, - // ..) The other peers take an extra timer period before - // they find that Peer 0 agrees with them ( 1->0->1, - // 2->0->2, ...) - if (p.id != 0) - BEAST_EXPECT( - p.prevRoundTime() > sim.peers[0].prevRoundTime()); - } - else // peer 0 is not participating - { - auto const proposers = p.prevProposers(); - if (p.id == 0) - BEAST_EXPECT(proposers == sim.peers.size() - 1); - else - BEAST_EXPECT(proposers == sim.peers.size() - 2); - - // so all peers should have closed together - BEAST_EXPECT( - p.prevRoundTime() == sim.peers[0].prevRoundTime()); - } + BEAST_EXPECT(p.prevProposers() == sim.peers.size() - 1); + BEAST_EXPECT(p.prevRoundTime() == sim.peers[0].prevRoundTime()); BEAST_EXPECT(lgrID.txs.find(Tx{0}) == lgrID.txs.end()); - for (std::uint32_t i = 1; i < sim.peers.size(); ++i) + for (std::uint32_t i = 2; i < sim.peers.size(); ++i) BEAST_EXPECT(lgrID.txs.find(Tx{i}) != lgrID.txs.end()); // Matches peer 0 ledger BEAST_EXPECT(lgrID.txs == sim.peers[0].prevLedgerID().txs); @@ -159,6 +225,99 @@ public: BEAST_EXPECT( sim.peers[0].openTxs.find(Tx{0}) != sim.peers[0].openTxs.end()); } + + // Test when the slow peers delay a consensus quorum (4/6 agree) + { + // Run two tests + // 1. The slow peers are participating in consensus + // 2. The slow peers are just observing + + for (auto isParticipant : {true, false}) + { + ConsensusParms parms; + auto tg = TrustGraph::makeComplete(6); + + // Peers 0,1 are slow, 2-5 are fast + // This choice is based on parms.minCONSENSUS_PCT of 80 + Sim sim( + parms, tg, topology(tg, [&](PeerID i, PeerID j) { + auto delayFactor = (i <= 1 || j <= 1) ? 1.1 : 0.2; + return round( + delayFactor * parms.ledgerGRANULARITY); + })); + + sim.peers[0].proposing_ = sim.peers[0].validating_ = + isParticipant; + sim.peers[1].proposing_ = sim.peers[1].validating_ = + isParticipant; + + // All peers submit their own ID as a transaction and relay it + // to peers + for (auto& p : sim.peers) + { + p.submit(Tx{p.id}); + } + + sim.run(1); + + // Verify all peers have same LCL but are missing transaction 0 + // which was not received by all peers before the ledger closed + for (auto& p : sim.peers) + { + auto const& lgrID = p.prevLedgerID(); + BEAST_EXPECT(lgrID.seq == 1); + + // If peer 0,1 are participating + if (isParticipant) + { + BEAST_EXPECT(p.prevProposers() == sim.peers.size() - 1); + // Due to the network link delay settings + // Peer 0 initially proposes {0} + // Peer 1 initially proposes {1} + // Peers 2-5 initially propose {2,3,4,5} + // Since peers 2-5 agree, 4/6 > the initial 50% + // threshold on disputed transactions, so Peer 0 and 1 + // change their position to match peers 2-5 and declare + // consensus now that 5/6 proposed positions match + // (themselves and peers 2-5). + // + // Peers 2-5 do not change position, since tx 0 or tx 1 + // have less than the 50% initial threshold. They also + // cannot declare consensus, since 4/6 < 80% threshold + // agreement on current positions. Instead, they have + // to wait an additional timerEntry call for the updated + // peer 0 and peer 1 positions to arrive. Once they do, + // now peers 2-5 see complete agreement and declare + // consensus + if (p.id > 1) + BEAST_EXPECT( + p.prevRoundTime() > + sim.peers[0].prevRoundTime()); + } + else // peer 0,1 are not participating + { + auto const proposers = p.prevProposers(); + if (p.id <= 1) + BEAST_EXPECT(proposers == sim.peers.size() - 2); + else + BEAST_EXPECT(proposers == sim.peers.size() - 3); + + // so all peers should have closed together + BEAST_EXPECT( + p.prevRoundTime() == sim.peers[0].prevRoundTime()); + } + + BEAST_EXPECT(lgrID.txs.find(Tx{0}) == lgrID.txs.end()); + for (std::uint32_t i = 2; i < sim.peers.size(); ++i) + BEAST_EXPECT(lgrID.txs.find(Tx{i}) != lgrID.txs.end()); + // Matches peer 0 ledger + BEAST_EXPECT(lgrID.txs == sim.peers[0].prevLedgerID().txs); + } + BEAST_EXPECT( + sim.peers[0].openTxs.find(Tx{0}) != + sim.peers[0].openTxs.end()); + } + } } void @@ -183,7 +342,7 @@ public: // the skews need to be at least 10 seconds. // Complicating this matter is that nodes will ignore proposals - // with times more than PROPOSE_FRESHNESS =20s in the past. So at + // with times more than proposeFRESHNESS =20s in the past. So at // the minimum granularity, we have at most 3 types of skews // (0s,10s,20s). @@ -191,22 +350,26 @@ public: // skew. Then no majority (1/3 < 1/2) of nodes will agree on an // actual close time. + ConsensusParms parms; auto tg = TrustGraph::makeComplete(6); Sim sim( + parms, tg, - topology(tg, fixed{round(0.2 * LEDGER_GRANULARITY)})); + topology( + tg, + fixed{round(0.2 * parms.ledgerGRANULARITY)})); // Run consensus without skew until we have a short close time // resolution while (sim.peers.front().lastClosedLedger.closeTimeResolution() >= - PROPOSE_FRESHNESS) + parms.proposeFRESHNESS) sim.run(1); // Introduce a shift on the time of half the peers - sim.peers[0].clockSkew = PROPOSE_FRESHNESS / 2; - sim.peers[1].clockSkew = PROPOSE_FRESHNESS / 2; - sim.peers[2].clockSkew = PROPOSE_FRESHNESS; - sim.peers[3].clockSkew = PROPOSE_FRESHNESS; + sim.peers[0].clockSkew = parms.proposeFRESHNESS / 2; + sim.peers[1].clockSkew = parms.proposeFRESHNESS / 2; + sim.peers[2].clockSkew = parms.proposeFRESHNESS; + sim.peers[3].clockSkew = parms.proposeFRESHNESS; // Verify all peers have the same LCL and it has all the Txs sim.run(1); @@ -224,9 +387,12 @@ public: // Specialized test to exercise a temporary fork in which some peers // are working on an incorrect prior ledger. + ConsensusParms parms; + + // Vary the time it takes to process validations to exercise detecting // the wrong LCL at different phases of consensus - for (auto validationDelay : {0s, LEDGER_MIN_CLOSE}) + for (auto validationDelay : {0ms, parms.ledgerMIN_CLOSE}) { // Consider 10 peers: // 0 1 2 3 4 5 6 7 8 9 @@ -256,10 +422,10 @@ public: // This topology can fork, which is why we are using it for this // test. - BEAST_EXPECT(tg.canFork(minimumConsensusPercentage / 100.)); + BEAST_EXPECT(tg.canFork(parms.minCONSENSUS_PCT / 100.)); - auto netDelay = round(0.2 * LEDGER_GRANULARITY); - Sim sim(tg, topology(tg, fixed{netDelay})); + auto netDelay = round(0.2 * parms.ledgerGRANULARITY); + Sim sim(parms, tg, topology(tg, fixed{netDelay})); // initial round to set prior state sim.run(1); @@ -283,7 +449,7 @@ public: // wrong ones) and recover within that round since wrong LCL // is detected before we close // - // With a validation delay of LEDGER_MIN_CLOSE, we need 3 more + // With a validation delay of ledgerMIN_CLOSE, we need 3 more // rounds. // 1. Round to generate different ledgers // 2. Round to detect different prior ledgers (but still generate @@ -324,9 +490,6 @@ public: // switchLCL switched from establish to open phase, but still processed // the establish phase logic. { - using namespace csf; - using namespace std::chrono; - // A mostly disjoint topology std::vector unls; unls.push_back({0, 1}); @@ -337,14 +500,14 @@ public: TrustGraph tg{unls, membership}; - Sim sim(tg, topology(tg, fixed{round( - 0.2 * LEDGER_GRANULARITY)})); + Sim sim(parms, tg, topology(tg, fixed{round( + 0.2 * parms.ledgerGRANULARITY)})); // initial ground to set prior state sim.run(1); for (auto &p : sim.peers) { // A long delay to acquire a missing ledger from the network - p.missingLedgerDelay = 2 * LEDGER_MIN_CLOSE; + p.missingLedgerDelay = 2 * parms.ledgerMIN_CLOSE; // Everyone sees only their own LCL p.openTxs.insert(Tx(p.id)); @@ -367,11 +530,15 @@ public: int numPeers = 10; for (int overlap = 0; overlap <= numPeers; ++overlap) { + ConsensusParms parms; auto tg = TrustGraph::makeClique(numPeers, overlap); Sim sim( + parms, tg, topology( - tg, fixed{round(0.2 * LEDGER_GRANULARITY)})); + tg, + fixed{ + round(0.2 * parms.ledgerGRANULARITY)})); // Initial round to set prior state sim.run(1); @@ -406,7 +573,7 @@ public: simClockSkew() { using namespace csf; - + using namespace std::chrono_literals; // Attempting to test what happens if peers enter consensus well // separated in time. Initial round (in which peers are not staggered) // is used to get the network going, then transactions are submitted @@ -422,8 +589,9 @@ public: for (auto stagger : {800ms, 1600ms, 3200ms, 30000ms, 45000ms, 300000ms}) { + ConsensusParms parms; auto tg = TrustGraph::makeComplete(5); - Sim sim(tg, topology(tg, [](PeerID i, PeerID) { + Sim sim(parms, tg, topology(tg, [](PeerID i, PeerID) { return 200ms * (i + 1); })); @@ -474,6 +642,7 @@ public: double transProb = 0.5; std::mt19937_64 rng; + ConsensusParms parms; auto tg = TrustGraph::makeRandomRanked( N, @@ -483,8 +652,11 @@ public: rng); Sim sim{ + parms, tg, - topology(tg, fixed{round(0.2 * LEDGER_GRANULARITY)})}; + topology( + tg, + fixed{round(0.2 * parms.ledgerGRANULARITY)})}; // Initial round to set prior state sim.run(1); @@ -511,9 +683,12 @@ public: void run() override { + testShouldCloseLedger(); + testCheckConsensus(); + testStandalone(); testPeersAgree(); - testSlowPeer(); + testSlowPeers(); testCloseTimeDisagree(); testWrongLCL(); testFork(); diff --git a/src/test/consensus/LedgerTiming_test.cpp b/src/test/consensus/LedgerTiming_test.cpp index 1c9894ab86..b3ce73733d 100644 --- a/src/test/consensus/LedgerTiming_test.cpp +++ b/src/test/consensus/LedgerTiming_test.cpp @@ -77,6 +77,7 @@ class LedgerTiming_test : public beast::unit_test::suite void testRoundCloseTime() { + using namespace std::chrono_literals; // A closeTime equal to the epoch is not modified using tp = NetClock::time_point; tp def; @@ -94,67 +95,11 @@ class LedgerTiming_test : public beast::unit_test::suite } - void testShouldCloseLedger() - { - // Bizarre times forcibly close - BEAST_EXPECT(shouldCloseLedger(true, 10, 10, 10, -10s, 10s, 1s, 1s, j)); - BEAST_EXPECT(shouldCloseLedger(true, 10, 10, 10, 100h, 10s, 1s, 1s, j)); - BEAST_EXPECT(shouldCloseLedger(true, 10, 10, 10, 10s, 100h, 1s, 1s, j)); - - // Rest of network has closed - BEAST_EXPECT(shouldCloseLedger(true, 10, 3, 5, 10s, 10s, 10s, 10s, j)); - - // No transactions means wait until end of internval - BEAST_EXPECT(!shouldCloseLedger(false, 10, 0, 0, 1s, 1s, 1s, 10s, j)); - BEAST_EXPECT(shouldCloseLedger(false, 10, 0, 0, 1s, 10s, 1s, 10s, j)); - - // Enforce minimum ledger open time - BEAST_EXPECT(!shouldCloseLedger(true, 10, 0, 0, 10s, 10s, 1s, 10s, j)); - - // Don't go too much faster than last time - BEAST_EXPECT(!shouldCloseLedger(true, 10, 0, 0, 10s, 10s, 3s, 10s, j)); - - BEAST_EXPECT(shouldCloseLedger(true, 10, 0, 0, 10s, 10s, 10s, 10s, j)); - - } - - void testCheckConsensus() - { - // Not enough time has elapsed - BEAST_EXPECT( ConsensusState::No - == checkConsensus(10, 2, 2, 0, 3s, 2s, true, j)); - - // If not enough peers have propsed, ensure - // more time for proposals - BEAST_EXPECT( ConsensusState::No - == checkConsensus(10, 2, 2, 0, 3s, 4s, true, j)); - - // Enough time has elapsed and we all agree - BEAST_EXPECT( ConsensusState::Yes - == checkConsensus(10, 2, 2, 0, 3s, 10s, true, j)); - - // Enough time has elapsed and we don't yet agree - BEAST_EXPECT( ConsensusState::No - == checkConsensus(10, 2, 1, 0, 3s, 10s, true, j)); - - // Our peers have moved on - // Enough time has elapsed and we all agree - BEAST_EXPECT( ConsensusState::MovedOn - == checkConsensus(10, 2, 1, 8, 3s, 10s, true, j)); - - // No peers makes it easy to agree - BEAST_EXPECT( ConsensusState::Yes - == checkConsensus(0, 0, 0, 0, 3s, 10s, true, j)); - - } - void run() override { testGetNextLedgerTimeResolution(); testRoundCloseTime(); - testShouldCloseLedger(); - testCheckConsensus(); } }; diff --git a/src/test/consensus/Validations_test.cpp b/src/test/consensus/Validations_test.cpp new file mode 100644 index 0000000000..7c807ac457 --- /dev/null +++ b/src/test/consensus/Validations_test.cpp @@ -0,0 +1,963 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012-2017 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace ripple { +namespace test { + +class Validations_test : public beast::unit_test::suite +{ + using clock_type = beast::abstract_clock const; + //-------------------------------------------------------------------------- + // Basic type wrappers for validation types + + // Represents a ledger sequence number + struct Seq + { + explicit Seq(std::uint32_t sIn) : s{sIn} + { + } + + Seq() : s{0} + { + } + + operator std::uint32_t() const + { + return s; + } + + std::uint32_t s; + }; + + // Represents a unique ledger identifier + struct ID + { + explicit ID(std::uint32_t idIn) : id{idIn} + { + } + + ID() : id{0} + { + } + + int + signum() const + { + return id == 0 ? 0 : 1; + } + + operator std::size_t() const + { + return id; + } + + template + friend void + hash_append(Hasher& h, ID const& id) + { + using beast::hash_append; + hash_append(h, id.id); + } + + std::uint32_t id; + }; + + class Node; + + // Basic implementation of the requirements of Validation in the generic + // Validations class + class Validation + { + friend class Node; + + ID ledgerID_ = ID{0}; + Seq seq_ = Seq{0}; + NetClock::time_point signTime_; + NetClock::time_point seenTime_; + std::string key_; + std::size_t nodeID_ = 0; + bool trusted_ = true; + boost::optional loadFee_; + + public: + Validation() + { + } + + ID + ledgerID() const + { + return ledgerID_; + } + + Seq + seq() const + { + return seq_; + } + + NetClock::time_point + signTime() const + { + return signTime_; + } + + NetClock::time_point + seenTime() const + { + return seenTime_; + } + + std::string + key() const + { + return key_; + } + + std::uint32_t + nodeID() const + { + return nodeID_; + } + + bool + trusted() const + { + return trusted_; + } + + boost::optional + loadFee() const + { + return loadFee_; + } + + Validation const& + unwrap() const + { + return *this; + } + + auto + asTie() const + { + return std::tie( + ledgerID_, + seq_, + signTime_, + seenTime_, + key_, + nodeID_, + trusted_, + loadFee_); + } + bool + operator==(Validation const& o) const + { + return asTie() == o.asTie(); + } + + bool + operator<(Validation const& o) const + { + return asTie() < o.asTie(); + } + }; + + // Helper to convert steady_clock to a reasonable NetClock + // This allows a single manual clock in the unit tests + static NetClock::time_point + toNetClock(clock_type const& c) + { + // We don't care about the actual epochs, but do want the + // generated NetClock time to be well past its epoch to ensure + // any subtractions are positive + using namespace std::chrono; + return NetClock::time_point(duration_cast( + c.now().time_since_epoch() + 86400s)); + } + + // Represents a node that can issue validations + class Node + { + clock_type const& c_; + std::size_t nodeID_; + bool trusted_ = true; + std::size_t signIdx_ = 0; + boost::optional loadFee_; + + public: + Node(std::uint32_t nodeID, clock_type const& c) : c_(c), nodeID_(nodeID) + { + } + + void + untrust() + { + trusted_ = false; + } + + void + trust() + { + trusted_ = true; + } + + void + setLoadFee(std::uint32_t fee) + { + loadFee_ = fee; + } + + std::size_t + nodeID() const + { + return nodeID_; + } + + void + advanceKey() + { + signIdx_++; + } + + std::string + masterKey() const + { + return std::to_string(nodeID_); + } + + std::string + currKey() const + { + return masterKey() + "_" + std::to_string(signIdx_); + } + + NetClock::time_point + now() const + { + return toNetClock(c_); + } + + // Issue a new validation with given sequence number and id and + // with signing and seen times offset from the common clock + Validation + validation( + Seq seq, + ID i, + NetClock::duration signOffset, + NetClock::duration seenOffset) const + { + Validation v; + v.seq_ = seq; + v.ledgerID_ = i; + + v.signTime_ = now() + signOffset; + v.seenTime_ = now() + seenOffset; + + v.nodeID_ = nodeID_; + v.key_ = currKey(); + v.trusted_ = trusted_; + v.loadFee_ = loadFee_; + return v; + } + + // Issue a new validation with the given sequence number and id + Validation + validation(Seq seq, ID i) const + { + return validation( + seq, i, NetClock::duration{0}, NetClock::duration{0}); + } + }; + + // Non-locking mutex to avoid the need for testing generic Validations + struct DummyMutex + { + void + lock() + { + } + + void + unlock() + { + } + }; + + // Saved StaleData for inspection in test + struct StaleData + { + std::vector stale; + hash_map flushed; + }; + + // Generic Validations policy that saves stale/flushed data into + // a StaleData instance. + class StalePolicy + { + StaleData& staleData_; + clock_type& c_; + + public: + StalePolicy(StaleData& sd, clock_type& c) + : staleData_{sd}, c_{c} + { + } + + NetClock::time_point + now() const + { + return toNetClock(c_); + } + + void + onStale(Validation&& v) + { + staleData_.stale.emplace_back(std::move(v)); + } + + void + flush(hash_map&& remaining) + { + staleData_.flushed = std::move(remaining); + } + }; + + // Specialize generic Validations using the above types + using TestValidations = + Validations; + + // Hoist enum for writing simpler tests + using AddOutcome = TestValidations::AddOutcome; + + // Gather the dependencies of TestValidations in a single class and provide + // accessors for simplifying test logic + class TestHarness + { + StaleData staleData_; + ValidationParms p_; + beast::manual_clock clock_; + beast::Journal j_; + TestValidations tv_; + int nextNodeId_ = 0; + + public: + TestHarness() : tv_(p_, clock_, j_, staleData_, clock_) + { + } + + // Helper to add an existing validation + AddOutcome + add(Node const& n, Validation const& v) + { + return tv_.add(n.masterKey(), v); + } + + // Helper to directly create the validation + template + std::enable_if_t<(sizeof...(Ts) > 1), AddOutcome> + add(Node const& n, Ts&&... ts) + { + return add(n, n.validation(std::forward(ts)...)); + } + + TestValidations& + vals() + { + return tv_; + } + + Node + makeNode() + { + return Node(nextNodeId_++, clock_); + } + + ValidationParms + parms() const + { + return p_; + } + + auto& + clock() + { + return clock_; + } + + std::vector const& + stale() const + { + return staleData_.stale; + } + + hash_map const& + flushed() const + { + return staleData_.flushed; + } + }; + + void + testAddValidation() + { + // Test adding current,stale,repeat,sameSeq validations + using namespace std::chrono_literals; + + TestHarness harness; + Node a = harness.makeNode(); + { + { + auto const v = a.validation(Seq{1}, ID{1}); + + // Add a current validation + BEAST_EXPECT(AddOutcome::current == harness.add(a, v)); + + // Re-adding is repeat + BEAST_EXPECT(AddOutcome::repeat == harness.add(a, v)); + } + + { + harness.clock().advance(1s); + // Replace with a new validation and ensure the old one is stale + BEAST_EXPECT(harness.stale().empty()); + + BEAST_EXPECT( + AddOutcome::current == harness.add(a, Seq{2}, ID{2})); + + BEAST_EXPECT(harness.stale().size() == 1); + + BEAST_EXPECT(harness.stale()[0].ledgerID() == 1); + } + + { + // Test the node changing signing key, then reissuing a ledger + + // Confirm old ledger on hand, but not new ledger + BEAST_EXPECT(harness.vals().numTrustedForLedger(ID{2}) == 1); + BEAST_EXPECT(harness.vals().numTrustedForLedger(ID{20}) == 0); + + // Issue a new signing key and re-issue the validation with a + // new ID but the same sequence number + a.advanceKey(); + + // No validations following ID{2} + BEAST_EXPECT(harness.vals().getNodesAfter(ID{2}) == 0); + + BEAST_EXPECT( + AddOutcome::sameSeq == harness.add(a, Seq{2}, ID{20})); + + // Old ID should be gone ... + BEAST_EXPECT(harness.vals().numTrustedForLedger(ID{2}) == 0); + BEAST_EXPECT(harness.vals().numTrustedForLedger(ID{20}) == 1); + { + // Should be the only trusted for ID{20} + auto trustedVals = + harness.vals().getTrustedForLedger(ID{20}); + BEAST_EXPECT(trustedVals.size() == 1); + BEAST_EXPECT(trustedVals[0].key() == a.currKey()); + // ... and should be the only node after ID{2} + BEAST_EXPECT(harness.vals().getNodesAfter(ID{2}) == 1); + + } + + // A new key, but re-issue a validation with the same ID and + // Sequence + a.advanceKey(); + + BEAST_EXPECT( + AddOutcome::sameSeq == harness.add(a, Seq{2}, ID{20})); + { + // Still the only trusted validation for ID{20} + auto trustedVals = + harness.vals().getTrustedForLedger(ID{20}); + BEAST_EXPECT(trustedVals.size() == 1); + BEAST_EXPECT(trustedVals[0].key() == a.currKey()); + // and still follows ID{2} since it was a re-issue + BEAST_EXPECT(harness.vals().getNodesAfter(ID{2}) == 1); + } + } + + { + // Processing validations out of order should ignore the older + harness.clock().advance(2s); + auto const val3 = a.validation(Seq{3}, ID{3}); + + harness.clock().advance(4s); + auto const val4 = a.validation(Seq{4}, ID{4}); + + BEAST_EXPECT(AddOutcome::current == harness.add(a, val4)); + + BEAST_EXPECT(AddOutcome::stale == harness.add(a, val3)); + + // re-issued should not be added + auto const val4reissue = a.validation(Seq{4}, ID{44}); + + BEAST_EXPECT(AddOutcome::stale == harness.add(a, val4reissue)); + + } + { + // Process validations out of order with shifted times + + // flush old validations + harness.clock().advance(1h); + + // Establish a new current validation + BEAST_EXPECT( + AddOutcome::current == harness.add(a, Seq{8}, ID{8})); + + // Process a validation that has "later" seq but early sign time + BEAST_EXPECT( + AddOutcome::stale == + harness.add(a, Seq{9}, ID{9}, -1s, -1s)); + + // Process a validation that has an "earlier" seq but later sign time + BEAST_EXPECT( + AddOutcome::current == + harness.add(a, Seq{7}, ID{7}, 1s, 1s)); + } + { + // Test stale on arrival validations + harness.clock().advance(1h); + + BEAST_EXPECT( + AddOutcome::stale == + harness.add( + a, + Seq{15}, + ID{15}, + -harness.parms().validationCURRENT_EARLY, + 0s)); + + BEAST_EXPECT( + AddOutcome::stale == + harness.add( + a, + Seq{15}, + ID{15}, + harness.parms().validationCURRENT_WALL, + 0s)); + + BEAST_EXPECT( + AddOutcome::stale == + harness.add( + a, + Seq{15}, + ID{15}, + 0s, + harness.parms().validationCURRENT_LOCAL)); + } + } + } + + void + testOnStale() + { + // Verify validation becomes stale based solely on time passing + TestHarness harness; + Node a = harness.makeNode(); + + BEAST_EXPECT(AddOutcome::current == harness.add(a, Seq{1}, ID{1})); + harness.vals().currentTrusted(); + BEAST_EXPECT(harness.stale().empty()); + harness.clock().advance(harness.parms().validationCURRENT_LOCAL); + + // trigger iteration over current + harness.vals().currentTrusted(); + + BEAST_EXPECT(harness.stale().size() == 1); + BEAST_EXPECT(harness.stale()[0].ledgerID() == 1); + } + + void + testGetNodesAfter() + { + // Test getting number of nodes working on a validation following + // a prescribed one + using namespace std::chrono_literals; + + TestHarness harness; + Node a = harness.makeNode(), b = harness.makeNode(), + c = harness.makeNode(), d = harness.makeNode(); + + c.untrust(); + + // first round a,b,c agree, d has differing id + for (auto const& node : {a, b, c}) + BEAST_EXPECT( + AddOutcome::current == harness.add(node, Seq{1}, ID{1})); + BEAST_EXPECT(AddOutcome::current == harness.add(d, Seq{1}, ID{10})); + + // Nothing past ledger 1 yet + BEAST_EXPECT(harness.vals().getNodesAfter(ID{1}) == 0); + + harness.clock().advance(5s); + + // a and b have the same prior id, but b has a different current id + // c is untrusted but on the same prior id + // d has a different prior id + BEAST_EXPECT(AddOutcome::current == harness.add(a, Seq{2}, ID{2})); + BEAST_EXPECT(AddOutcome::current == harness.add(b, Seq{2}, ID{20})); + BEAST_EXPECT(AddOutcome::current == harness.add(c, Seq{2}, ID{2})); + BEAST_EXPECT(AddOutcome::current == harness.add(d, Seq{2}, ID{2})); + + BEAST_EXPECT(harness.vals().getNodesAfter(ID{1}) == 2); + } + + void + testCurrentTrusted() + { + // Test getting current trusted validations + using namespace std::chrono_literals; + + TestHarness harness; + Node a = harness.makeNode(), b = harness.makeNode(); + b.untrust(); + + BEAST_EXPECT(AddOutcome::current == harness.add(a, Seq{1}, ID{1})); + BEAST_EXPECT(AddOutcome::current == harness.add(b, Seq{1}, ID{3})); + + // Only a is trusted + BEAST_EXPECT(harness.vals().currentTrusted().size() == 1); + BEAST_EXPECT(harness.vals().currentTrusted()[0].ledgerID() == ID{1}); + BEAST_EXPECT(harness.vals().currentTrusted()[0].seq() == Seq{1}); + + harness.clock().advance(3s); + + for (auto const& node : {a, b}) + BEAST_EXPECT( + AddOutcome::current == harness.add(node, Seq{2}, ID{2})); + + // New validation for a + BEAST_EXPECT(harness.vals().currentTrusted().size() == 1); + BEAST_EXPECT(harness.vals().currentTrusted()[0].ledgerID() == ID{2}); + BEAST_EXPECT(harness.vals().currentTrusted()[0].seq() == Seq{2}); + + // Pass enough time for it to go stale + harness.clock().advance(harness.parms().validationCURRENT_LOCAL); + BEAST_EXPECT(harness.vals().currentTrusted().empty()); + } + + void + testGetCurrentPublicKeys() + { + // Test getting current keys validations + using namespace std::chrono_literals; + + TestHarness harness; + Node a = harness.makeNode(), b = harness.makeNode(); + b.untrust(); + + for (auto const& node : {a, b}) + BEAST_EXPECT( + AddOutcome::current == harness.add(node, Seq{1}, ID{1})); + + { + hash_set const expectedKeys = {a.masterKey(), + b.masterKey()}; + BEAST_EXPECT( + harness.vals().getCurrentPublicKeys() == expectedKeys); + } + + harness.clock().advance(3s); + + // Change keys + a.advanceKey(); + b.advanceKey(); + + for (auto const& node : {a, b}) + BEAST_EXPECT( + AddOutcome::current == harness.add(node, Seq{2}, ID{2})); + + { + hash_set const expectedKeys = {a.masterKey(), + b.masterKey()}; + BEAST_EXPECT( + harness.vals().getCurrentPublicKeys() == expectedKeys); + } + + // Pass enough time for them to go stale + harness.clock().advance(harness.parms().validationCURRENT_LOCAL); + BEAST_EXPECT(harness.vals().getCurrentPublicKeys().empty()); + } + + void + testCurrentTrustedDistribution() + { + // Test the trusted distribution calculation, including ledger slips + // and sequence cutoffs + using namespace std::chrono_literals; + + TestHarness harness; + + Node baby = harness.makeNode(), papa = harness.makeNode(), + mama = harness.makeNode(), goldilocks = harness.makeNode(); + goldilocks.untrust(); + + // Stagger the validations around sequence 2 + // papa on seq 1 is behind + // baby on seq 2 is just right + // mama on seq 3 is ahead + // goldilocks on seq 2, but is not trusted + + for (auto const& node : {baby, papa, mama, goldilocks}) + BEAST_EXPECT( + AddOutcome::current == harness.add(node, Seq{1}, ID{1})); + + harness.clock().advance(1s); + for (auto const& node : {baby, mama, goldilocks}) + BEAST_EXPECT( + AddOutcome::current == harness.add(node, Seq{2}, ID{2})); + + harness.clock().advance(1s); + BEAST_EXPECT(AddOutcome::current == harness.add(mama, Seq{3}, ID{3})); + + { + // Allow slippage that treats all trusted as the current ledger + auto res = harness.vals().currentTrustedDistribution( + ID{2}, // Current ledger + ID{1}, // Prior ledger + Seq{0}); // No cutoff + + BEAST_EXPECT(res.size() == 1); + BEAST_EXPECT(res[ID{2}] == 3); + } + + { + // Don't allow slippage back for prior ledger + auto res = harness.vals().currentTrustedDistribution( + ID{2}, // Current ledger + ID{0}, // No prior ledger + Seq{0}); // No cutoff + + BEAST_EXPECT(res.size() == 2); + BEAST_EXPECT(res[ID{2}] == 2); + BEAST_EXPECT(res[ID{1}] == 1); + } + + { + // Don't allow any slips + auto res = harness.vals().currentTrustedDistribution( + ID{0}, // No current ledger + ID{0}, // No prior ledger + Seq{0}); // No cutoff + + BEAST_EXPECT(res.size() == 3); + BEAST_EXPECT(res[ID{1}] == 1); + BEAST_EXPECT(res[ID{2}] == 1); + BEAST_EXPECT(res[ID{3}] == 1); + } + + { + // Cutoff old sequence numbers + auto res = harness.vals().currentTrustedDistribution( + ID{2}, // current ledger + ID{1}, // prior ledger + Seq{2}); // Only sequence 2 or later + BEAST_EXPECT(res.size() == 1); + BEAST_EXPECT(res[ID{2}] == 2); + } + } + + void + testTrustedByLedgerFunctions() + { + // Test the Validations functions that calculate a value by ledger ID + using namespace std::chrono_literals; + + // Several Validations functions return a set of values associated + // with trusted ledgers sharing the same ledger ID. The tests below + // exercise this logic by saving the set of trusted Validations, and + // verifying that the Validations member functions all calculate the + // proper transformation of the available ledgers. + + TestHarness harness; + Node a = harness.makeNode(), b = harness.makeNode(), + c = harness.makeNode(), d = harness.makeNode(); + c.untrust(); + // Mix of load fees + a.setLoadFee(12); + b.setLoadFee(1); + c.setLoadFee(12); + + hash_map> trustedValidations; + + //---------------------------------------------------------------------- + // checkers + auto sorted = [](auto vec) { + std::sort(vec.begin(), vec.end()); + return vec; + }; + auto compare = [&]() { + for (auto& it : trustedValidations) + { + auto const& id = it.first; + auto const& expectedValidations = it.second; + + BEAST_EXPECT( + harness.vals().numTrustedForLedger(id) == + expectedValidations.size()); + BEAST_EXPECT( + sorted(harness.vals().getTrustedForLedger(id)) == + sorted(expectedValidations)); + + std::vector expectedTimes; + std::uint32_t baseFee = 0; + std::vector expectedFees; + for (auto const& val : expectedValidations) + { + expectedTimes.push_back(val.signTime()); + expectedFees.push_back(val.loadFee().value_or(baseFee)); + } + + BEAST_EXPECT( + sorted(harness.vals().fees(id, baseFee)) == + sorted(expectedFees)); + + BEAST_EXPECT( + sorted(harness.vals().getTrustedValidationTimes(id)) == + sorted(expectedTimes)); + } + }; + + //---------------------------------------------------------------------- + // Add a dummy ID to cover unknown ledger identifiers + trustedValidations[ID{100}] = {}; + + // first round a,b,c agree, d differs + for (auto const& node : {a, b, c}) + { + auto const val = node.validation(Seq{1}, ID{1}); + BEAST_EXPECT(AddOutcome::current == harness.add(node, val)); + if (val.trusted()) + trustedValidations[val.ledgerID()].emplace_back(val); + } + { + auto const val = d.validation(Seq{1}, ID{11}); + BEAST_EXPECT(AddOutcome::current == harness.add(d, val)); + trustedValidations[val.ledgerID()].emplace_back(val); + } + + harness.clock().advance(5s); + // second round, a,b,c move to ledger 2, d now thinks ledger 1 + for (auto const& node : {a, b, c}) + { + auto const val = node.validation(Seq{2}, ID{2}); + BEAST_EXPECT(AddOutcome::current == harness.add(node, val)); + if (val.trusted()) + trustedValidations[val.ledgerID()].emplace_back(val); + } + { + auto const val = d.validation(Seq{2}, ID{1}); + BEAST_EXPECT(AddOutcome::current == harness.add(d, val)); + trustedValidations[val.ledgerID()].emplace_back(val); + } + + compare(); + } + + void + testExpire() + { + // Verify expiring clears out validations stored by ledger + + TestHarness harness; + Node a = harness.makeNode(); + + BEAST_EXPECT(AddOutcome::current == harness.add(a, Seq{1}, ID{1})); + BEAST_EXPECT(harness.vals().numTrustedForLedger(ID{1})); + harness.clock().advance(harness.parms().validationSET_EXPIRES); + harness.vals().expire(); + BEAST_EXPECT(!harness.vals().numTrustedForLedger(ID{1})); + } + + void + testFlush() + { + // Test final flush of validations + using namespace std::chrono_literals; + + TestHarness harness; + + Node a = harness.makeNode(), b = harness.makeNode(), + c = harness.makeNode(); + c.untrust(); + + hash_map expected; + Validation staleA; + for (auto const& node : {a, b, c}) + { + auto const val = node.validation(Seq{1}, ID{1}); + BEAST_EXPECT(AddOutcome::current == harness.add(node, val)); + if (node.nodeID() == a.nodeID()) + { + staleA = val; + } + else + expected[node.masterKey()] = val; + } + + // Send in a new validation for a, saving the new one into the expected + // map after setting the proper prior ledger ID it replaced + harness.clock().advance(1s); + auto newVal = a.validation(Seq{2}, ID{2}); + BEAST_EXPECT(AddOutcome::current == harness.add(a, newVal)); + expected[a.masterKey()] = newVal; + + // Now flush + harness.vals().flush(); + + // Original a validation was stale + BEAST_EXPECT(harness.stale().size() == 1); + BEAST_EXPECT(harness.stale()[0] == staleA); + BEAST_EXPECT(harness.stale()[0].nodeID() == a.nodeID()); + + auto const& flushed = harness.flushed(); + + BEAST_EXPECT(flushed == expected); + } + + void + run() override + { + testAddValidation(); + testOnStale(); + testGetNodesAfter(); + testCurrentTrusted(); + testGetCurrentPublicKeys(); + testCurrentTrustedDistribution(); + testTrustedByLedgerFunctions(); + testExpire(); + testFlush(); + } +}; + +BEAST_DEFINE_TESTSUITE(Validations, consensus, ripple); +} // namespace test +} // namespace ripple diff --git a/src/test/core/ClosureCounter_test.cpp b/src/test/core/ClosureCounter_test.cpp new file mode 100644 index 0000000000..406cac47ab --- /dev/null +++ b/src/test/core/ClosureCounter_test.cpp @@ -0,0 +1,329 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2017 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { +namespace test { + +//------------------------------------------------------------------------------ + +class ClosureCounter_test : public beast::unit_test::suite +{ + // We're only using Env for its Journal. + jtx::Env env {*this}; + beast::Journal j {env.app().journal ("ClosureCounter_test")}; + + void testConstruction() + { + // Build different kinds of ClosureCounters. + { + // Count closures that return void and take no arguments. + ClosureCounter voidCounter; + BEAST_EXPECT (voidCounter.count() == 0); + + int evidence = 0; + // Make sure voidCounter.wrap works with an rvalue closure. + auto wrapped = voidCounter.wrap ([&evidence] () { ++evidence; }); + BEAST_EXPECT (voidCounter.count() == 1); + BEAST_EXPECT (evidence == 0); + BEAST_EXPECT (wrapped); + + // wrapped() should be callable with no arguments. + (*wrapped)(); + BEAST_EXPECT (evidence == 1); + (*wrapped)(); + BEAST_EXPECT (evidence == 2); + + // Destroying the contents of wrapped should decrement voidCounter. + wrapped = boost::none; + BEAST_EXPECT (voidCounter.count() == 0); + } + { + // Count closures that return void and take one int argument. + ClosureCounter setCounter; + BEAST_EXPECT (setCounter.count() == 0); + + int evidence = 0; + // Make sure setCounter.wrap works with a non-const lvalue closure. + auto setInt = [&evidence] (int i) { evidence = i; }; + auto wrapped = setCounter.wrap (setInt); + + BEAST_EXPECT (setCounter.count() == 1); + BEAST_EXPECT (evidence == 0); + BEAST_EXPECT (wrapped); + + // wrapped() should be callable with one integer argument. + (*wrapped)(5); + BEAST_EXPECT (evidence == 5); + (*wrapped)(11); + BEAST_EXPECT (evidence == 11); + + // Destroying the contents of wrapped should decrement setCounter. + wrapped = boost::none; + BEAST_EXPECT (setCounter.count() == 0); + } + { + // Count closures that return int and take two int arguments. + ClosureCounter sumCounter; + BEAST_EXPECT (sumCounter.count() == 0); + + // Make sure sumCounter.wrap works with a const lvalue closure. + auto const sum = [] (int i, int j) { return i + j; }; + auto wrapped = sumCounter.wrap (sum); + + BEAST_EXPECT (sumCounter.count() == 1); + BEAST_EXPECT (wrapped); + + // wrapped() should be callable with two integers. + BEAST_EXPECT ((*wrapped)(5, 2) == 7); + BEAST_EXPECT ((*wrapped)(2, -8) == -6); + + // Destroying the contents of wrapped should decrement sumCounter. + wrapped = boost::none; + BEAST_EXPECT (sumCounter.count() == 0); + } + } + + // A class used to test argument passing. + class TrackedString + { + public: + int copies = {0}; + int moves = {0}; + std::string str; + + TrackedString() = delete; + + explicit TrackedString(char const* rhs) + : str (rhs) {} + + // Copy constructor + TrackedString (TrackedString const& rhs) + : copies (rhs.copies + 1) + , moves (rhs.moves) + , str (rhs.str) {} + + // Move constructor + TrackedString (TrackedString&& rhs) + : copies (rhs.copies) + , moves (rhs.moves + 1) + , str (std::move(rhs.str)) {} + + // Delete copy and move assignment. + TrackedString& operator=(TrackedString const& rhs) = delete; + + // String concatenation + TrackedString& operator+=(char const* rhs) + { + str += rhs; + return *this; + } + + friend + TrackedString operator+(TrackedString const& str, char const* rhs) + { + TrackedString ret {str}; + ret.str += rhs; + return ret; + } + }; + + void testArgs() + { + // Make sure a wrapped closure handles rvalue reference arguments + // correctly. + { + // Pass by value. + ClosureCounter strCounter; + BEAST_EXPECT (strCounter.count() == 0); + + auto wrapped = strCounter.wrap ( + [] (TrackedString in) { return in += "!"; }); + + BEAST_EXPECT (strCounter.count() == 1); + BEAST_EXPECT (wrapped); + + TrackedString const strValue ("value"); + TrackedString const result = (*wrapped)(strValue); + BEAST_EXPECT (result.copies == 2); + BEAST_EXPECT (result.moves == 1); + BEAST_EXPECT (result.str == "value!"); + BEAST_EXPECT (strValue.str.size() == 5); + } + { + // Use a const lvalue argument. + ClosureCounter strCounter; + BEAST_EXPECT (strCounter.count() == 0); + + auto wrapped = strCounter.wrap ( + [] (TrackedString const& in) { return in + "!"; }); + + BEAST_EXPECT (strCounter.count() == 1); + BEAST_EXPECT (wrapped); + + TrackedString const strConstLValue ("const lvalue"); + TrackedString const result = (*wrapped)(strConstLValue); + BEAST_EXPECT (result.copies == 1); + // BEAST_EXPECT (result.moves == ?); // moves VS == 1, gcc == 0 + BEAST_EXPECT (result.str == "const lvalue!"); + BEAST_EXPECT (strConstLValue.str.size() == 12); + } + { + // Use a non-const lvalue argument. + ClosureCounter strCounter; + BEAST_EXPECT (strCounter.count() == 0); + + auto wrapped = strCounter.wrap ( + [] (TrackedString& in) { return in += "!"; }); + + BEAST_EXPECT (strCounter.count() == 1); + BEAST_EXPECT (wrapped); + + TrackedString strLValue ("lvalue"); + TrackedString const result = (*wrapped)(strLValue); + BEAST_EXPECT (result.copies == 1); + BEAST_EXPECT (result.moves == 0); + BEAST_EXPECT (result.str == "lvalue!"); + BEAST_EXPECT (strLValue.str == result.str); + } + { + // Use an rvalue argument. + ClosureCounter strCounter; + BEAST_EXPECT (strCounter.count() == 0); + + auto wrapped = strCounter.wrap ( + [] (TrackedString&& in) { + // Note that none of the compilers noticed that in was + // leaving scope. So, without intervention, they would + // do a copy for the return (June 2017). An explicit + // std::move() was required. + return std::move(in += "!"); + }); + + BEAST_EXPECT (strCounter.count() == 1); + BEAST_EXPECT (wrapped); + + // Make the string big enough to (probably) avoid the small string + // optimization. + TrackedString strRValue ("rvalue abcdefghijklmnopqrstuvwxyz"); + TrackedString const result = (*wrapped)(std::move(strRValue)); + BEAST_EXPECT (result.copies == 0); + BEAST_EXPECT (result.moves == 1); + BEAST_EXPECT (result.str == "rvalue abcdefghijklmnopqrstuvwxyz!"); + BEAST_EXPECT (strRValue.str.size() == 0); + } + } + + void testWrap() + { + // Verify reference counting. + ClosureCounter voidCounter; + BEAST_EXPECT (voidCounter.count() == 0); + { + auto wrapped1 = voidCounter.wrap ([] () {}); + BEAST_EXPECT (voidCounter.count() == 1); + { + // Copy should increase reference count. + auto wrapped2 (wrapped1); + BEAST_EXPECT (voidCounter.count() == 2); + { + // Move should increase reference count. + auto wrapped3 (std::move(wrapped2)); + BEAST_EXPECT (voidCounter.count() == 3); + { + // An additional closure also increases count. + auto wrapped4 = voidCounter.wrap ([] () {}); + BEAST_EXPECT (voidCounter.count() == 4); + } + BEAST_EXPECT (voidCounter.count() == 3); + } + BEAST_EXPECT (voidCounter.count() == 2); + } + BEAST_EXPECT (voidCounter.count() == 1); + } + BEAST_EXPECT (voidCounter.count() == 0); + + // Join with 0 count should not stall. + using namespace std::chrono_literals; + voidCounter.join("testWrap", 1ms, j); + + // Wrapping a closure after join() should return boost::none. + BEAST_EXPECT (voidCounter.wrap ([] () {}) == boost::none); + } + + void testWaitOnJoin() + { + // Verify reference counting. + ClosureCounter voidCounter; + BEAST_EXPECT (voidCounter.count() == 0); + + auto wrapped = (voidCounter.wrap ([] () {})); + BEAST_EXPECT (voidCounter.count() == 1); + + // Calling join() now should stall, so do it on a different thread. + std::atomic threadExited {false}; + std::thread localThread ([&voidCounter, &threadExited, this] () + { + // Should stall after calling join. + using namespace std::chrono_literals; + voidCounter.join("testWaitOnJoin", 1ms, j); + threadExited.store (true); + }); + + // Wait for the thread to call voidCounter.join(). + while (! voidCounter.joined()); + + // The thread should still be active after waiting 5 milliseconds. + // This is not a guarantee that join() stalled the thread, but it + // improves confidence. + using namespace std::chrono_literals; + std::this_thread::sleep_for (5ms); + BEAST_EXPECT (threadExited == false); + + // Destroy the contents of wrapped and expect the thread to exit + // (asynchronously). + wrapped = boost::none; + BEAST_EXPECT (voidCounter.count() == 0); + + // Wait for the thread to exit. + while (threadExited == false); + localThread.join(); + } + +public: + void run() + { + testConstruction(); + testArgs(); + testWrap(); + testWaitOnJoin(); + } +}; + +BEAST_DEFINE_TESTSUITE(ClosureCounter, core, ripple); + +} // test +} // ripple diff --git a/src/test/core/DeadlineTimer_test.cpp b/src/test/core/DeadlineTimer_test.cpp deleted file mode 100644 index 05681bd6b6..0000000000 --- a/src/test/core/DeadlineTimer_test.cpp +++ /dev/null @@ -1,123 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2016 Ripple Labs Inc. - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ -//============================================================================== - -#include -#include -#include -#include -#include - -namespace ripple { - -//------------------------------------------------------------------------------ - -class DeadlineTimer_test : public beast::unit_test::suite -{ -public: - struct TestCallback : DeadlineTimer::Listener - { - TestCallback() = default; - - void onDeadlineTimer (DeadlineTimer&) override - { - ++count; - } - - std::atomic count {0}; - }; - - void testExpiration() - { - using clock = DeadlineTimer::clock; - - using namespace std::chrono_literals; - using namespace std::this_thread; - - TestCallback cb; - DeadlineTimer dt {&cb}; - - // There are parts of this test that are somewhat race conditional. - // The test is designed to avoid spurious failures, rather than - // fail occasionally but randomly, where ever possible. So there may - // be occasional gratuitous passes. Unfortunately, since it is a - // time-based test, there may also be occasional spurious failures - // on low-powered continuous integration platforms. - { - testcase("Expiration"); - - // Set a deadline timer that should only fire once in 5ms. - cb.count = 0; - auto const startTime = clock::now(); - dt.setExpiration (5ms); - - // Make sure the timer didn't fire immediately. - int const count = cb.count.load(); - if (clock::now() < startTime + 4ms) - { - BEAST_EXPECT (count == 0); - } - - // Wait until the timer should have fired and check that it did. - // In fact, we wait long enough that if it were to fire multiple - // times we'd see that. - sleep_until (startTime + 50ms); - BEAST_EXPECT (cb.count.load() == 1); - } - { - testcase("RecurringExpiration"); - - // Set a deadline timer that should fire once every 5ms. - cb.count = 0; - auto const startTime = clock::now(); - dt.setRecurringExpiration (5ms); - - // Make sure the timer didn't fire immediately. - { - int const count = cb.count.load(); - if (clock::now() < startTime + 4ms) - { - BEAST_EXPECT (count == 0); - } - } - - // Wait until the timer should have fired several times and - // check that it did. - sleep_until (startTime + 100ms); - { - auto const count = cb.count.load(); - BEAST_EXPECT ((count > 1) && (count < 21)); - } - - // Cancel the recurring timer and it should not fire any more. - dt.cancel(); - auto const count = cb.count.load(); - sleep_for (50ms); - BEAST_EXPECT (count == cb.count.load()); - } - } - - void run() - { - testExpiration(); - } -}; - -BEAST_DEFINE_TESTSUITE(DeadlineTimer, core, ripple); - -} \ No newline at end of file diff --git a/src/test/core/JobCounter_test.cpp b/src/test/core/JobCounter_test.cpp deleted file mode 100644 index 435fccecab..0000000000 --- a/src/test/core/JobCounter_test.cpp +++ /dev/null @@ -1,122 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2017 Ripple Labs Inc. - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ -//============================================================================== - -#include -#include -#include -#include -#include -#include - -namespace ripple { - -//------------------------------------------------------------------------------ - -class JobCounter_test : public beast::unit_test::suite -{ - void testWrap() - { - // Verify reference counting. - JobCounter jobCounter; - BEAST_EXPECT (jobCounter.count() == 0); - { - auto wrapped1 = jobCounter.wrap ([] (Job&) {}); - BEAST_EXPECT (jobCounter.count() == 1); - - // wrapped1 should be callable with a Job. - { - Job j; - (*wrapped1)(j); - } - { - // Copy should increase reference count. - auto wrapped2 (wrapped1); - BEAST_EXPECT (jobCounter.count() == 2); - { - // Move should increase reference count. - auto wrapped3 (std::move(wrapped2)); - BEAST_EXPECT (jobCounter.count() == 3); - { - // An additional Job also increases count. - auto wrapped4 = jobCounter.wrap ([] (Job&) {}); - BEAST_EXPECT (jobCounter.count() == 4); - } - BEAST_EXPECT (jobCounter.count() == 3); - } - BEAST_EXPECT (jobCounter.count() == 2); - } - BEAST_EXPECT (jobCounter.count() == 1); - } - BEAST_EXPECT (jobCounter.count() == 0); - - // Join with 0 count should not stall. - jobCounter.join(); - - // Wrapping a Job after join() should return boost::none. - BEAST_EXPECT (jobCounter.wrap ([] (Job&) {}) == boost::none); - } - - void testWaitOnJoin() - { - // Verify reference counting. - JobCounter jobCounter; - BEAST_EXPECT (jobCounter.count() == 0); - - auto job = (jobCounter.wrap ([] (Job&) {})); - BEAST_EXPECT (jobCounter.count() == 1); - - // Calling join() now should stall, so do it on a different thread. - std::atomic threadExited {false}; - std::thread localThread ([&jobCounter, &threadExited] () - { - // Should stall after calling join. - jobCounter.join(); - threadExited.store (true); - }); - - // Wait for the thread to call jobCounter.join(). - while (! jobCounter.joined()); - - // The thread should still be active after waiting a millisecond. - // This is not a guarantee that join() stalled the thread, but it - // improves confidence. - using namespace std::chrono_literals; - std::this_thread::sleep_for (1ms); - BEAST_EXPECT (threadExited == false); - - // Destroy the Job and expect the thread to exit (asynchronously). - job = boost::none; - BEAST_EXPECT (jobCounter.count() == 0); - - // Wait for the thread to exit. - while (threadExited == false); - localThread.join(); - } - -public: - void run() - { - testWrap(); - testWaitOnJoin(); - } -}; - -BEAST_DEFINE_TESTSUITE(JobCounter, core, ripple); - -} diff --git a/src/test/core/JobQueue_test.cpp b/src/test/core/JobQueue_test.cpp new file mode 100644 index 0000000000..11f355dd62 --- /dev/null +++ b/src/test/core/JobQueue_test.cpp @@ -0,0 +1,154 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2017 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include + +namespace ripple { +namespace test { + +//------------------------------------------------------------------------------ + +class JobQueue_test : public beast::unit_test::suite +{ + void testAddJob() + { + jtx::Env env {*this}; + + JobQueue& jQueue = env.app().getJobQueue(); + { + // addJob() should run the Job (and return true). + std::atomic jobRan {false}; + BEAST_EXPECT (jQueue.addJob (jtCLIENT, "JobAddTest1", + [&jobRan] (Job&) { jobRan = true; }) == true); + + // Wait for the Job to run. + while (jobRan == false); + } + { + // If the JobQueue's JobCounter is join()ed we should no + // longer be able to add Jobs (and calling addJob() should + // return false). + using namespace std::chrono_literals; + beast::Journal j {env.app().journal ("JobQueue_test")}; + JobCounter& jCounter = jQueue.jobCounter(); + jCounter.join("JobQueue_test", 1s, j); + + // The Job should never run, so having the Job access this + // unprotected variable on the stack should be completely safe. + // Not recommended for the faint of heart... + bool unprotected; + BEAST_EXPECT (jQueue.addJob (jtCLIENT, "JobAddTest2", + [&unprotected] (Job&) { unprotected = false; }) == false); + } + } + + void testPostCoro() + { + jtx::Env env {*this}; + + JobQueue& jQueue = env.app().getJobQueue(); + { + // Test repeated post()s until the Coro completes. + std::atomic yieldCount {0}; + auto const coro = jQueue.postCoro (jtCLIENT, "PostCoroTest1", + [&yieldCount] (std::shared_ptr const& coroCopy) + { + while (++yieldCount < 4) + coroCopy->yield(); + }); + BEAST_EXPECT (coro != nullptr); + + // Wait for the Job to run and yield. + while (yieldCount == 0); + + // Now re-post until the Coro says it is done. + int old = yieldCount; + while (coro->runnable()) + { + BEAST_EXPECT (coro->post()); + while (old == yieldCount) { } + coro->join(); + BEAST_EXPECT (++old == yieldCount); + } + BEAST_EXPECT (yieldCount == 4); + } + { + // Test repeated resume()s until the Coro completes. + int yieldCount {0}; + auto const coro = jQueue.postCoro (jtCLIENT, "PostCoroTest2", + [&yieldCount] (std::shared_ptr const& coroCopy) + { + while (++yieldCount < 4) + coroCopy->yield(); + }); + if (! coro) + { + // There's no good reason we should not get a Coro, but we + // can't continue without one. + BEAST_EXPECT (false); + return; + } + + // Wait for the Job to run and yield. + coro->join(); + + // Now resume until the Coro says it is done. + int old = yieldCount; + while (coro->runnable()) + { + coro->resume(); // Resume runs synchronously on this thread. + BEAST_EXPECT (++old == yieldCount); + } + BEAST_EXPECT (yieldCount == 4); + } + { + // If the JobQueue's JobCounter is join()ed we should no + // longer be able to add a Coro (and calling postCoro() should + // return false). + using namespace std::chrono_literals; + beast::Journal j {env.app().journal ("JobQueue_test")}; + JobCounter& jCounter = jQueue.jobCounter(); + jCounter.join("JobQueue_test", 1s, j); + + // The Coro should never run, so having the Coro access this + // unprotected variable on the stack should be completely safe. + // Not recommended for the faint of heart... + bool unprotected; + auto const coro = jQueue.postCoro (jtCLIENT, "PostCoroTest3", + [&unprotected] (std::shared_ptr const&) + { unprotected = false; }); + BEAST_EXPECT (coro == nullptr); + } + } + +public: + void run() + { + testAddJob(); + testPostCoro(); + } +}; + +BEAST_DEFINE_TESTSUITE(JobQueue, core, ripple); + +} // test +} // ripple diff --git a/src/test/csf/Ledger.h b/src/test/csf/Ledger.h index 1787ce33c4..addc064273 100644 --- a/src/test/csf/Ledger.h +++ b/src/test/csf/Ledger.h @@ -20,6 +20,7 @@ #define RIPPLE_TEST_CSF_LEDGER_H_INCLUDED #include +#include #include namespace ripple { diff --git a/src/test/csf/Peer.h b/src/test/csf/Peer.h index f713f487b1..41e5aae9dd 100644 --- a/src/test/csf/Peer.h +++ b/src/test/csf/Peer.h @@ -21,7 +21,8 @@ #include #include - +#include +#include #include #include #include @@ -115,20 +116,43 @@ public: directly from the generic types. */ using Proposal = ConsensusProposal; +class PeerPosition +{ +public: + PeerPosition(Proposal const & p) + : proposal_(p) + { + } -struct Traits + Proposal const& + proposal() const + { + return proposal_; + } + + Json::Value + getJson() const + { + return proposal_.getJson(); + } + +private: + Proposal proposal_; +}; + + +/** Represents a single node participating in the consensus process. + It implements the Adaptor requirements of generic Consensus. +*/ +struct Peer { using Ledger_t = Ledger; using NodeID_t = PeerID; using TxSet_t = TxSet; -}; + using PeerPosition_t = PeerPosition; + using Result = ConsensusResult; -/** Represents a single node participating in the consensus process. - It implements the Callbacks required by Consensus. -*/ -struct Peer : public Consensus -{ - using Base = Consensus; + Consensus consensus; //! Our unique ID PeerID id; @@ -172,12 +196,17 @@ struct Peer : public Consensus bool validating_ = true; bool proposing_ = true; + ConsensusParms parms_; + std::size_t prevProposers_ = 0; + std::chrono::milliseconds prevRoundTime_; + //! All peers start from the default constructed ledger - Peer(PeerID i, BasicNetwork& n, UNL const& u) - : Consensus(n.clock(), beast::Journal{}) + Peer(PeerID i, BasicNetwork& n, UNL const& u, ConsensusParms p) + : consensus(n.clock(), *this, beast::Journal{}) , id{i} , net{n} , unl(u) + , parms_(p) { ledgers[lastClosedLedger.id()] = lastClosedLedger; } @@ -210,12 +239,6 @@ struct Peer : public Consensus return nullptr; } - auto const& - proposals(Ledger::ID const& ledgerHash) - { - return peerPositions_[ledgerHash]; - } - TxSet const* acquireTxSet(TxSet::ID const& setId) { @@ -245,17 +268,17 @@ struct Peer : public Consensus } Result - onClose(Ledger const& prevLedger, NetClock::time_point closeTime, Mode mode) + onClose(Ledger const& prevLedger, NetClock::time_point closeTime, ConsensusMode mode) { TxSet res{openTxs}; - return Result{TxSet{openTxs}, - Proposal{prevLedger.id(), + return Result(TxSet{openTxs}, + Proposal(prevLedger.id(), Proposal::seqJoin, res.id(), closeTime, now(), - id}}; + id)); } void @@ -263,10 +286,17 @@ struct Peer : public Consensus Result const& result, Ledger const& prevLedger, NetClock::duration const& closeResolution, - CloseTimes const& rawCloseTimes, - Mode const& mode) + ConsensusCloseTimes const& rawCloseTimes, + ConsensusMode const& mode, + Json::Value && consensusJson) { - onAccept(result, prevLedger, closeResolution, rawCloseTimes, mode); + onAccept( + result, + prevLedger, + closeResolution, + rawCloseTimes, + mode, + std::move(consensusJson)); } void @@ -274,8 +304,9 @@ struct Peer : public Consensus Result const& result, Ledger const& prevLedger, NetClock::duration const& closeResolution, - CloseTimes const& rawCloseTimes, - Mode const& mode) + ConsensusCloseTimes const& rawCloseTimes, + ConsensusMode const& mode, + Json::Value && consensusJson) { auto newLedger = prevLedger.close( result.set.txs_, @@ -283,7 +314,8 @@ struct Peer : public Consensus rawCloseTimes.self, result.position.closeTime() != NetClock::time_point{}); ledgers[newLedger.id()] = newLedger; - + prevProposers_ = result.proposers; + prevRoundTime_ = result.roundTime.read(); lastClosedLedger = newLedger; auto it = @@ -304,16 +336,16 @@ struct Peer : public Consensus // TODO: reconsider this and instead just save LCL generated here? if (completedLedgers <= targetLedgers) { - startRound( + consensus.startRound( now(), lastClosedLedger.id(), lastClosedLedger, proposing_); } } Ledger::ID - getPrevLedger(Ledger::ID const& ledgerID, Ledger const& ledger, Mode mode) + getPrevLedger(Ledger::ID const& ledgerID, Ledger const& ledger, ConsensusMode mode) { // TODO: Use generic validation code - if (mode != Mode::wrongLedger && ledgerID.seq > 0 && + if (mode != ConsensusMode::wrongLedger && ledgerID.seq > 0 && ledger.id().seq > 0) return peerValidations.getBestLCL(ledgerID, ledger.parentID()); return ledgerID; @@ -323,24 +355,31 @@ struct Peer : public Consensus propose(Proposal const& pos) { if (proposing_) - relay(pos); + relay(PeerPosition(pos)); + } + + ConsensusParms const & + parms() const + { + return parms_; } //------------------------------------------------------------------------- // non-callback helpers void - receive(Proposal const& p) + receive(PeerPosition const& peerPos) { + Proposal const & p = peerPos.proposal(); if (unl.find(p.nodeID()) == unl.end()) return; - // TODO: Be sure this is a new proposal!!!!! + // TODO: Supress repeats more efficiently auto& dest = peerPositions_[p.prevLedger()]; if (std::find(dest.begin(), dest.end(), p) != dest.end()) return; dest.push_back(p); - peerProposal(now(), p); + consensus.peerProposal(now(), peerPos); } void @@ -349,7 +388,7 @@ struct Peer : public Consensus // save and map complete? auto it = txSets.insert(std::make_pair(txs.id(), txs)); if (it.second) - gotTxSet(now(), txs); + consensus.gotTxSet(now(), txs); } void @@ -392,21 +431,21 @@ struct Peer : public Consensus void timerEntry() { - Base::timerEntry(now()); + consensus.timerEntry(now()); // only reschedule if not completed if (completedLedgers < targetLedgers) - net.timer(LEDGER_GRANULARITY, [&]() { timerEntry(); }); + net.timer(parms().ledgerGRANULARITY, [&]() { timerEntry(); }); } void start() { - net.timer(LEDGER_GRANULARITY, [&]() { timerEntry(); }); + net.timer(parms().ledgerGRANULARITY, [&]() { timerEntry(); }); // The ID is the one we have seen the most validations for // In practice, we might not actually have that ledger itself yet, // so there is no gaurantee that bestLCL == lastClosedLedger.id() auto bestLCL = peerValidations.getBestLCL( lastClosedLedger.id(), lastClosedLedger.parentID()); - startRound(now(), bestLCL, lastClosedLedger, proposing_); + consensus.startRound(now(), bestLCL, lastClosedLedger, proposing_); } NetClock::time_point @@ -415,8 +454,9 @@ struct Peer : public Consensus // We don't care about the actual epochs, but do want the // generated NetClock time to be well past its epoch to ensure // any subtractions of two NetClock::time_point in the consensu - // code are positive. (e.g. PROPOSE_FRESHNESS) + // code are positive. (e.g. proposeFRESHNESS) using namespace std::chrono; + using namespace std::chrono_literals; return NetClock::time_point(duration_cast( net.now().time_since_epoch() + 86400s + clockSkew)); } @@ -427,11 +467,35 @@ struct Peer : public Consensus void schedule(std::chrono::nanoseconds when, T&& what) { + using namespace std::chrono_literals; + if (when == 0ns) what(); else net.timer(when, std::forward(what)); } + + Ledger::ID + prevLedgerID() + { + return consensus.prevLedgerID(); + } + + std::size_t + prevProposers() + { + return prevProposers_; + } + + std::chrono::milliseconds + prevRoundTime() + { + return prevRoundTime_; + } + + // Not interested in tracking consensus mode + void + onModeChange(ConsensusMode, ConsensusMode) {} }; } // csf diff --git a/src/test/csf/Sim.h b/src/test/csf/Sim.h index 4515deddf7..c88ef5e7c0 100644 --- a/src/test/csf/Sim.h +++ b/src/test/csf/Sim.h @@ -47,14 +47,15 @@ public: @param g The trust graph between peers. @param top The network topology between peers. + @param parms Consensus parameters to use in the simulation */ template - Sim(TrustGraph const& g, Topology const& top) + Sim(ConsensusParms parms, TrustGraph const& g, Topology const& top) { peers.reserve(g.numPeers()); for (int i = 0; i < g.numPeers(); ++i) - peers.emplace_back(i, net, g.unl(i)); + peers.emplace_back(i, net, g.unl(i), parms); for (int i = 0; i < peers.size(); ++i) { diff --git a/src/test/jtx/Env.h b/src/test/jtx/Env.h index 3cea99248a..f3f0c3fd1b 100644 --- a/src/test/jtx/Env.h +++ b/src/test/jtx/Env.h @@ -44,7 +44,7 @@ #include #include #include -#include +#include #include #include #include @@ -56,6 +56,13 @@ namespace ripple { + +namespace detail { +extern +std::vector +supportedAmendments (); +} + namespace test { namespace jtx { @@ -67,30 +74,137 @@ noripple (Account const& account, Args const&... args) return {{account, args...}}; } -/** Activate features in the Env ctor */ +/** + * @brief create collection of features to pass to Env ctor + * + * The resulting collection will contain *only* the features + * passed as arguments. + * + * @param key+args features to include in resulting collection + */ template -std::array -features (uint256 const& key, Args const&... args) +FeatureBitset +with_features (uint256 const& key, Args const&... args) { - return {{key, args...}}; + return makeFeatureBitset( + std::array{{key, args...}}); } -/** Activate features in the Env ctor */ -inline -auto -features (std::initializer_list keys) +/** + * @brief create collection of features to pass to Env ctor + * + * The resulting collection will contain *only* the features + * passed as arguments. + * + * @param keys features to include in resulting collection + */ +template +FeatureBitset +with_features (Col&& keys) { - return keys; + return makeFeatureBitset(std::forward(keys)); } -/** Activate features in the Env ctor */ +constexpr FeatureBitset no_features = {}; + inline -auto -features (std::vector keys) +FeatureBitset +all_amendments() { - return keys; + static const FeatureBitset ids = []{ + auto const& sa = ripple::detail::supportedAmendments(); + std::vector feats; + feats.reserve(sa.size()); + for (auto const& s : sa) + { + if (auto const f = getRegisteredFeature(s.substr(65))) + feats.push_back(*f); + else + Throw ("Unknown feature: " + s + " in supportedAmendments."); + } + return makeFeatureBitset(feats); + }(); + return ids; } +/** + * @brief create collection of features to pass to Env ctor + * + * The resulting collection will contain *all supported amendments* minus + * the features passed as arguments. + * + * @param keys features to exclude from the resulting collection + */ +template +FeatureBitset +all_features_except (Col const& keys) +{ + return all_amendments() & ~makeFeatureBitset(keys); +} + +/** + * + * @brief create collection of features to pass to Env ctor + * The resulting collection will contain *all supported amendments* minus + * the features passed as arguments. + * + * @param key+args features to exclude from the resulting collection + */ +template +FeatureBitset +all_features_except (uint256 const& key, Args const&... args) +{ + return all_features_except( + std::array{{key, args...}}); +} + +class SuiteSink : public beast::Journal::Sink +{ + std::string partition_; + beast::unit_test::suite& suite_; + +public: + SuiteSink(std::string const& partition, + beast::severities::Severity threshold, + beast::unit_test::suite& suite) + : Sink (threshold, false) + , partition_(partition + " ") + , suite_ (suite) + { + } + + // For unit testing, always generate logging text. + inline bool active(beast::severities::Severity level) const override + { + return true; + } + + void + write(beast::severities::Severity level, std::string const& text) override; +}; + +class SuiteLogs : public Logs +{ + beast::unit_test::suite& suite_; + +public: + explicit + SuiteLogs(beast::unit_test::suite& suite) + : Logs (beast::severities::kError) + , suite_(suite) + { + } + + ~SuiteLogs() override = default; + + std::unique_ptr + makeSink(std::string const& partition, + beast::severities::Severity threshold) override + { + return std::make_unique(partition, threshold, suite_); + } +}; + //------------------------------------------------------------------------------ /** A transaction testing environment. */ @@ -114,76 +228,98 @@ private: std::unique_ptr client; AppBundle (beast::unit_test::suite& suite, - std::unique_ptr config); + std::unique_ptr config, + std::unique_ptr logs); ~AppBundle(); }; AppBundle bundle_; - inline - void - construct() - { - } - - template - void - construct (Arg&& arg, Args&&... args) - { - construct_arg(std::forward(arg)); - construct(std::forward(args)...); - } - - template - void - construct_arg ( - std::array const& list) - { - for(auto const& key : list) - app().config().features.insert(key); - } - - void - construct_arg ( - std::initializer_list list) - { - for(auto const& key : list) - app().config().features.insert(key); - } - - void - construct_arg ( - std::vector const& list) - { - for(auto const& key : list) - app().config().features.insert(key); - } - public: Env() = delete; - Env (Env const&) = delete; Env& operator= (Env const&) = delete; + Env (Env const&) = delete; + /** + * @brief Create Env using suite, Config pointer, and explicit features. + * + * This constructor will create an Env with the specified configuration + * and takes ownership the passed Config pointer. Features will be enabled + * according to rules described below (see next constructor). + * + * @param suite_ the current unit_test::suite + * @param config The desired Config - ownership will be taken by moving + * the pointer. See envconfig and related functions for common config tweaks. + * @param args with_features() to explicitly enable or all_features_except() to + * enable all and disable specific features + */ // VFALCO Could wrap the suite::log in a Journal here - template Env (beast::unit_test::suite& suite_, - std::unique_ptr config, - Args&&... args) + std::unique_ptr config, + FeatureBitset features, + std::unique_ptr logs = nullptr) : test (suite_) - , bundle_ (suite_, std::move(config)) + , bundle_ ( + suite_, + std::move(config), + logs ? std::move(logs) : std::make_unique(suite_)) { memoize(Account::master); Pathfinder::initPathTable(); - // enable the the invariant enforcement amendment by default. - construct( - features(featureEnforceInvariants), - std::forward(args)...); + foreachFeature( + features, [& appFeats = app().config().features](uint256 const& f) { + appFeats.insert(f); + }); } - template + /** + * @brief Create Env with default config and specified + * features. + * + * This constructor will create an Env with the standard Env configuration + * (from envconfig()) and features explicitly specified. Use with_features(...) + * or all_features_except(...) to create a collection of features appropriate + * for passing here. + * + * @param suite_ the current unit_test::suite + * @param args collection of features + * + */ Env (beast::unit_test::suite& suite_, - Args&&... args) - : Env(suite_, envconfig(), std::forward(args)...) + FeatureBitset features) + : Env(suite_, envconfig(), features) + { + } + + /** + * @brief Create Env using suite and Config pointer. + * + * This constructor will create an Env with the specified configuration + * and takes ownership the passed Config pointer. All supported amendments + * are enabled by this version of the constructor. + * + * @param suite_ the current unit_test::suite + * @param config The desired Config - ownership will be taken by moving + * the pointer. See envconfig and related functions for common config tweaks. + */ + Env (beast::unit_test::suite& suite_, + std::unique_ptr config, + std::unique_ptr logs = nullptr) + : Env(suite_, std::move(config), all_amendments(), std::move(logs)) + { + } + + /** + * @brief Create Env with only the current test suite + * + * This constructor will create an Env with the standard + * test Env configuration (from envconfig()) and all supported + * amendments enabled. + * + * @param suite_ the current unit_test::suite + */ + Env (beast::unit_test::suite& suite_) + : Env(suite_, envconfig()) { } @@ -488,6 +624,9 @@ public: std::shared_ptr tx() const; + void + enableFeature(uint256 const feature); + private: void fund (bool setDefaultRipple, @@ -642,7 +781,7 @@ protected: FN const&... fN) { maybe_invoke(stx, f, - beast::detail::is_call_possible()); invoke(stx, fN...); } @@ -676,7 +815,7 @@ protected: FN const&... fN) { maybe_invoke(jt, f, - beast::detail::is_call_possible()); invoke(jt, fN...); } diff --git a/src/test/jtx/Env_test.cpp b/src/test/jtx/Env_test.cpp index 18a4419cf8..407d8d110d 100644 --- a/src/test/jtx/Env_test.cpp +++ b/src/test/jtx/Env_test.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include namespace ripple { @@ -354,7 +355,7 @@ public: { using namespace jtx; - Env env(*this, features(featureMultiSign)); + Env env(*this, with_features(featureMultiSign)); env.fund(XRP(10000), "alice"); env(signers("alice", 1, { { "alice", 1 }, { "bob", 2 } }), ter(temBAD_SIGNER)); @@ -383,7 +384,7 @@ public: ticket::create("alice", 60, "bob"); { - Env env(*this, features(featureTickets)); + Env env(*this, with_features(featureTickets)); env.fund(XRP(10000), "alice"); env(noop("alice"), require(owners("alice", 0), tickets("alice", 0))); env(ticket::create("alice"), require(owners("alice", 1), tickets("alice", 1))); @@ -627,6 +628,129 @@ public: } } + void testFeatures() + { + testcase("Env features"); + using namespace jtx; + auto const supported = all_amendments(); + + // this finds a feature that is not in + // the supported amendments list and tests that it can be + // enabled explicitly + + auto const neverSupportedFeat = [&]() -> boost::optional + { + auto const n = supported.size(); + for(size_t i = 0; i < n; ++i) + if (!supported[i]) + return bitsetIndexToFeature(i); + + return boost::none; + }(); + + if (!neverSupportedFeat) + { + log << "No unsupported features found - skipping test." << std::endl; + pass(); + return; + } + + auto const neverSupported = with_features(*neverSupportedFeat); + + auto hasFeature = [](Env& env, uint256 const& f) + { + return (env.app().config().features.find (f) != + env.app().config().features.end()); + }; + + { + // default Env has all supported features + Env env{*this}; + BEAST_EXPECT( + supported.count() == env.app().config().features.size()); + foreachFeature(supported, [&](uint256 const& f) { + this->BEAST_EXPECT(hasFeature(env, f)); + }); + } + + { + // a Env with_features has *only* those features + Env env{*this, with_features(featureEscrow, featureTickets)}; + BEAST_EXPECT(env.app().config().features.size() == 2); + foreachFeature(supported, [&](uint256 const& f) { + bool const has = (f == featureEscrow || f == featureTickets); + this->BEAST_EXPECT(has == hasFeature(env, f)); + }); + } + + { + // a Env all_features_except is missing *only* those features + Env env{*this, all_features_except(featureEscrow, featureTickets)}; + BEAST_EXPECT( + env.app().config().features.size() == (supported.count() - 2)); + foreachFeature(supported, [&](uint256 const& f) { + bool hasnot = (f == featureEscrow || f == featureTickets); + this->BEAST_EXPECT(hasnot != hasFeature(env, f)); + }); + } + + { + // add a feature that is NOT in the supported amendments list + // along with a list of explicit amendments + // the unsupported feature should be enabled along with + // the two supported ones + Env env{ + *this, + with_features(featureEscrow, featureTickets) | neverSupported}; + + // this app will have just 2 supported amendments and + // one additional never supported feature flag + BEAST_EXPECT(env.app().config().features.size() == (2 + 1)); + BEAST_EXPECT(hasFeature(env, *neverSupportedFeat)); + + foreachFeature(supported, [&](uint256 const& f) { + bool has = (f == featureEscrow || f == featureTickets); + this->BEAST_EXPECT(has == hasFeature(env, f)); + }); + } + + { + // add a feature that is NOT in the supported amendments list + // and omit a few standard amendments + // the unsupported features should be enabled + Env env{*this, + all_features_except(featureEscrow, featureTickets) | + neverSupported}; + + // this app will have all supported amendments minus 2 and then the + // one additional never supported feature flag + BEAST_EXPECT( + env.app().config().features.size() == + (supported.count() - 2 + 1)); + BEAST_EXPECT(hasFeature(env, *neverSupportedFeat)); + foreachFeature(supported, [&](uint256 const& f) { + bool hasnot = (f == featureEscrow || f == featureTickets); + this->BEAST_EXPECT(hasnot != hasFeature(env, f)); + }); + } + + { + // add a feature that is NOT in the supported amendments list + // along with all supported amendments + // the unsupported features should be enabled + Env env{*this, all_amendments() | neverSupported}; + + // this app will have all supported amendments and then the + // one additional never supported feature flag + BEAST_EXPECT( + env.app().config().features.size() == (supported.count() + 1)); + BEAST_EXPECT(hasFeature(env, *neverSupportedFeat)); + foreachFeature(supported, [&](uint256 const& f) { + this->BEAST_EXPECT(hasFeature(env, f)); + }); + } + } + void run() { @@ -648,6 +772,7 @@ public: testPath(); testResignSigned(); testSignAndSubmit(); + testFeatures(); } }; diff --git a/src/test/jtx/impl/Env.cpp b/src/test/jtx/impl/Env.cpp index 4a7564e620..753d7046bc 100644 --- a/src/test/jtx/impl/Env.cpp +++ b/src/test/jtx/impl/Env.cpp @@ -55,81 +55,36 @@ namespace ripple { namespace test { namespace jtx { -class SuiteSink : public beast::Journal::Sink +void +SuiteSink::write(beast::severities::Severity level, std::string const& text) { - std::string partition_; - beast::unit_test::suite& suite_; - -public: - SuiteSink(std::string const& partition, - beast::severities::Severity threshold, - beast::unit_test::suite& suite) - : Sink (threshold, false) - , partition_(partition + " ") - , suite_ (suite) + using namespace beast::severities; + std::string s; + switch(level) { + case kTrace: s = "TRC:"; break; + case kDebug: s = "DBG:"; break; + case kInfo: s = "INF:"; break; + case kWarning: s = "WRN:"; break; + case kError: s = "ERR:"; break; + default: + case kFatal: s = "FTL:"; break; } - // For unit testing, always generate logging text. - bool active(beast::severities::Severity level) const override - { - return true; - } - - void - write(beast::severities::Severity level, - std::string const& text) override - { - using namespace beast::severities; - std::string s; - switch(level) - { - case kTrace: s = "TRC:"; break; - case kDebug: s = "DBG:"; break; - case kInfo: s = "INF:"; break; - case kWarning: s = "WRN:"; break; - case kError: s = "ERR:"; break; - default: - case kFatal: s = "FTL:"; break; - } - - // Only write the string if the level at least equals the threshold. - if (level >= threshold()) - suite_.log << s << partition_ << text << std::endl; - } -}; - -class SuiteLogs : public Logs -{ - beast::unit_test::suite& suite_; - -public: - explicit - SuiteLogs(beast::unit_test::suite& suite) - : Logs (beast::severities::kError) - , suite_(suite) - { - } - - ~SuiteLogs() override = default; - - std::unique_ptr - makeSink(std::string const& partition, - beast::severities::Severity threshold) override - { - return std::make_unique(partition, threshold, suite_); - } -}; + // Only write the string if the level at least equals the threshold. + if (level >= threshold()) + suite_.log << s << partition_ << text << std::endl; +} //------------------------------------------------------------------------------ Env::AppBundle::AppBundle(beast::unit_test::suite& suite, - std::unique_ptr config) + std::unique_ptr config, + std::unique_ptr logs) { using namespace beast::severities; // Use kFatal threshold to reduce noise from STObject. setDebugLogSink (std::make_unique("Debug", kFatal, suite)); - auto logs = std::make_unique(suite); auto timeKeeper_ = std::make_unique(); timeKeeper = timeKeeper_.get(); @@ -537,6 +492,14 @@ Env::do_rpc(std::vector const& args) return response; } +void +Env::enableFeature(uint256 const feature) +{ + // Env::close() must be called for feature + // enable to take place. + app().config().features.insert(feature); +} + } // jtx } // test diff --git a/src/test/jtx/impl/JSONRPCClient.cpp b/src/test/jtx/impl/JSONRPCClient.cpp index 72ad45e4d5..a09fa7af42 100644 --- a/src/test/jtx/impl/JSONRPCClient.cpp +++ b/src/test/jtx/impl/JSONRPCClient.cpp @@ -23,7 +23,7 @@ #include #include #include -#include +#include #include #include #include @@ -74,8 +74,8 @@ class JSONRPCClient : public AbstractClient boost::asio::ip::tcp::endpoint ep_; boost::asio::io_service ios_; boost::asio::ip::tcp::socket stream_; - beast::streambuf bin_; - beast::streambuf bout_; + beast::multi_buffer bin_; + beast::multi_buffer bout_; unsigned rpc_version_; public: @@ -109,12 +109,11 @@ public: using namespace std::string_literals; request req; - req.method = "POST"; - req.url = "/"; + req.method(beast::http::verb::post); + req.target("/"); req.version = 11; - req.fields.insert("Content-Type", "application/json; charset=UTF-8"); - req.fields.insert("Host", - ep_.address().to_string() + ":" + std::to_string(ep_.port())); + req.insert("Content-Type", "application/json; charset=UTF-8"); + req.insert("Host", ep_); { Json::Value jr; jr[jss::method] = cmd; @@ -131,10 +130,10 @@ public: } req.body = to_string(jr); } - prepare(req); + req.prepare_payload(); write(stream_, req); - response res; + response res; read(stream_, bin_, res); Json::Reader jr; diff --git a/src/test/jtx/impl/WSClient.cpp b/src/test/jtx/impl/WSClient.cpp index ce3d566998..9f5de744b6 100644 --- a/src/test/jtx/impl/WSClient.cpp +++ b/src/test/jtx/impl/WSClient.cpp @@ -24,8 +24,7 @@ #include #include #include -#include -#include +#include #include #include @@ -95,8 +94,7 @@ class WSClientImpl : public WSClient std::thread thread_; boost::asio::ip::tcp::socket stream_; beast::websocket::stream ws_; - beast::websocket::opcode op_; - beast::streambuf rb_; + beast::multi_buffer rb_; bool peerClosed_ = false; @@ -139,9 +137,9 @@ public: stream_.connect(ep); ws_.handshake(ep.address().to_string() + ":" + std::to_string(ep.port()), "/"); - ws_.async_read(op_, rb_, + ws_.async_read(rb_, strand_.wrap(std::bind(&WSClientImpl::on_read_msg, - this, beast::asio::placeholders::error))); + this, std::placeholders::_1))); } catch(std::exception&) { @@ -278,9 +276,9 @@ private: msgs_.push_front(m); cv_.notify_all(); } - ws_.async_read(op_, rb_, strand_.wrap( + ws_.async_read(rb_, strand_.wrap( std::bind(&WSClientImpl::on_read_msg, - this, beast::asio::placeholders::error))); + this, std::placeholders::_1))); } // Called when the read op terminates diff --git a/src/test/ledger/BookDirs_test.cpp b/src/test/ledger/BookDirs_test.cpp index 1418641e7a..933f24d742 100644 --- a/src/test/ledger/BookDirs_test.cpp +++ b/src/test/ledger/BookDirs_test.cpp @@ -28,7 +28,7 @@ struct BookDirs_test : public beast::unit_test::suite void test_bookdir(std::initializer_list fs) { using namespace jtx; - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); auto gw = Account("gw"); auto USD = gw["USD"]; env.fund(XRP(1000000), "alice", "bob", "gw"); diff --git a/src/test/ledger/Directory_test.cpp b/src/test/ledger/Directory_test.cpp index b4045c5e19..0261c5de06 100644 --- a/src/test/ledger/Directory_test.cpp +++ b/src/test/ledger/Directory_test.cpp @@ -15,69 +15,431 @@ */ //============================================================================== -#include -#include +#include +#include #include +#include +#include +#include +#include +#include +#include namespace ripple { namespace test { struct Directory_test : public beast::unit_test::suite { - void testDirectory() + // Map [0-15576] into a a unique 3 letter currency code + std::string + currcode (std::size_t i) + { + // There are only 17576 possible combinations + BEAST_EXPECT (i < 17577); + + std::string code; + + for (int j = 0; j != 3; ++j) + { + code.push_back ('A' + (i % 26)); + i /= 26; + } + + return code; + } + + // Insert n empty pages, numbered [0, ... n - 1], in the + // specified directory: + void + makePages( + Sandbox& sb, + uint256 const& base, + std::uint64_t n) + { + for (std::uint64_t i = 0; i < n; ++i) + { + auto p = std::make_shared(keylet::page(base, i)); + + p->setFieldV256 (sfIndexes, STVector256{}); + + if (i + 1 == n) + p->setFieldU64 (sfIndexNext, 0); + else + p->setFieldU64 (sfIndexNext, i + 1); + + if (i == 0) + p->setFieldU64 (sfIndexPrevious, n - 1); + else + p->setFieldU64 (sfIndexPrevious, i - 1); + + sb.insert (p); + } + } + + void testDirectoryOrdering() { using namespace jtx; - Env env(*this); + auto gw = Account("gw"); auto USD = gw["USD"]; + auto alice = Account("alice"); + auto bob = Account("bob"); { - auto dir = Dir(*env.current(), - keylet::ownerDir(Account("alice"))); - BEAST_EXPECT(std::begin(dir) == std::end(dir)); - BEAST_EXPECT(std::end(dir) == dir.find(uint256(), uint256())); + testcase ("Directory Ordering (without 'SortedDirectories' amendment"); + + Env env(*this, all_features_except(featureSortedDirectories)); + env.fund(XRP(10000000), alice, bob, gw); + + // Insert 400 offers from Alice, then one from Bob: + for (std::size_t i = 1; i <= 400; ++i) + env(offer(alice, USD(10), XRP(10))); + + // Check Alice's directory: it should contain one + // entry for each offer she added. Within each + // page, the entries should be in sorted order. + { + auto dir = Dir(*env.current(), + keylet::ownerDir(alice)); + + std::uint32_t lastSeq = 1; + + // Check that the orders are sequential by checking + // that their sequence numbers are: + for (auto iter = dir.begin(); iter != std::end(dir); ++iter) { + BEAST_EXPECT(++lastSeq == (*iter)->getFieldU32(sfSequence)); + } + BEAST_EXPECT(lastSeq != 1); + } } - env.fund(XRP(10000), "alice", "bob", gw); - - auto i = 10; - for (; i <= 400; i += 10) - env(offer("alice", USD(i), XRP(10))); - env(offer("bob", USD(500), XRP(10))); - { - auto dir = Dir(*env.current(), - keylet::ownerDir(Account("bob"))); - BEAST_EXPECT(std::begin(dir)->get()-> - getFieldAmount(sfTakerPays) == USD(500)); + testcase ("Directory Ordering (with 'SortedDirectories' amendment)"); + + Env env(*this, with_features(featureSortedDirectories)); + env.fund(XRP(10000000), alice, gw); + + for (std::size_t i = 1; i <= 400; ++i) + env(offer(alice, USD(i), XRP(i))); + env.close(); + + // Check Alice's directory: it should contain one + // entry for each offer she added, and, within each + // page the entries should be in sorted order. + { + auto const view = env.closed(); + + std::uint64_t page = 0; + + do + { + auto p = view->read(keylet::page(keylet::ownerDir(alice), page)); + + // Ensure that the entries in the page are sorted + auto const& v = p->getFieldV256(sfIndexes); + BEAST_EXPECT (std::is_sorted(v.begin(), v.end())); + + // Ensure that the page contains the correct orders by + // calculating which sequence numbers belong here. + std::uint32_t minSeq = 2 + (page * dirNodeMaxEntries); + std::uint32_t maxSeq = minSeq + dirNodeMaxEntries; + + for (auto const& e : v) + { + auto c = view->read(keylet::child(e)); + BEAST_EXPECT(c); + BEAST_EXPECT(c->getFieldU32(sfSequence) >= minSeq); + BEAST_EXPECT(c->getFieldU32(sfSequence) < maxSeq); + } + + page = p->getFieldU64(sfIndexNext); + } while (page != 0); + } + + // Now check the orderbook: it should be in the order we placed + // the offers. + auto book = BookDirs(*env.current(), + Book({xrpIssue(), USD.issue()})); + int count = 1; + + for (auto const& offer : book) + { + count++; + BEAST_EXPECT(offer->getFieldAmount(sfTakerPays) == USD(count)); + BEAST_EXPECT(offer->getFieldAmount(sfTakerGets) == XRP(count)); + } + } + } + + void + testDirIsEmpty() + { + testcase ("dirIsEmpty"); + + using namespace jtx; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const charlie = Account ("charlie"); + auto const gw = Account ("gw"); + + beast::xor_shift_engine eng; + + Env env(*this, with_features(featureSortedDirectories, featureMultiSign)); + + env.fund(XRP(1000000), alice, charlie, gw); + env.close(); + + // alice should have an empty directory. + BEAST_EXPECT(dirIsEmpty (*env.closed(), keylet::ownerDir(alice))); + + // Give alice a signer list, then there will be stuff in the directory. + env(signers(alice, 1, { { bob, 1} })); + env.close(); + BEAST_EXPECT(! dirIsEmpty (*env.closed(), keylet::ownerDir(alice))); + + env(signers(alice, jtx::none)); + env.close(); + BEAST_EXPECT(dirIsEmpty (*env.closed(), keylet::ownerDir(alice))); + + std::vector const currencies = [this, &gw]() + { + std::vector c; + + c.reserve((2 * dirNodeMaxEntries) + 3); + + while (c.size() != c.capacity()) + c.push_back(gw[currcode(c.size())]); + + return c; + }(); + + // First, Alices creates a lot of trustlines, and then + // deletes them in a different order: + { + auto cl = currencies; + + for (auto const& c : cl) + { + env(trust(alice, c(50))); + env.close(); + } + + BEAST_EXPECT(! dirIsEmpty (*env.closed(), keylet::ownerDir(alice))); + + std::shuffle (cl.begin(), cl.end(), eng); + + for (auto const& c : cl) + { + env(trust(alice, c(0))); + env.close(); + } + + BEAST_EXPECT(dirIsEmpty (*env.closed(), keylet::ownerDir(alice))); } - auto dir = Dir(*env.current(), - keylet::ownerDir(Account("alice"))); - i = 0; - for (auto const& e : dir) - BEAST_EXPECT(e->getFieldAmount(sfTakerPays) == USD(i += 10)); + // Now, Alice creates offers to buy currency, creating + // implicit trust lines. + { + auto cl = currencies; - BEAST_EXPECT(std::begin(dir) != std::end(dir)); - BEAST_EXPECT(std::end(dir) == - dir.find(std::begin(dir).page().key, - uint256())); - BEAST_EXPECT(std::begin(dir) == - dir.find(std::begin(dir).page().key, - std::begin(dir).index())); - auto entry = std::next(std::begin(dir), 32); - auto it = dir.find(entry.page().key, entry.index()); - BEAST_EXPECT(it != std::end(dir)); - BEAST_EXPECT((*it)->getFieldAmount(sfTakerPays) == USD(330)); + BEAST_EXPECT(dirIsEmpty (*env.closed(), keylet::ownerDir(alice))); + + for (auto c : currencies) + { + env(trust(charlie, c(50))); + env.close(); + env(pay(gw, charlie, c(50))); + env.close(); + env(offer(alice, c(50), XRP(50))); + env.close(); + } + + BEAST_EXPECT(! dirIsEmpty (*env.closed(), keylet::ownerDir(alice))); + + // Now fill the offers in a random order. Offer + // entries will drop, and be replaced by trust + // lines that are implicitly created. + std::shuffle (cl.begin(), cl.end(), eng); + + for (auto const& c : cl) + { + env(offer(charlie, XRP(50), c(50))); + env.close(); + } + BEAST_EXPECT(! dirIsEmpty (*env.closed(), keylet::ownerDir(alice))); + // Finally, Alice now sends the funds back to + // Charlie. The implicitly created trust lines + // should drop away: + std::shuffle (cl.begin(), cl.end(), eng); + + for (auto const& c : cl) + { + env(pay(alice, charlie, c(50))); + env.close(); + } + + BEAST_EXPECT(dirIsEmpty (*env.closed(), keylet::ownerDir(alice))); + } + } + + void + testRipd1353() + { + testcase("RIPD-1353 Empty Offer Directories"); + + using namespace jtx; + Env env(*this, with_features(featureSortedDirectories)); + + auto const gw = Account{"gateway"}; + auto const alice = Account{"alice"}; + auto const USD = gw["USD"]; + + env.fund(XRP(10000), alice, gw); + env.trust(USD(1000), alice); + env(pay(gw, alice, USD(1000))); + + auto const firstOfferSeq = env.seq(alice); + + // Fill up three pages of offers + for (int i = 0; i < 3; ++i) + for (int j = 0; j < dirNodeMaxEntries; ++j) + env(offer(alice, XRP(1), USD(1))); + env.close(); + + // remove all the offers. Remove the middle page last + for (auto page : {0, 2, 1}) + { + for (int i = 0; i < dirNodeMaxEntries; ++i) + { + Json::Value cancelOffer; + cancelOffer[jss::Account] = alice.human(); + cancelOffer[jss::OfferSequence] = + Json::UInt(firstOfferSeq + page * dirNodeMaxEntries + i); + cancelOffer[jss::TransactionType] = "OfferCancel"; + env(cancelOffer); + env.close(); + } + } + + // All the offers have been cancelled, so the book + // should have no entries and be empty: + { + Sandbox sb(env.closed().get(), tapNONE); + uint256 const bookBase = getBookBase({xrpIssue(), USD.issue()}); + + BEAST_EXPECT(dirIsEmpty (sb, keylet::page(bookBase))); + BEAST_EXPECT (!sb.succ(bookBase, getQualityNext(bookBase))); + } + + // Alice returns the USD she has to the gateway + // and removes her trust line. Her owner directory + // should now be empty: + { + env.trust(USD(0), alice); + env(pay(alice, gw, alice["USD"](1000))); + env.close(); + BEAST_EXPECT(dirIsEmpty (*env.closed(), keylet::ownerDir(alice))); + } + } + + void + testEmptyChain() + { + testcase("Empty Chain on Delete"); + + using namespace jtx; + Env env(*this, with_features(featureSortedDirectories)); + + auto const gw = Account{"gateway"}; + auto const alice = Account{"alice"}; + auto const USD = gw["USD"]; + + env.fund(XRP(10000), alice); + env.close(); + + uint256 base; + base.SetHex("fb71c9aa3310141da4b01d6c744a98286af2d72ab5448d5adc0910ca0c910880"); + + uint256 item; + item.SetHex("bad0f021aa3b2f6754a8fe82a5779730aa0bbbab82f17201ef24900efc2c7312"); + + { + // Create a chain of three pages: + Sandbox sb(env.closed().get(), tapNONE); + makePages (sb, base, 3); + + // Insert an item in the middle page: + { + auto p = sb.peek (keylet::page(base, 1)); + BEAST_EXPECT(p); + + STVector256 v; + v.push_back (item); + p->setFieldV256 (sfIndexes, v); + sb.update(p); + } + + // Now, try to delete the item from the middle + // page. This should cause all pages to be deleted: + BEAST_EXPECT (sb.dirRemove (keylet::page(base, 0), 1, keylet::unchecked(item), false)); + BEAST_EXPECT (!sb.peek(keylet::page(base, 2))); + BEAST_EXPECT (!sb.peek(keylet::page(base, 1))); + BEAST_EXPECT (!sb.peek(keylet::page(base, 0))); + } + + { + // Create a chain of four pages: + Sandbox sb(env.closed().get(), tapNONE); + makePages (sb, base, 4); + + // Now add items on pages 1 and 2: + { + auto p1 = sb.peek (keylet::page(base, 1)); + BEAST_EXPECT(p1); + + STVector256 v1; + v1.push_back (~item); + p1->setFieldV256 (sfIndexes, v1); + sb.update(p1); + + auto p2 = sb.peek (keylet::page(base, 2)); + BEAST_EXPECT(p2); + + STVector256 v2; + v2.push_back (item); + p2->setFieldV256 (sfIndexes, v2); + sb.update(p2); + } + + // Now, try to delete the item from page 2. + // This should cause pages 2 and 3 to be + // deleted: + BEAST_EXPECT (sb.dirRemove (keylet::page(base, 0), 2, keylet::unchecked(item), false)); + BEAST_EXPECT (!sb.peek(keylet::page(base, 3))); + BEAST_EXPECT (!sb.peek(keylet::page(base, 2))); + + auto p1 = sb.peek(keylet::page(base, 1)); + BEAST_EXPECT (p1); + BEAST_EXPECT (p1->getFieldU64 (sfIndexNext) == 0); + BEAST_EXPECT (p1->getFieldU64 (sfIndexPrevious) == 0); + + auto p0 = sb.peek(keylet::page(base, 0)); + BEAST_EXPECT (p0); + BEAST_EXPECT (p0->getFieldU64 (sfIndexNext) == 1); + BEAST_EXPECT (p0->getFieldU64 (sfIndexPrevious) == 1); + } } void run() override { - testDirectory(); + testDirectoryOrdering(); + testDirIsEmpty(); + testRipd1353(); + testEmptyChain(); } }; BEAST_DEFINE_TESTSUITE(Directory,ledger,ripple); -} // test -} // ripple +} +} diff --git a/src/test/ledger/Invariants_test.cpp b/src/test/ledger/Invariants_test.cpp index 520d915a99..c59d9de0cf 100644 --- a/src/test/ledger/Invariants_test.cpp +++ b/src/test/ledger/Invariants_test.cpp @@ -55,6 +55,7 @@ class Invariants_test : public beast::unit_test::suite // changes that will cause the check to fail. void doInvariantCheck( bool enabled, + std::vector const& expect_logs, std::function < bool ( test::jtx::Account const& a, @@ -94,18 +95,31 @@ class Invariants_test : public beast::unit_test::suite BEAST_EXPECT(precheck(A1, A2, ac)); - auto tr = ac.checkInvariants(tesSUCCESS); - if (enabled) + auto tr = tesSUCCESS; + // invoke check twice to cover tec and tef cases + for (auto i : {0,1}) { - BEAST_EXPECT(tr == tecINVARIANT_FAILED); - BEAST_EXPECT(boost::starts_with(sink.strm_.str(), "Invariant failed:")); - //uncomment if you want to log the invariant failure message - //log << " --> " << sink.strm_.str() << std::endl; - } - else - { - BEAST_EXPECT(tr == tesSUCCESS); - BEAST_EXPECT(sink.strm_.str().empty()); + tr = ac.checkInvariants(tr); + if (enabled) + { + BEAST_EXPECT( + tr == (i == 0 ? tecINVARIANT_FAILED : tefINVARIANT_FAILED)); + BEAST_EXPECT( + boost::starts_with(sink.strm_.str(), "Invariant failed:") || + boost::starts_with(sink.strm_.str(), + "Transaction caused an exception")); + //uncomment if you want to log the invariant failure message + //log << " --> " << sink.strm_.str() << std::endl; + for (auto const& m : expect_logs) + { + BEAST_EXPECT(sink.strm_.str().find(m) != std::string::npos); + } + } + else + { + BEAST_EXPECT(tr == tesSUCCESS); + BEAST_EXPECT(sink.strm_.str().empty()); + } } } @@ -130,6 +144,7 @@ class Invariants_test : public beast::unit_test::suite testcase << "checks " << (enabled ? "enabled" : "disabled") << " - XRP created"; doInvariantCheck (enabled, + {{ "XRP net change was 500 on a fee of 0" }}, [](Account const& A1, Account const&, ApplyContext& ac) { // put a single account in the view and "manufacture" some XRP @@ -150,6 +165,7 @@ class Invariants_test : public beast::unit_test::suite testcase << "checks " << (enabled ? "enabled" : "disabled") << " - account root removed"; doInvariantCheck (enabled, + {{ "an account root was deleted" }}, [](Account const& A1, Account const&, ApplyContext& ac) { // remove an account from the view @@ -168,6 +184,8 @@ class Invariants_test : public beast::unit_test::suite testcase << "checks " << (enabled ? "enabled" : "disabled") << " - LE types don't match"; doInvariantCheck (enabled, + {{ "ledger entry type mismatch" }, + { "XRP net change was -1000000000 on a fee of 0" }}, [](Account const& A1, Account const&, ApplyContext& ac) { // replace an entry in the table with an SLE of a different type @@ -180,6 +198,7 @@ class Invariants_test : public beast::unit_test::suite }); doInvariantCheck (enabled, + {{ "invalid ledger entry type added" }}, [](Account const& A1, Account const&, ApplyContext& ac) { // add an entry in the table with an SLE of an invalid type @@ -204,6 +223,7 @@ class Invariants_test : public beast::unit_test::suite testcase << "checks " << (enabled ? "enabled" : "disabled") << " - trust lines with XRP not allowed"; doInvariantCheck (enabled, + {{ "an XRP trust line was created" }}, [](Account const& A1, Account const& A2, ApplyContext& ac) { // create simple trust SLE with xrp currency @@ -215,18 +235,192 @@ class Invariants_test : public beast::unit_test::suite }); } + void + testXRPBalanceCheck(bool enabled) + { + using namespace test::jtx; + testcase << "checks " << (enabled ? "enabled" : "disabled") << + " - XRP balance checks"; + + doInvariantCheck (enabled, + {{ "Cannot return non-native STAmount as XRPAmount" }}, + [](Account const& A1, Account const& A2, ApplyContext& ac) + { + //non-native balance + auto const sle = ac.view().peek (keylet::account(A1.id())); + if(! sle) + return false; + STAmount nonNative (A2["USD"](51)); + sle->setFieldAmount (sfBalance, nonNative); + ac.view().update (sle); + return true; + }); + + doInvariantCheck (enabled, + {{ "incorrect account XRP balance" }, + { "XRP net change was 99999999000000001 on a fee of 0" }}, + [](Account const& A1, Account const&, ApplyContext& ac) + { + // balance exceeds genesis amount + auto const sle = ac.view().peek (keylet::account(A1.id())); + if(! sle) + return false; + sle->setFieldAmount (sfBalance, SYSTEM_CURRENCY_START + 1); + ac.view().update (sle); + return true; + }); + + doInvariantCheck (enabled, + {{ "incorrect account XRP balance" }, + { "XRP net change was -1000000001 on a fee of 0" }}, + [](Account const& A1, Account const&, ApplyContext& ac) + { + // balance is negative + auto const sle = ac.view().peek (keylet::account(A1.id())); + if(! sle) + return false; + sle->setFieldAmount (sfBalance, -1); + ac.view().update (sle); + return true; + }); + } + + void + testNoBadOffers(bool enabled) + { + using namespace test::jtx; + testcase << "checks " << (enabled ? "enabled" : "disabled") << + " - no bad offers"; + + doInvariantCheck (enabled, + {{ "offer with a bad amount" }}, + [](Account const& A1, Account const&, ApplyContext& ac) + { + // offer with negative takerpays + auto const sle = ac.view().peek (keylet::account(A1.id())); + if(! sle) + return false; + auto const offer_index = + getOfferIndex (A1.id(), (*sle)[sfSequence]); + auto sleNew = std::make_shared (ltOFFER, offer_index); + sleNew->setAccountID (sfAccount, A1.id()); + sleNew->setFieldU32 (sfSequence, (*sle)[sfSequence]); + sleNew->setFieldAmount (sfTakerPays, XRP(-1)); + ac.view().insert (sleNew); + return true; + }); + + doInvariantCheck (enabled, + {{ "offer with a bad amount" }}, + [](Account const& A1, Account const&, ApplyContext& ac) + { + // offer with negative takergets + auto const sle = ac.view().peek (keylet::account(A1.id())); + if(! sle) + return false; + auto const offer_index = + getOfferIndex (A1.id(), (*sle)[sfSequence]); + auto sleNew = std::make_shared (ltOFFER, offer_index); + sleNew->setAccountID (sfAccount, A1.id()); + sleNew->setFieldU32 (sfSequence, (*sle)[sfSequence]); + sleNew->setFieldAmount (sfTakerPays, A1["USD"](10)); + sleNew->setFieldAmount (sfTakerGets, XRP(-1)); + ac.view().insert (sleNew); + return true; + }); + + doInvariantCheck (enabled, + {{ "offer with a bad amount" }}, + [](Account const& A1, Account const&, ApplyContext& ac) + { + // offer XRP to XRP + auto const sle = ac.view().peek (keylet::account(A1.id())); + if(! sle) + return false; + auto const offer_index = + getOfferIndex (A1.id(), (*sle)[sfSequence]); + auto sleNew = std::make_shared (ltOFFER, offer_index); + sleNew->setAccountID (sfAccount, A1.id()); + sleNew->setFieldU32 (sfSequence, (*sle)[sfSequence]); + sleNew->setFieldAmount (sfTakerPays, XRP(10)); + sleNew->setFieldAmount (sfTakerGets, XRP(11)); + ac.view().insert (sleNew); + return true; + }); + } + + void + testNoZeroEscrow(bool enabled) + { + using namespace test::jtx; + testcase << "checks " << (enabled ? "enabled" : "disabled") << + " - no zero escrow"; + + doInvariantCheck (enabled, + {{ "Cannot return non-native STAmount as XRPAmount" }}, + [](Account const& A1, Account const& A2, ApplyContext& ac) + { + // escrow with nonnative amount + auto const sle = ac.view().peek (keylet::account(A1.id())); + if(! sle) + return false; + auto sleNew = std::make_shared ( + keylet::escrow(A1, (*sle)[sfSequence] + 2)); + STAmount nonNative (A2["USD"](51)); + sleNew->setFieldAmount (sfAmount, nonNative); + ac.view().insert (sleNew); + return true; + }); + + doInvariantCheck (enabled, + {{ "XRP net change was -1000000 on a fee of 0"}, + { "escrow specifies invalid amount" }}, + [](Account const& A1, Account const&, ApplyContext& ac) + { + // escrow with negative amount + auto const sle = ac.view().peek (keylet::account(A1.id())); + if(! sle) + return false; + auto sleNew = std::make_shared ( + keylet::escrow(A1, (*sle)[sfSequence] + 2)); + sleNew->setFieldAmount (sfAmount, XRP(-1)); + ac.view().insert (sleNew); + return true; + }); + + doInvariantCheck (enabled, + {{ "XRP net change was 100000000000000001 on a fee of 0" }, + { "escrow specifies invalid amount" }}, + [](Account const& A1, Account const&, ApplyContext& ac) + { + // escrow with too-large amount + auto const sle = ac.view().peek (keylet::account(A1.id())); + if(! sle) + return false; + auto sleNew = std::make_shared ( + keylet::escrow(A1, (*sle)[sfSequence] + 2)); + sleNew->setFieldAmount (sfAmount, SYSTEM_CURRENCY_START + 1); + ac.view().insert (sleNew); + return true; + }); + } + public: void run () { testEnabled (); - // all invariant checks are run with - // the checks enabled and disabled - for(auto const& b : {true, false}) + + // now run each invariant check test with + // the feature enabled and disabled + for(auto const& b : {false, true}) { testXRPNotCreated (b); testAccountsNotRemoved (b); testTypesMatch (b); testNoXRPTrustLine (b); + testXRPBalanceCheck (b); + testNoBadOffers (b); + testNoZeroEscrow (b); } } }; @@ -234,3 +428,4 @@ public: BEAST_DEFINE_TESTSUITE (Invariants, ledger, ripple); } // ripple + diff --git a/src/test/ledger/PaymentSandbox_test.cpp b/src/test/ledger/PaymentSandbox_test.cpp index ebed1ee0ff..0ab0efef29 100644 --- a/src/test/ledger/PaymentSandbox_test.cpp +++ b/src/test/ledger/PaymentSandbox_test.cpp @@ -60,7 +60,7 @@ class PaymentSandbox_test : public beast::unit_test::suite testcase ("selfFunding"); using namespace jtx; - Env env (*this, features(fs)); + Env env (*this, with_features(fs)); Account const gw1 ("gw1"); Account const gw2 ("gw2"); Account const snd ("snd"); @@ -101,7 +101,7 @@ class PaymentSandbox_test : public beast::unit_test::suite testcase ("subtractCredits"); using namespace jtx; - Env env (*this, features(fs)); + Env env (*this, with_features(fs)); Account const gw1 ("gw1"); Account const gw2 ("gw2"); Account const alice ("alice"); @@ -266,7 +266,7 @@ class PaymentSandbox_test : public beast::unit_test::suite using namespace jtx; - Env env (*this, features(fs)); + Env env (*this, with_features(fs)); Account const gw ("gw"); Account const alice ("alice"); @@ -278,10 +278,10 @@ class PaymentSandbox_test : public beast::unit_test::suite STAmount hugeAmt (issue, STAmount::cMaxValue, STAmount::cMaxOffset - 1, false, false, STAmount::unchecked{}); - for (auto timeDelta : {-env.closed ()->info ().closeTimeResolution, - env.closed ()->info ().closeTimeResolution}) + for (auto d : {-1, 1}) { - auto const closeTime = fix1141Time () + timeDelta; + auto const closeTime = fix1141Time () + + d * env.closed()->info().closeTimeResolution; env.close (closeTime); ApplyViewImpl av (&*env.current (), tapNONE); PaymentSandbox pv (&av); @@ -312,7 +312,7 @@ class PaymentSandbox_test : public beast::unit_test::suite return env.current ()->fees ().accountReserve (count); }; - Env env (*this, features(fs)); + Env env (*this, with_features(fs)); Account const alice ("alice"); env.fund (reserve(env, 1), alice); @@ -340,7 +340,7 @@ class PaymentSandbox_test : public beast::unit_test::suite testcase ("balanceHook"); using namespace jtx; - Env env (*this, features(fs)); + Env env (*this, with_features(fs)); Account const gw ("gw"); auto const USD = gw["USD"]; diff --git a/src/test/ledger/View_test.cpp b/src/test/ledger/View_test.cpp index 4c698a1460..42bc8db559 100644 --- a/src/test/ledger/View_test.cpp +++ b/src/test/ledger/View_test.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include namespace ripple { @@ -834,109 +835,8 @@ class GetAmendments_test } }; -class DirIsEmpty_test - : public beast::unit_test::suite -{ - void - testDirIsEmpty() - { - using namespace jtx; - auto const alice = Account("alice"); - auto const bogie = Account("bogie"); - - Env env(*this, features(featureMultiSign)); - - env.fund(XRP(10000), alice); - env.close(); - - // alice should have an empty directory. - BEAST_EXPECT(dirIsEmpty (*env.closed(), keylet::ownerDir(alice))); - - // Give alice a signer list, then there will be stuff in the directory. - env(signers(alice, 1, { { bogie, 1} })); - env.close(); - BEAST_EXPECT(! dirIsEmpty (*env.closed(), keylet::ownerDir(alice))); - - env(signers(alice, jtx::none)); - env.close(); - BEAST_EXPECT(dirIsEmpty (*env.closed(), keylet::ownerDir(alice))); - - // The next test is a bit awkward. It tests the case where alice - // uses 3 directory pages and then deletes all entries from the - // first 2 pages. dirIsEmpty() should still return false in this - // circumstance. - // - // Fill alice's directory with implicit trust lines (produced by - // taking offers) and then remove all but the last one. - auto const becky = Account ("becky"); - auto const gw = Account ("gw"); - env.fund(XRP(10000), becky, gw); - env.close(); - - // The DIR_NODE_MAX constant is hidden in View.cpp (Feb 2016). But, - // ideally, we'd verify we're doing a good test with the following: -// static_assert (64 >= (2 * DIR_NODE_MAX), ""); - - // Generate 64 currencies named AAA -> AAP and ADA -> ADP. - std::vector currencies; - currencies.reserve(64); - for (char b = 'A'; b <= 'D'; ++b) - { - for (char c = 'A'; c <= 'P'; ++c) - { - currencies.push_back(gw[std::string("A") + b + c]); - IOU const& currency = currencies.back(); - - // Establish trust lines. - env(trust(becky, currency(50))); - env.close(); - env(pay(gw, becky, currency(50))); - env.close(); - env(offer(alice, currency(50), XRP(10))); - env(offer(becky, XRP(10), currency(50))); - env.close(); - } - } - - // Set up one more currency that alice will hold onto. We expect - // this one to go in the third directory page. - IOU const lastCurrency = gw["ZZZ"]; - env(trust(becky, lastCurrency(50))); - env.close(); - env(pay(gw, becky, lastCurrency(50))); - env.close(); - env(offer(alice, lastCurrency(50), XRP(10))); - env(offer(becky, XRP(10), lastCurrency(50))); - env.close(); - - BEAST_EXPECT(! dirIsEmpty (*env.closed(), keylet::ownerDir(alice))); - - // Now alice gives all the currencies except the last one back to becky. - for (auto currency : currencies) - { - env(pay(alice, becky, currency(50))); - env.close(); - } - - // This is the crux of the test. - BEAST_EXPECT(! dirIsEmpty (*env.closed(), keylet::ownerDir(alice))); - - // Give the last currency to becky. Now alice's directory is empty. - env(pay(alice, becky, lastCurrency(50))); - env.close(); - - BEAST_EXPECT(dirIsEmpty (*env.closed(), keylet::ownerDir(alice))); - } - - void run() override - { - testDirIsEmpty(); - } -}; - BEAST_DEFINE_TESTSUITE(View,ledger,ripple); BEAST_DEFINE_TESTSUITE(GetAmendments,ledger,ripple); -BEAST_DEFINE_TESTSUITE(DirIsEmpty, ledger,ripple); } // test } // ripple diff --git a/src/test/nodestore/import_test.cpp b/src/test/nodestore/import_test.cpp index db28d3f4b4..ad1930f43c 100644 --- a/src/test/nodestore/import_test.cpp +++ b/src/test/nodestore/import_test.cpp @@ -27,7 +27,7 @@ #include #include #include -#include +#include #include #include #include @@ -258,7 +258,7 @@ public: } }; -std::map +std::map parse_args(std::string const& s) { // '=' @@ -274,7 +274,7 @@ parse_args(std::string const& s) , boost::regex_constants::optimize ); std::map map; + std::string, beast::iless> map; auto const v = beast::rfc2616::split( s.begin(), s.end(), ','); for (auto const& kv : v) diff --git a/src/test/overlay/short_read_test.cpp b/src/test/overlay/short_read_test.cpp index 7d2f99550b..20bf5af056 100644 --- a/src/test/overlay/short_read_test.cpp +++ b/src/test/overlay/short_read_test.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include @@ -211,7 +210,7 @@ private: { acceptor_.async_accept(socket_, strand_.wrap(std::bind( &Acceptor::on_accept, shared_from_this(), - beast::asio::placeholders::error))); + std::placeholders::_1))); } void @@ -237,7 +236,7 @@ private: p->run(); acceptor_.async_accept(socket_, strand_.wrap(std::bind( &Acceptor::on_accept, shared_from_this(), - beast::asio::placeholders::error))); + std::placeholders::_1))); } }; @@ -281,10 +280,10 @@ private: { timer_.expires_from_now(std::chrono::seconds(3)); timer_.async_wait(strand_.wrap(std::bind(&Connection::on_timer, - shared_from_this(), beast::asio::placeholders::error))); + shared_from_this(), std::placeholders::_1))); stream_.async_handshake(stream_type::server, strand_.wrap( std::bind(&Connection::on_handshake, shared_from_this(), - beast::asio::placeholders::error))); + std::placeholders::_1))); } void @@ -319,8 +318,8 @@ private: #if 1 boost::asio::async_read_until(stream_, buf_, "\n", strand_.wrap( std::bind(&Connection::on_read, shared_from_this(), - beast::asio::placeholders::error, - beast::asio::placeholders::bytes_transferred))); + std::placeholders::_1, + std::placeholders::_2))); #else close(); #endif @@ -334,7 +333,7 @@ private: server_.test_.log << "[server] read: EOF" << std::endl; return stream_.async_shutdown(strand_.wrap(std::bind( &Connection::on_shutdown, shared_from_this(), - beast::asio::placeholders::error))); + std::placeholders::_1))); } if (ec) return fail("read", ec); @@ -344,8 +343,8 @@ private: write(buf_, "BYE\n"); boost::asio::async_write(stream_, buf_.data(), strand_.wrap( std::bind(&Connection::on_write, shared_from_this(), - beast::asio::placeholders::error, - beast::asio::placeholders::bytes_transferred))); + std::placeholders::_1, + std::placeholders::_2))); } void @@ -356,7 +355,7 @@ private: return fail("write", ec); stream_.async_shutdown(strand_.wrap(std::bind( &Connection::on_shutdown, shared_from_this(), - beast::asio::placeholders::error))); + std::placeholders::_1))); } void @@ -432,10 +431,10 @@ private: { timer_.expires_from_now(std::chrono::seconds(3)); timer_.async_wait(strand_.wrap(std::bind(&Connection::on_timer, - shared_from_this(), beast::asio::placeholders::error))); + shared_from_this(), std::placeholders::_1))); socket_.async_connect(endpoint(), strand_.wrap(std::bind( &Connection::on_connect, shared_from_this(), - beast::asio::placeholders::error))); + std::placeholders::_1))); } void @@ -469,7 +468,7 @@ private: return fail("connect", ec); stream_.async_handshake(stream_type::client, strand_.wrap( std::bind(&Connection::on_handshake, shared_from_this(), - beast::asio::placeholders::error))); + std::placeholders::_1))); } void @@ -482,12 +481,12 @@ private: #if 1 boost::asio::async_write(stream_, buf_.data(), strand_.wrap( std::bind(&Connection::on_write, shared_from_this(), - beast::asio::placeholders::error, - beast::asio::placeholders::bytes_transferred))); + std::placeholders::_1, + std::placeholders::_2))); #else stream_.async_shutdown(strand_.wrap(std::bind( &Connection::on_shutdown, shared_from_this(), - beast::asio::placeholders::error))); + std::placeholders::_1))); #endif } @@ -500,12 +499,12 @@ private: #if 1 boost::asio::async_read_until(stream_, buf_, "\n", strand_.wrap( std::bind(&Connection::on_read, shared_from_this(), - beast::asio::placeholders::error, - beast::asio::placeholders::bytes_transferred))); + std::placeholders::_1, + std::placeholders::_2))); #else stream_.async_shutdown(strand_.wrap(std::bind( &Connection::on_shutdown, shared_from_this(), - beast::asio::placeholders::error))); + std::placeholders::_1))); #endif } @@ -517,7 +516,7 @@ private: buf_.commit(bytes_transferred); stream_.async_shutdown(strand_.wrap(std::bind( &Connection::on_shutdown, shared_from_this(), - beast::asio::placeholders::error))); + std::placeholders::_1))); } void diff --git a/src/test/protocol/STTx_test.cpp b/src/test/protocol/STTx_test.cpp index 4e7ad6a6cf..54795b39a9 100644 --- a/src/test/protocol/STTx_test.cpp +++ b/src/test/protocol/STTx_test.cpp @@ -128,7 +128,7 @@ public: // This lambda contains the bulk of the test code. auto testMalformedSigningAccount = - [this, &txn, &id1] + [this, &txn] (STObject const& signer, bool expectPass) { // Create SigningAccounts array. diff --git a/src/test/quiet_reporter.h b/src/test/quiet_reporter.h index da5a3bbd93..7b6c804af9 100644 --- a/src/test/quiet_reporter.h +++ b/src/test/quiet_reporter.h @@ -134,7 +134,8 @@ public: return b.second < a.second; }); - top.resize(10); + if(top.size() > 10) + top.resize(10); os_ << "Longest suite times:\n"; for(auto const& i : top) diff --git a/src/test/rpc/AccountInfo_test.cpp b/src/test/rpc/AccountInfo_test.cpp index 31904ef9fe..1dc29004d9 100644 --- a/src/test/rpc/AccountInfo_test.cpp +++ b/src/test/rpc/AccountInfo_test.cpp @@ -59,7 +59,7 @@ public: void testSignerLists() { using namespace jtx; - Env env(*this, features(featureMultiSign)); + Env env(*this, with_features(featureMultiSign)); Account const alice {"alice"}; env.fund(XRP(1000), alice); @@ -165,7 +165,7 @@ public: void testSignerListsV2() { using namespace jtx; - Env env(*this, features(featureMultiSign)); + Env env(*this, with_features(featureMultiSign)); Account const alice {"alice"}; env.fund(XRP(1000), alice); diff --git a/src/test/rpc/AccountLinesRPC_test.cpp b/src/test/rpc/AccountLinesRPC_test.cpp index 477eb6b7c3..860726b475 100644 --- a/src/test/rpc/AccountLinesRPC_test.cpp +++ b/src/test/rpc/AccountLinesRPC_test.cpp @@ -33,6 +33,8 @@ class AccountLinesRPC_test : public beast::unit_test::suite public: void testAccountLines() { + testcase ("acccount_lines"); + using namespace test::jtx; Env env(*this); { @@ -284,6 +286,7 @@ public: void testAccountLineDelete() { + testcase ("Entry pointed to by marker is removed"); using namespace test::jtx; Env env(*this); @@ -308,35 +311,35 @@ public: auto const USD = gw1["USD"]; auto const EUR = gw2["EUR"]; - env(trust(alice, EUR(200))); - env(trust(becky, USD(200))); - env(trust(cheri, USD(200))); + env(trust(alice, USD(200))); + env(trust(becky, EUR(200))); + env(trust(cheri, EUR(200))); env.close(); // becky gets 100 USD from gw1. - env(pay(gw1, becky, USD(100))); + env(pay(gw2, becky, EUR(100))); env.close(); - // alice offers to buy 100 USD for 100 XRP. - env(offer(alice, USD(100), XRP(100))); + // alice offers to buy 100 EUR for 100 XRP. + env(offer(alice, EUR(100), XRP(100))); env.close(); - // becky offers to buy 100 XRP for 100 USD. - env(offer(becky, XRP(100), USD(100))); + // becky offers to buy 100 XRP for 100 EUR. + env(offer(becky, XRP(100), EUR(100))); env.close(); // Get account_lines for alice. Limit at 1, so we get a marker. auto const linesBeg = env.rpc ("json", "account_lines", R"({"account": ")" + alice.human() + R"(", )" R"("limit": 1})"); - BEAST_EXPECT(linesBeg[jss::result][jss::lines][0u][jss::currency] == "EUR"); + BEAST_EXPECT(linesBeg[jss::result][jss::lines][0u][jss::currency] == "USD"); BEAST_EXPECT(linesBeg[jss::result].isMember(jss::marker)); - // alice pays 100 USD to cheri. - env(pay(alice, cheri, USD(100))); + // alice pays 100 EUR to cheri. + env(pay(alice, cheri, EUR(100))); env.close(); - // Since alice paid all her USD to cheri, alice should no longer + // Since alice paid all her EUR to cheri, alice should no longer // have a trust line to gw1. So the old marker should now be invalid. auto const linesEnd = env.rpc ("json", "account_lines", R"({"account": ")" + alice.human() + R"(", )" @@ -349,6 +352,8 @@ public: // test API V2 void testAccountLines2() { + testcase ("V2: acccount_lines"); + using namespace test::jtx; Env env(*this); { @@ -779,6 +784,8 @@ public: // test API V2 void testAccountLineDelete2() { + testcase ("V2: account_lines with removed marker"); + using namespace test::jtx; Env env(*this); @@ -787,12 +794,12 @@ public: // // It isn't easy to explicitly delete a trust line, so we do so in a // round-about fashion. It takes 4 actors: - // o Gateway gw1 issues USD - // o alice offers to buy 100 USD for 100 XRP. - // o becky offers to sell 100 USD for 100 XRP. - // There will now be an inferred trustline between alice and gw1. - // o alice pays her 100 USD to cheri. - // alice should now have no USD and no trustline to gw1. + // o Gateway gw1 issues EUR + // o alice offers to buy 100 EUR for 100 XRP. + // o becky offers to sell 100 EUR for 100 XRP. + // There will now be an inferred trustline between alice and gw2. + // o alice pays her 100 EUR to cheri. + // alice should now have no EUR and no trustline to gw2. Account const alice {"alice"}; Account const becky {"becky"}; Account const cheri {"cheri"}; @@ -803,21 +810,21 @@ public: auto const USD = gw1["USD"]; auto const EUR = gw2["EUR"]; - env(trust(alice, EUR(200))); - env(trust(becky, USD(200))); - env(trust(cheri, USD(200))); + env(trust(alice, USD(200))); + env(trust(becky, EUR(200))); + env(trust(cheri, EUR(200))); env.close(); - // becky gets 100 USD from gw1. - env(pay(gw1, becky, USD(100))); + // becky gets 100 EUR from gw1. + env(pay(gw2, becky, EUR(100))); env.close(); - // alice offers to buy 100 USD for 100 XRP. - env(offer(alice, USD(100), XRP(100))); + // alice offers to buy 100 EUR for 100 XRP. + env(offer(alice, EUR(100), XRP(100))); env.close(); - // becky offers to buy 100 XRP for 100 USD. - env(offer(becky, XRP(100), USD(100))); + // becky offers to buy 100 XRP for 100 EUR. + env(offer(becky, XRP(100), EUR(100))); env.close(); // Get account_lines for alice. Limit at 1, so we get a marker. @@ -829,17 +836,17 @@ public: R"("params": [ )" R"({"account": ")" + alice.human() + R"(", )" R"("limit": 1}]})"); - BEAST_EXPECT(linesBeg[jss::result][jss::lines][0u][jss::currency] == "EUR"); + BEAST_EXPECT(linesBeg[jss::result][jss::lines][0u][jss::currency] == "USD"); BEAST_EXPECT(linesBeg[jss::result].isMember(jss::marker)); BEAST_EXPECT(linesBeg.isMember(jss::jsonrpc) && linesBeg[jss::jsonrpc] == "2.0"); BEAST_EXPECT(linesBeg.isMember(jss::ripplerpc) && linesBeg[jss::ripplerpc] == "2.0"); BEAST_EXPECT(linesBeg.isMember(jss::id) && linesBeg[jss::id] == 5); // alice pays 100 USD to cheri. - env(pay(alice, cheri, USD(100))); + env(pay(alice, cheri, EUR(100))); env.close(); - // Since alice paid all her USD to cheri, alice should no longer + // Since alice paid all her EUR to cheri, alice should no longer // have a trust line to gw1. So the old marker should now be invalid. auto const linesEnd = env.rpc ("json2", "{ " R"("method" : "account_lines",)" diff --git a/src/test/rpc/AccountObjects_test.cpp b/src/test/rpc/AccountObjects_test.cpp index 909161bc13..302287e26f 100644 --- a/src/test/rpc/AccountObjects_test.cpp +++ b/src/test/rpc/AccountObjects_test.cpp @@ -29,96 +29,85 @@ namespace ripple { namespace test { static char const* bobs_account_objects[] = { -R"json( -{ - "Balance": { - "currency": "USD", - "issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji", - "value": "-1000" - }, - "Flags": 131072, - "HighLimit": { - "currency": "USD", - "issuer": "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK", - "value": "1000" - }, - "HighNode": "0000000000000000", - "LedgerEntryType": "RippleState", - "LowLimit": { - "currency": "USD", - "issuer": "r32rQHyesiTtdWFU7UJVtff4nCR5SHCbJW", - "value": "0" - }, - "LowNode": "0000000000000000", - "index": - "D89BC239086183EB9458C396E643795C1134963E6550E682A190A5F021766D43" -})json" -, -R"json( -{ - "Balance": { - "currency": "USD", - "issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji", - "value": "-1000" - }, - "Flags": 131072, - "HighLimit": { - "currency": "USD", - "issuer": "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK", - "value": "1000" - }, - "HighNode": "0000000000000000", - "LedgerEntryType": "RippleState", - "LowLimit": { - "currency": "USD", - "issuer": "r9cZvwKU3zzuZK9JFovGg1JC5n7QiqNL8L", - "value": "0" - }, - "LowNode": "0000000000000000", - "index": - "D13183BCFFC9AAC9F96AEBB5F66E4A652AD1F5D10273AEB615478302BEBFD4A4" -})json" -, -R"json( -{ - "Account": "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK", - "BookDirectory": - "50AD0A9E54D2B381288D535EB724E4275FFBF41580D28A925D038D7EA4C68000", - "BookNode": "0000000000000000", - "Flags": 65536, - "LedgerEntryType": "Offer", - "OwnerNode": "0000000000000000", - "Sequence": 4, - "TakerGets": { - "currency": "USD", - "issuer": "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK", - "value": "1" - }, - "TakerPays": "100000000", - "index": - "A984D036A0E562433A8377CA57D1A1E056E58C0D04818F8DFD3A1AA3F217DD82" -})json" -, -R"json( -{ - "Account": "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK", - "BookDirectory": - "B025997A323F5C3E03DDF1334471F5984ABDE31C59D463525D038D7EA4C68000", - "BookNode": "0000000000000000", - "Flags": 65536, - "LedgerEntryType": "Offer", - "OwnerNode": "0000000000000000", - "Sequence": 5, - "TakerGets": { - "currency": "USD", - "issuer" : "r32rQHyesiTtdWFU7UJVtff4nCR5SHCbJW", +R"json({ + "Account" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK", + "BookDirectory" : "50AD0A9E54D2B381288D535EB724E4275FFBF41580D28A925D038D7EA4C68000", + "BookNode" : "0000000000000000", + "Flags" : 65536, + "LedgerEntryType" : "Offer", + "OwnerNode" : "0000000000000000", + "Sequence" : 4, + "TakerGets" : { + "currency" : "USD", + "issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK", "value" : "1" }, "TakerPays" : "100000000", - "index" : - "CAFE32332D752387B01083B60CC63069BA4A969C9730836929F841450F6A718E" -} -)json" + "index" : "A984D036A0E562433A8377CA57D1A1E056E58C0D04818F8DFD3A1AA3F217DD82" +})json" +, +R"json({ + "Account" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK", + "BookDirectory" : "B025997A323F5C3E03DDF1334471F5984ABDE31C59D463525D038D7EA4C68000", + "BookNode" : "0000000000000000", + "Flags" : 65536, + "LedgerEntryType" : "Offer", + "OwnerNode" : "0000000000000000", + "Sequence" : 5, + "TakerGets" : { + "currency" : "USD", + "issuer" : "r32rQHyesiTtdWFU7UJVtff4nCR5SHCbJW", + "value" : "1" + }, + "TakerPays" : "100000000", + "index" : "CAFE32332D752387B01083B60CC63069BA4A969C9730836929F841450F6A718E" +})json" +, +R"json({ + "Balance" : { + "currency" : "USD", + "issuer" : "rrrrrrrrrrrrrrrrrrrrBZbvji", + "value" : "-1000" + }, + "Flags" : 131072, + "HighLimit" : { + "currency" : "USD", + "issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK", + "value" : "1000" + }, + "HighNode" : "0000000000000000", + "LedgerEntryType" : "RippleState", + "LowLimit" : { + "currency" : "USD", + "issuer" : "r9cZvwKU3zzuZK9JFovGg1JC5n7QiqNL8L", + "value" : "0" + }, + "LowNode" : "0000000000000000", + "index" : "D13183BCFFC9AAC9F96AEBB5F66E4A652AD1F5D10273AEB615478302BEBFD4A4" +})json" +, +R"json({ + "Balance" : { + "currency" : "USD", + "issuer" : "rrrrrrrrrrrrrrrrrrrrBZbvji", + "value" : "-1000" + }, + "Flags" : 131072, + "HighLimit" : { + "currency" : "USD", + "issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK", + "value" : "1000" + }, + "HighNode" : "0000000000000000", + "LedgerEntryType" : "RippleState", + "LowLimit" : { + "currency" : "USD", + "issuer" : "r32rQHyesiTtdWFU7UJVtff4nCR5SHCbJW", + "value" : "0" + }, + "LowNode" : "0000000000000000", + "index" : "D89BC239086183EB9458C396E643795C1134963E6550E682A190A5F021766D43" +})json" }; class AccountObjects_test : public beast::unit_test::suite @@ -196,7 +185,7 @@ public: env.trust(USD(1000), bob); env(pay(gw, bob, XRP(1))); env(offer(bob, XRP(100), bob["USD"](1)), txflags(tfPassive)); - + Json::Value params; params[jss::account] = bob.human(); params[jss::limit] = 1; @@ -254,7 +243,7 @@ public: env.fund(XRP(1000), gw1, gw2, bob); env.trust(USD1(1000), bob); env.trust(USD2(1000), bob); - + env(pay(gw1, bob, USD1(1000))); env(pay(gw2, bob, USD2(1000))); @@ -280,7 +269,9 @@ public: aobj.removeMember("PreviousTxnID"); aobj.removeMember("PreviousTxnLgrSeq"); - BEAST_EXPECT( aobj == bobj[i]); + if (aobj != bobj[i]) + std::cout << "Fail at " << i << ": " << aobj << std::endl; + BEAST_EXPECT(aobj == bobj[i]); } } // test request with type parameter as filter, unstepped @@ -298,7 +289,7 @@ public: aobj.removeMember("PreviousTxnID"); aobj.removeMember("PreviousTxnLgrSeq"); - BEAST_EXPECT( aobj == bobj[i]); + BEAST_EXPECT( aobj == bobj[i+2]); } } // test stepped one-at-a-time with limit=1, resume from prev marker @@ -316,7 +307,8 @@ public: aobj.removeMember("PreviousTxnID"); aobj.removeMember("PreviousTxnLgrSeq"); - BEAST_EXPECT( aobj == bobj[i]); + + BEAST_EXPECT(aobj == bobj[i]); auto resume_marker = resp[jss::result][jss::marker]; params[jss::marker] = resume_marker; diff --git a/src/test/rpc/AccountOffers_test.cpp b/src/test/rpc/AccountOffers_test.cpp index 7d76dc64c4..de574cbc25 100644 --- a/src/test/rpc/AccountOffers_test.cpp +++ b/src/test/rpc/AccountOffers_test.cpp @@ -108,21 +108,21 @@ public: { BEAST_EXPECT(jro[0u][jss::quality] == "100000000"); BEAST_EXPECT(jro[0u][jss::taker_gets][jss::currency] == "USD"); - BEAST_EXPECT(jro[0u][jss::taker_gets][jss::issuer] == bob.human()); + BEAST_EXPECT(jro[0u][jss::taker_gets][jss::issuer] == gw.human()); BEAST_EXPECT(jro[0u][jss::taker_gets][jss::value] == "1"); BEAST_EXPECT(jro[0u][jss::taker_pays] == "100000000"); - BEAST_EXPECT(jro[1u][jss::quality] == "100000000"); + BEAST_EXPECT(jro[1u][jss::quality] == "5000000"); BEAST_EXPECT(jro[1u][jss::taker_gets][jss::currency] == "USD"); BEAST_EXPECT(jro[1u][jss::taker_gets][jss::issuer] == gw.human()); - BEAST_EXPECT(jro[1u][jss::taker_gets][jss::value] == "1"); - BEAST_EXPECT(jro[1u][jss::taker_pays] == "100000000"); + BEAST_EXPECT(jro[1u][jss::taker_gets][jss::value] == "2"); + BEAST_EXPECT(jro[1u][jss::taker_pays] == "10000000"); - BEAST_EXPECT(jro[2u][jss::quality] == "5000000"); + BEAST_EXPECT(jro[2u][jss::quality] == "100000000"); BEAST_EXPECT(jro[2u][jss::taker_gets][jss::currency] == "USD"); - BEAST_EXPECT(jro[2u][jss::taker_gets][jss::issuer] == gw.human()); - BEAST_EXPECT(jro[2u][jss::taker_gets][jss::value] == "2"); - BEAST_EXPECT(jro[2u][jss::taker_pays] == "10000000"); + BEAST_EXPECT(jro[2u][jss::taker_gets][jss::issuer] == bob.human()); + BEAST_EXPECT(jro[2u][jss::taker_gets][jss::value] == "1"); + BEAST_EXPECT(jro[2u][jss::taker_pays] == "100000000"); } { diff --git a/src/test/rpc/AccountSet_test.cpp b/src/test/rpc/AccountSet_test.cpp index 248fb05680..7ff5426dc2 100644 --- a/src/test/rpc/AccountSet_test.cpp +++ b/src/test/rpc/AccountSet_test.cpp @@ -17,10 +17,13 @@ */ //============================================================================== -#include -#include -#include #include +#include +#include +#include +#include +#include +#include namespace ripple { @@ -203,30 +206,132 @@ public: void testTransferRate() { + struct test_results + { + double set; + TER code; + double get; + }; + using namespace test::jtx; - Env env(*this); - Account const alice ("alice"); - env.fund(XRP(10000), alice); - auto jt = noop(alice); + auto doTests = [this] (FeatureBitset const& features, + std::initializer_list testData) + { + Env env (*this, features); - uint32 xfer_rate = 2000000000; - jt[sfTransferRate.fieldName] = xfer_rate; - env(jt); - BEAST_EXPECT((*env.le(alice))[ sfTransferRate ] == xfer_rate); + Account const alice ("alice"); + env.fund(XRP(10000), alice); - jt[sfTransferRate.fieldName] = 0u; - env(jt); - BEAST_EXPECT(! env.le(alice)->isFieldPresent(sfTransferRate)); + for (auto const& r : testData) + { + env(rate(alice, r.set), ter(r.code)); - // set a bad value (< QUALITY_ONE) - jt[sfTransferRate.fieldName] = 10u; - env(jt, ter(temBAD_TRANSFER_RATE)); + // If the field is not present expect the default value + if (!(*env.le(alice))[~sfTransferRate]) + BEAST_EXPECT(r.get == 1.0); + else + BEAST_EXPECT(*(*env.le(alice))[~sfTransferRate] == + r.get * QUALITY_ONE); + } + }; + + { + testcase ("Setting transfer rate (without fix1201)"); + doTests (all_features_except(fix1201), + { + { 1.0, tesSUCCESS, 1.0 }, + { 1.1, tesSUCCESS, 1.1 }, + { 2.0, tesSUCCESS, 2.0 }, + { 2.1, tesSUCCESS, 2.1 }, + { 0.0, tesSUCCESS, 1.0 }, + { 2.0, tesSUCCESS, 2.0 }, + { 0.9, temBAD_TRANSFER_RATE, 2.0 }}); + } + + { + testcase ("Setting transfer rate (with fix1201)"); + doTests (all_amendments(), + { + { 1.0, tesSUCCESS, 1.0 }, + { 1.1, tesSUCCESS, 1.1 }, + { 2.0, tesSUCCESS, 2.0 }, + { 2.1, temBAD_TRANSFER_RATE, 2.0 }, + { 0.0, tesSUCCESS, 1.0 }, + { 2.0, tesSUCCESS, 2.0 }, + { 0.9, temBAD_TRANSFER_RATE, 2.0 }}); + } } - void testBadInputs() + void testGateway() { using namespace test::jtx; - Env env(*this); + auto runTest = [](Env&& env, double tr) + { + Account const alice ("alice"); + Account const bob ("bob"); + Account const gw ("gateway"); + auto const USD = gw["USD"]; + + env.fund(XRP(10000), gw, alice, bob); + env.trust(USD(3), alice, bob); + env(rate(gw, tr)); + env.close(); + + auto const amount = USD(1); + Rate const rate (tr * QUALITY_ONE); + auto const amountWithRate = + toAmount (multiply(amount.value(), rate)); + + env(pay(gw, alice, USD(3))); + env(pay(alice, bob, USD(1)), sendmax(USD(3))); + + env.require(balance(alice, USD(3) - amountWithRate)); + env.require(balance(bob, USD(1))); + }; + + // Test gateway with allowed transfer rates + runTest (Env{*this, all_features_except(fix1201)}, 1.02); + runTest (Env{*this, all_features_except(fix1201)}, 1); + runTest (Env{*this, all_features_except(fix1201)}, 2); + runTest (Env{*this, all_features_except(fix1201)}, 2.1); + runTest (Env{*this, all_amendments()}, 1.02); + runTest (Env{*this, all_amendments()}, 2); + + // Test gateway when amendment is set after transfer rate + { + Env env (*this, all_features_except(fix1201)); + Account const alice ("alice"); + Account const bob ("bob"); + Account const gw ("gateway"); + auto const USD = gw["USD"]; + double const tr = 2.75; + + env.fund(XRP(10000), gw, alice, bob); + env.trust(USD(3), alice, bob); + env(rate(gw, tr)); + env.close(); + env.enableFeature(fix1201); + env.close(); + + auto const amount = USD(1); + Rate const rate (tr * QUALITY_ONE); + auto const amountWithRate = + toAmount (multiply(amount.value(), rate)); + + env(pay(gw, alice, USD(3))); + env(pay(alice, bob, amount), sendmax(USD(3))); + + env.require(balance(alice, USD(3) - amountWithRate)); + env.require(balance(bob, amount)); + } + } + + void testBadInputs(bool withFeatures) + { + using namespace test::jtx; + std::unique_ptr penv { + withFeatures ? new Env(*this) : new Env(*this, no_features)}; + Env& env = *penv; Account const alice ("alice"); env.fund(XRP(10000), alice); @@ -259,13 +364,14 @@ public: env(jt, ter(temINVALID_FLAG)); env(fset (alice, asfDisableMaster), - sig(alice), ter(tecNO_REGULAR_KEY)); + sig(alice), + ter(withFeatures ? tecNO_ALTERNATIVE_KEY : tecNO_REGULAR_KEY)); } void testRequireAuthWithDir() { using namespace test::jtx; - Env env(*this, features(featureMultiSign)); + Env env(*this, with_features(featureMultiSign)); Account const alice ("alice"); Account const bob ("bob"); @@ -300,10 +406,12 @@ public: testSetAndResetAccountTxnID(); testSetNoFreeze(); testDomain(); + testGateway(); testMessageKey(); testWalletID(); testEmailHash(); - testBadInputs(); + testBadInputs(true); + testBadInputs(false); testRequireAuthWithDir(); testTransferRate(); } diff --git a/src/test/rpc/AmendmentBlocked_test.cpp b/src/test/rpc/AmendmentBlocked_test.cpp new file mode 100644 index 0000000000..cd68e76bab --- /dev/null +++ b/src/test/rpc/AmendmentBlocked_test.cpp @@ -0,0 +1,169 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2017 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include + +namespace ripple { + +class AmendmentBlocked_test : public beast::unit_test::suite +{ + void testBlockedMethods() + { + using namespace test::jtx; + Env env {*this}; + auto const gw = Account {"gateway"}; + auto const USD = gw["USD"]; + auto const alice = Account {"alice"}; + auto const bob = Account {"bob"}; + env.fund (XRP(10000), alice, bob, gw); + env.trust (USD(600), alice); + env.trust (USD(700), bob); + env(pay (gw, alice, USD(70))); + env(pay (gw, bob, USD(50))); + env.close(); + + auto wsc = test::makeWSClient(env.app().config()); + + auto current = env.current (); + // ledger_accept + auto jr = env.rpc ("ledger_accept") [jss::result]; + BEAST_EXPECT (jr[jss::ledger_current_index] == current->seq ()+1); + + // ledger_current + jr = env.rpc ("ledger_current") [jss::result]; + BEAST_EXPECT (jr[jss::ledger_current_index] == current->seq ()+1); + + // owner_info + jr = env.rpc ("owner_info", alice.human()) [jss::result]; + BEAST_EXPECT (jr.isMember (jss::accepted) && jr.isMember (jss::current)); + + // path_find + Json::Value pf_req; + pf_req[jss::subcommand] = "create"; + pf_req[jss::source_account] = alice.human(); + pf_req[jss::destination_account] = bob.human(); + pf_req[jss::destination_amount] = bob["USD"](20).value ().getJson (0); + jr = wsc->invoke("path_find", pf_req) [jss::result]; + BEAST_EXPECT (jr.isMember (jss::alternatives) && + jr[jss::alternatives].isArray () && + jr[jss::alternatives].size () == 1); + + // submit + auto jt = env.jt (noop (alice)); + Serializer s; + jt.stx->add (s); + jr = env.rpc ("submit", strHex (s.slice ())) [jss::result]; + BEAST_EXPECT (jr.isMember (jss::engine_result) && + jr[jss::engine_result] == "tesSUCCESS"); + + // submit_multisigned + env(signers(bob, 1, {{alice, 1}}), sig (bob)); + Account const ali {"ali", KeyType::secp256k1}; + env(regkey (alice, ali)); + env.close(); + + Json::Value set_tx; + set_tx[jss::Account] = bob.human(); + set_tx[jss::TransactionType] = "AccountSet"; + set_tx[jss::Fee] = + static_cast(8 * env.current()->fees().base); + set_tx[jss::Sequence] = env.seq(bob); + set_tx[jss::SigningPubKey] = ""; + + Json::Value sign_for; + sign_for[jss::tx_json] = set_tx; + sign_for[jss::account] = alice.human(); + sign_for[jss::secret] = ali.name(); + jr = env.rpc("json", "sign_for", to_string(sign_for)) [jss::result]; + BEAST_EXPECT(jr[jss::status] == "success"); + + Json::Value ms_req; + ms_req[jss::tx_json] = jr[jss::tx_json]; + jr = env.rpc("json", "submit_multisigned", to_string(ms_req)) + [jss::result]; + BEAST_EXPECT (jr.isMember (jss::engine_result) && + jr[jss::engine_result] == "tesSUCCESS"); + + // make the network amendment blocked...now all the same + // requests should fail + + env.app ().getOPs ().setAmendmentBlocked (); + + // ledger_accept + jr = env.rpc ("ledger_accept") [jss::result]; + BEAST_EXPECT( + jr.isMember (jss::error) && + jr[jss::error] == "amendmentBlocked"); + BEAST_EXPECT(jr[jss::status] == "error"); + + // ledger_current + jr = env.rpc ("ledger_current") [jss::result]; + BEAST_EXPECT( + jr.isMember (jss::error) && + jr[jss::error] == "amendmentBlocked"); + BEAST_EXPECT(jr[jss::status] == "error"); + + // owner_info + jr = env.rpc ("owner_info", alice.human()) [jss::result]; + BEAST_EXPECT( + jr.isMember (jss::error) && + jr[jss::error] == "amendmentBlocked"); + BEAST_EXPECT(jr[jss::status] == "error"); + + // path_find + jr = wsc->invoke("path_find", pf_req) [jss::result]; + BEAST_EXPECT( + jr.isMember (jss::error) && + jr[jss::error] == "amendmentBlocked"); + BEAST_EXPECT(jr[jss::status] == "error"); + + // submit + jr = env.rpc("submit", strHex(s.slice())) [jss::result]; + BEAST_EXPECT( + jr.isMember (jss::error) && + jr[jss::error] == "amendmentBlocked"); + BEAST_EXPECT(jr[jss::status] == "error"); + + // submit_multisigned + set_tx[jss::Sequence] = env.seq(bob); + sign_for[jss::tx_json] = set_tx; + jr = env.rpc("json", "sign_for", to_string(sign_for)) [jss::result]; + BEAST_EXPECT(jr[jss::status] == "success"); + ms_req[jss::tx_json] = jr[jss::tx_json]; + jr = env.rpc("json", "submit_multisigned", to_string(ms_req)) + [jss::result]; + BEAST_EXPECT( + jr.isMember (jss::error) && + jr[jss::error] == "amendmentBlocked"); + } + +public: + void run() + { + testBlockedMethods(); + } +}; + +BEAST_DEFINE_TESTSUITE(AmendmentBlocked,app,ripple); + +} + diff --git a/src/test/rpc/Feature_test.cpp b/src/test/rpc/Feature_test.cpp index a375adf877..7fa2aa12a6 100644 --- a/src/test/rpc/Feature_test.cpp +++ b/src/test/rpc/Feature_test.cpp @@ -116,8 +116,7 @@ class Feature_test : public beast::unit_test::suite using namespace test::jtx; Env env {*this, - features(featureEscrow), - features(featureCryptoConditions)}; + with_features(featureEscrow, featureCryptoConditions)}; // The amendment table has to be modified // since that is what feature RPC actually checks env.app().getAmendmentTable().enable(featureEscrow); @@ -221,7 +220,7 @@ class Feature_test : public beast::unit_test::suite using namespace test::jtx; Env env {*this, - features(featureCryptoConditions)}; + with_features(featureCryptoConditions)}; // The amendment table has to be modified // since that is what feature RPC actually checks env.app().getAmendmentTable().enable(featureCryptoConditions); diff --git a/src/test/rpc/GatewayBalances_test.cpp b/src/test/rpc/GatewayBalances_test.cpp index 6f76e0e7cc..73bfa6db76 100644 --- a/src/test/rpc/GatewayBalances_test.cpp +++ b/src/test/rpc/GatewayBalances_test.cpp @@ -34,7 +34,7 @@ public: { using namespace std::chrono_literals; using namespace jtx; - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); // Gateway account and assets Account const alice {"alice"}; diff --git a/src/test/rpc/JSONRPC_test.cpp b/src/test/rpc/JSONRPC_test.cpp index d828c44067..4f31b953b3 100644 --- a/src/test/rpc/JSONRPC_test.cpp +++ b/src/test/rpc/JSONRPC_test.cpp @@ -1942,7 +1942,7 @@ public: .set("minimum_txn_in_ledger_standalone", "3"); return cfg; }), - test::jtx::features(featureFeeEscalation)}; + with_features(featureFeeEscalation)}; LoadFeeTrack const& feeTrack = env.app().getFeeTrack(); { @@ -2254,7 +2254,7 @@ public: // "b" (not in the ledger) is rDg53Haik2475DJx8bjMDSDPj4VX7htaMd. // "c" (phantom signer) is rPcNzota6B8YBokhYtcTNqQVCngtbnWfux. - test::jtx::Env env(*this, test::jtx::features(featureMultiSign)); + test::jtx::Env env(*this, test::jtx::with_features(featureMultiSign)); env.fund(test::jtx::XRP(100000), a, ed, g); env.close(); diff --git a/src/test/rpc/LedgerClosed_test.cpp b/src/test/rpc/LedgerClosed_test.cpp index f987658f4c..5bb1cdd5ac 100644 --- a/src/test/rpc/LedgerClosed_test.cpp +++ b/src/test/rpc/LedgerClosed_test.cpp @@ -30,7 +30,7 @@ public: void testMonitorRoot() { using namespace test::jtx; - Env env {*this}; + Env env {*this, no_features}; Account const alice {"alice"}; env.fund(XRP(10000), alice); diff --git a/src/test/rpc/LedgerData_test.cpp b/src/test/rpc/LedgerData_test.cpp index 384c3ec7f7..dbede8d2e5 100644 --- a/src/test/rpc/LedgerData_test.cpp +++ b/src/test/rpc/LedgerData_test.cpp @@ -268,7 +268,7 @@ public: using namespace test::jtx; using namespace std::chrono; Env env { *this, envconfig(validator, ""), - features(featureMultiSign, featureTickets, + with_features(featureMultiSign, featureTickets, featureEscrow, featurePayChan) }; Account const gw { "gateway" }; auto const USD = gw["USD"]; diff --git a/src/test/rpc/LedgerRPC_test.cpp b/src/test/rpc/LedgerRPC_test.cpp index 596e463e25..3828dc8e7f 100644 --- a/src/test/rpc/LedgerRPC_test.cpp +++ b/src/test/rpc/LedgerRPC_test.cpp @@ -286,7 +286,8 @@ class LedgerRPC_test : public beast::unit_test::suite void testLookupLedger() { using namespace test::jtx; - Env env { *this }; + Env env {*this, no_features}; // hashes requested below assume + //no amendments env.fund(XRP(10000), "alice"); env.close(); env.fund(XRP(10000), "bob"); @@ -476,7 +477,7 @@ class LedgerRPC_test : public beast::unit_test::suite .set("minimum_txn_in_ledger_standalone", "3"); return cfg; }), - features(featureFeeEscalation)}; + with_features(featureFeeEscalation)}; Json::Value jv; jv[jss::ledger_index] = "current"; diff --git a/src/test/rpc/LedgerRequestRPC_test.cpp b/src/test/rpc/LedgerRequestRPC_test.cpp index d6212fbc00..ae71d647e2 100644 --- a/src/test/rpc/LedgerRequestRPC_test.cpp +++ b/src/test/rpc/LedgerRequestRPC_test.cpp @@ -150,7 +150,8 @@ public: void testEvolution() { using namespace test::jtx; - Env env { *this }; + Env env {*this, no_features}; //the hashes being checked below assume + //no amendments Account const gw { "gateway" }; auto const USD = gw["USD"]; env.fund(XRP(100000), gw); diff --git a/src/test/rpc/NoRipple_test.cpp b/src/test/rpc/NoRipple_test.cpp index fe2dbe6074..cbd7067e12 100644 --- a/src/test/rpc/NoRipple_test.cpp +++ b/src/test/rpc/NoRipple_test.cpp @@ -72,7 +72,7 @@ public: testcase("Set noripple on a line with negative balance"); using namespace jtx; - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); auto const gw = Account("gateway"); auto const alice = Account("alice"); @@ -118,7 +118,7 @@ public: testcase("pairwise NoRipple"); using namespace jtx; - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -155,7 +155,7 @@ public: testcase("Set default ripple on an account and check new trustlines"); using namespace jtx; - Env env(*this, features(fs)); + Env env(*this, with_features(fs)); auto const gw = Account("gateway"); auto const alice = Account("alice"); diff --git a/src/test/rpc/OwnerInfo_test.cpp b/src/test/rpc/OwnerInfo_test.cpp new file mode 100644 index 0000000000..49b44178b0 --- /dev/null +++ b/src/test/rpc/OwnerInfo_test.cpp @@ -0,0 +1,214 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2017 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== +#include + +#include +#include +#include +#include + +namespace ripple { + +class OwnerInfo_test : public beast::unit_test::suite +{ + void + testBadInput () + { + testcase ("Bad input to owner_info"); + + using namespace test::jtx; + Env env {*this}; + + auto const alice = Account {"alice"}; + env.fund (XRP(10000), alice); + env.close (); + + { // missing account field + auto const result = + env.rpc ("json", "owner_info", "{}") [jss::result]; + BEAST_EXPECT (result[jss::error] == "invalidParams"); + BEAST_EXPECT (result[jss::error_message] == + "Missing field 'account'."); + } + + { // ask for empty account + Json::Value params; + params[jss::account] = ""; + auto const result = env.rpc ("json", "owner_info", + to_string(params)) [jss::result]; + if (BEAST_EXPECT ( + result.isMember(jss::accepted) && + result.isMember(jss::current))) + { + BEAST_EXPECT (result[jss::accepted][jss::error] == "badSeed"); + BEAST_EXPECT (result[jss::accepted][jss::error_message] == + "Disallowed seed."); + BEAST_EXPECT (result[jss::current][jss::error] == "badSeed"); + BEAST_EXPECT (result[jss::current][jss::error_message] == + "Disallowed seed."); + } + } + + { // ask for nonexistent account + // this seems like it should be an error, but current impl + // (deprecated) does not return an error, just empty fields. + Json::Value params; + params[jss::account] = Account{"bob"}.human(); + auto const result = env.rpc ("json", "owner_info", + to_string(params)) [jss::result]; + BEAST_EXPECT (result[jss::accepted] == Json::objectValue); + BEAST_EXPECT (result[jss::current] == Json::objectValue); + BEAST_EXPECT (result[jss::status] == "success"); + } + } + + void + testBasic () + { + testcase ("Basic request for owner_info"); + + using namespace test::jtx; + Env env {*this}; + + auto const alice = Account {"alice"}; + auto const gw = Account {"gateway"}; + env.fund (XRP(10000), alice, gw); + auto const USD = gw["USD"]; + auto const CNY = gw["CNY"]; + env(trust(alice, USD(1000))); + env(trust(alice, CNY(1000))); + env(offer(alice, USD(1), XRP(1000))); + env.close(); + + env(pay(gw, alice, USD(50))); + env(pay(gw, alice, CNY(50))); + env(offer(alice, CNY(2), XRP(1000))); + + Json::Value params; + params[jss::account] = alice.human(); + auto const result = env.rpc ("json", "owner_info", + to_string(params)) [jss::result]; + if (! BEAST_EXPECT ( + result.isMember(jss::accepted) && + result.isMember(jss::current))) + { + return; + } + + // accepted ledger entry + if (! BEAST_EXPECT (result[jss::accepted].isMember(jss::ripple_lines))) + return; + auto lines = result[jss::accepted][jss::ripple_lines]; + if (! BEAST_EXPECT (lines.isArray() && lines.size() == 2)) + return; + + BEAST_EXPECT ( + lines[0u][sfBalance.fieldName] == + (STAmount{Issue{to_currency("CNY"), noAccount()}, 0} + .value().getJson(0))); + BEAST_EXPECT ( + lines[0u][sfHighLimit.fieldName] == + alice["CNY"](1000).value().getJson(0)); + BEAST_EXPECT ( + lines[0u][sfLowLimit.fieldName] == + gw["CNY"](0).value().getJson(0)); + + BEAST_EXPECT ( + lines[1u][sfBalance.fieldName] == + (STAmount{Issue{to_currency("USD"), noAccount()}, 0} + .value().getJson(0))); + BEAST_EXPECT ( + lines[1u][sfHighLimit.fieldName] == + alice["USD"](1000).value().getJson(0)); + BEAST_EXPECT ( + lines[1u][sfLowLimit.fieldName] == + USD(0).value().getJson(0)); + + if (! BEAST_EXPECT (result[jss::accepted].isMember(jss::offers))) + return; + auto offers = result[jss::accepted][jss::offers]; + if (! BEAST_EXPECT (offers.isArray() && offers.size() == 1)) + return; + + BEAST_EXPECT ( + offers[0u][jss::Account] == alice.human()); + BEAST_EXPECT ( + offers[0u][sfTakerGets.fieldName] == XRP(1000).value().getJson(0)); + BEAST_EXPECT ( + offers[0u][sfTakerPays.fieldName] == USD(1).value().getJson(0)); + + + // current ledger entry + if (! BEAST_EXPECT (result[jss::current].isMember(jss::ripple_lines))) + return; + lines = result[jss::current][jss::ripple_lines]; + if (! BEAST_EXPECT (lines.isArray() && lines.size() == 2)) + return; + + BEAST_EXPECT ( + lines[0u][sfBalance.fieldName] == + (STAmount{Issue{to_currency("CNY"), noAccount()}, -50} + .value().getJson(0))); + BEAST_EXPECT ( + lines[0u][sfHighLimit.fieldName] == + alice["CNY"](1000).value().getJson(0)); + BEAST_EXPECT ( + lines[0u][sfLowLimit.fieldName] == + gw["CNY"](0).value().getJson(0)); + + BEAST_EXPECT ( + lines[1u][sfBalance.fieldName] == + (STAmount{Issue{to_currency("USD"), noAccount()}, -50} + .value().getJson(0))); + BEAST_EXPECT ( + lines[1u][sfHighLimit.fieldName] == + alice["USD"](1000).value().getJson(0)); + BEAST_EXPECT ( + lines[1u][sfLowLimit.fieldName] == + gw["USD"](0).value().getJson(0)); + + if (! BEAST_EXPECT (result[jss::current].isMember(jss::offers))) + return; + offers = result[jss::current][jss::offers]; + // 1 additional offer in current, (2 total) + if (! BEAST_EXPECT (offers.isArray() && offers.size() == 2)) + return; + + BEAST_EXPECT ( + offers[1u] == result[jss::accepted][jss::offers][0u]); + BEAST_EXPECT ( + offers[0u][jss::Account] == alice.human()); + BEAST_EXPECT ( + offers[0u][sfTakerGets.fieldName] == XRP(1000).value().getJson(0)); + BEAST_EXPECT ( + offers[0u][sfTakerPays.fieldName] == CNY(2).value().getJson(0)); + } + +public: + void run () + { + testBadInput (); + testBasic (); + } +}; + +BEAST_DEFINE_TESTSUITE(OwnerInfo,app,ripple); + +} // ripple + diff --git a/src/test/rpc/Subscribe_test.cpp b/src/test/rpc/Subscribe_test.cpp index 419258e941..a8102becac 100644 --- a/src/test/rpc/Subscribe_test.cpp +++ b/src/test/rpc/Subscribe_test.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -382,6 +383,259 @@ public: BEAST_EXPECT(jv[jss::status] == "success"); } + void + testSubByUrl() + { + using namespace jtx; + testcase("Subscribe by url"); + Env env {*this}; + + Json::Value jv; + jv[jss::url] = "http://localhost/events"; + jv[jss::url_username] = "admin"; + jv[jss::url_password] = "password"; + jv[jss::streams] = Json::arrayValue; + jv[jss::streams][0u] = "validations"; + auto jr = env.rpc("json", "subscribe", to_string(jv)) [jss::result]; + BEAST_EXPECT(jr[jss::status] == "success"); + + jv[jss::streams][0u] = "ledger"; + jr = env.rpc("json", "subscribe", to_string(jv)) [jss::result]; + BEAST_EXPECT(jr[jss::status] == "success"); + + jr = env.rpc("json", "unsubscribe", to_string(jv)) [jss::result]; + BEAST_EXPECT(jr[jss::status] == "success"); + + jv[jss::streams][0u] = "validations"; + jr = env.rpc("json", "unsubscribe", to_string(jv)) [jss::result]; + BEAST_EXPECT(jr[jss::status] == "success"); + } + + void + testSubErrors(bool subscribe) + { + using namespace jtx; + auto const method = subscribe ? "subscribe" : "unsubscribe"; + testcase << "Error cases for " << method; + + Env env {*this}; + auto wsc = makeWSClient(env.app().config()); + + { + auto jr = env.rpc("json", method, "{}") [jss::result]; + BEAST_EXPECT(jr[jss::error] == "invalidParams"); + BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters."); + } + + { + Json::Value jv; + jv[jss::url] = "not-a-url"; + jv[jss::username] = "admin"; + jv[jss::password] = "password"; + auto jr = env.rpc("json", method, to_string(jv)) [jss::result]; + if (subscribe) + { + BEAST_EXPECT(jr[jss::error] == "invalidParams"); + BEAST_EXPECT(jr[jss::error_message] == "Failed to parse url."); + } + // else TODO: why isn't this an error for unsubscribe ? + // (findRpcSub returns null) + } + + { + Json::Value jv; + jv[jss::url] = "ftp://scheme.not.supported.tld"; + auto jr = env.rpc("json", method, to_string(jv)) [jss::result]; + if (subscribe) + { + BEAST_EXPECT(jr[jss::error] == "invalidParams"); + BEAST_EXPECT(jr[jss::error_message] == "Only http and https is supported."); + } + } + + { + Env env_nonadmin {*this, no_admin(envconfig(port_increment, 2))}; + Json::Value jv; + jv[jss::url] = "no-url"; + auto jr = env_nonadmin.rpc("json", method, to_string(jv)) [jss::result]; + BEAST_EXPECT(jr[jss::error] == "noPermission"); + BEAST_EXPECT(jr[jss::error_message] == "You don't have permission for this command."); + } + + for (auto const& f : {jss::accounts_proposed, jss::accounts}) + { + { + Json::Value jv; + jv[f] = ""; + auto jr = wsc->invoke(method, jv) [jss::result]; + BEAST_EXPECT(jr[jss::error] == "invalidParams"); + BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters."); + } + + { + Json::Value jv; + jv[f] = Json::arrayValue; + auto jr = wsc->invoke(method, jv) [jss::result]; + BEAST_EXPECT(jr[jss::error] == "actMalformed"); + BEAST_EXPECT(jr[jss::error_message] == "Account malformed."); + } + } + + { + Json::Value jv; + jv[jss::books] = ""; + auto jr = wsc->invoke(method, jv) [jss::result]; + BEAST_EXPECT(jr[jss::error] == "invalidParams"); + BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters."); + } + + { + Json::Value jv; + jv[jss::books] = Json::arrayValue; + jv[jss::books][0u] = 1; + auto jr = wsc->invoke(method, jv) [jss::result]; + BEAST_EXPECT(jr[jss::error] == "invalidParams"); + BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters."); + } + + { + Json::Value jv; + jv[jss::books] = Json::arrayValue; + jv[jss::books][0u] = Json::objectValue; + jv[jss::books][0u][jss::taker_gets] = Json::objectValue; + jv[jss::books][0u][jss::taker_pays] = Json::objectValue; + auto jr = wsc->invoke(method, jv) [jss::result]; + BEAST_EXPECT(jr[jss::error] == "srcCurMalformed"); + BEAST_EXPECT(jr[jss::error_message] == "Source currency is malformed."); + } + + { + Json::Value jv; + jv[jss::books] = Json::arrayValue; + jv[jss::books][0u] = Json::objectValue; + jv[jss::books][0u][jss::taker_gets] = Json::objectValue; + jv[jss::books][0u][jss::taker_pays] = Json::objectValue; + jv[jss::books][0u][jss::taker_pays][jss::currency] = "ZZZZ"; + auto jr = wsc->invoke(method, jv) [jss::result]; + BEAST_EXPECT(jr[jss::error] == "srcCurMalformed"); + BEAST_EXPECT(jr[jss::error_message] == "Source currency is malformed."); + } + + { + Json::Value jv; + jv[jss::books] = Json::arrayValue; + jv[jss::books][0u] = Json::objectValue; + jv[jss::books][0u][jss::taker_gets] = Json::objectValue; + jv[jss::books][0u][jss::taker_pays] = Json::objectValue; + jv[jss::books][0u][jss::taker_pays][jss::currency] = "USD"; + jv[jss::books][0u][jss::taker_pays][jss::issuer] = 1; + auto jr = wsc->invoke(method, jv) [jss::result]; + BEAST_EXPECT(jr[jss::error] == "srcIsrMalformed"); + BEAST_EXPECT(jr[jss::error_message] == "Source issuer is malformed."); + } + + { + Json::Value jv; + jv[jss::books] = Json::arrayValue; + jv[jss::books][0u] = Json::objectValue; + jv[jss::books][0u][jss::taker_gets] = Json::objectValue; + jv[jss::books][0u][jss::taker_pays] = Json::objectValue; + jv[jss::books][0u][jss::taker_pays][jss::currency] = "USD"; + jv[jss::books][0u][jss::taker_pays][jss::issuer] = Account{"gateway"}.human() + "DEAD"; + auto jr = wsc->invoke(method, jv) [jss::result]; + BEAST_EXPECT(jr[jss::error] == "srcIsrMalformed"); + BEAST_EXPECT(jr[jss::error_message] == "Source issuer is malformed."); + } + + { + Json::Value jv; + jv[jss::books] = Json::arrayValue; + jv[jss::books][0u] = Json::objectValue; + jv[jss::books][0u][jss::taker_pays] = Account{"gateway"}["USD"](1).value().getJson(1); + jv[jss::books][0u][jss::taker_gets] = Json::objectValue; + auto jr = wsc->invoke(method, jv) [jss::result]; + // NOTE: this error is slightly incongruous with the + // equivalent source currency error + BEAST_EXPECT(jr[jss::error] == "dstAmtMalformed"); + BEAST_EXPECT(jr[jss::error_message] == "Destination amount/currency/issuer is malformed."); + } + + { + Json::Value jv; + jv[jss::books] = Json::arrayValue; + jv[jss::books][0u] = Json::objectValue; + jv[jss::books][0u][jss::taker_pays] = Account{"gateway"}["USD"](1).value().getJson(1); + jv[jss::books][0u][jss::taker_gets][jss::currency] = "ZZZZ"; + auto jr = wsc->invoke(method, jv) [jss::result]; + // NOTE: this error is slightly incongruous with the + // equivalent source currency error + BEAST_EXPECT(jr[jss::error] == "dstAmtMalformed"); + BEAST_EXPECT(jr[jss::error_message] == "Destination amount/currency/issuer is malformed."); + } + + { + Json::Value jv; + jv[jss::books] = Json::arrayValue; + jv[jss::books][0u] = Json::objectValue; + jv[jss::books][0u][jss::taker_pays] = Account{"gateway"}["USD"](1).value().getJson(1); + jv[jss::books][0u][jss::taker_gets][jss::currency] = "USD"; + jv[jss::books][0u][jss::taker_gets][jss::issuer] = 1; + auto jr = wsc->invoke(method, jv) [jss::result]; + BEAST_EXPECT(jr[jss::error] == "dstIsrMalformed"); + BEAST_EXPECT(jr[jss::error_message] == "Destination issuer is malformed."); + } + + { + Json::Value jv; + jv[jss::books] = Json::arrayValue; + jv[jss::books][0u] = Json::objectValue; + jv[jss::books][0u][jss::taker_pays] = Account{"gateway"}["USD"](1).value().getJson(1); + jv[jss::books][0u][jss::taker_gets][jss::currency] = "USD"; + jv[jss::books][0u][jss::taker_gets][jss::issuer] = Account{"gateway"}.human() + "DEAD"; + auto jr = wsc->invoke(method, jv) [jss::result]; + BEAST_EXPECT(jr[jss::error] == "dstIsrMalformed"); + BEAST_EXPECT(jr[jss::error_message] == "Destination issuer is malformed."); + } + + { + Json::Value jv; + jv[jss::books] = Json::arrayValue; + jv[jss::books][0u] = Json::objectValue; + jv[jss::books][0u][jss::taker_pays] = Account{"gateway"}["USD"](1).value().getJson(1); + jv[jss::books][0u][jss::taker_gets] = Account{"gateway"}["USD"](1).value().getJson(1); + auto jr = wsc->invoke(method, jv) [jss::result]; + BEAST_EXPECT(jr[jss::error] == "badMarket"); + BEAST_EXPECT(jr[jss::error_message] == "No such market."); + } + + { + Json::Value jv; + jv[jss::streams] = ""; + auto jr = wsc->invoke(method, jv) [jss::result]; + BEAST_EXPECT(jr[jss::error] == "invalidParams"); + BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters."); + } + + { + Json::Value jv; + jv[jss::streams] = Json::arrayValue; + jv[jss::streams][0u] = 1; + auto jr = wsc->invoke(method, jv) [jss::result]; + BEAST_EXPECT(jr[jss::error] == "malformedStream"); + BEAST_EXPECT(jr[jss::error_message] == "Stream malformed."); + } + + { + Json::Value jv; + jv[jss::streams] = Json::arrayValue; + jv[jss::streams][0u] = "not_a_stream"; + auto jr = wsc->invoke(method, jv) [jss::result]; + BEAST_EXPECT(jr[jss::error] == "malformedStream"); + BEAST_EXPECT(jr[jss::error_message] == "Stream malformed."); + } + + } + void run() override { testServer(); @@ -389,6 +643,9 @@ public: testTransactions(); testManifests(); testValidations(); + testSubErrors(true); + testSubErrors(false); + testSubByUrl(); } }; diff --git a/src/test/server/ServerStatus_test.cpp b/src/test/server/ServerStatus_test.cpp index 94d2385716..2386575573 100644 --- a/src/test/server/ServerStatus_test.cpp +++ b/src/test/server/ServerStatus_test.cpp @@ -20,18 +20,21 @@ #include #include #include +#include #include #include #include #include -#include -#include +#include +#include #include #include #include +#include #include #include #include +#include namespace ripple { namespace test { @@ -39,6 +42,8 @@ namespace test { class ServerStatus_test : public beast::unit_test::suite, public beast::test::enable_yield_to { + class myFields : public beast::http::fields {}; + auto makeConfig( std::string const& proto, bool admin = true, @@ -86,44 +91,48 @@ class ServerStatus_test : using namespace beast::http; request req; - req.url = "/"; + req.target("/"); req.version = 11; - req.fields.insert("Host", host + ":" + to_string(port)); - req.fields.insert("User-Agent", "test"); - req.method = "GET"; - req.fields.insert("Upgrade", "websocket"); + req.insert("Host", host + ":" + std::to_string(port)); + req.insert("User-Agent", "test"); + req.method(beast::http::verb::get); + req.insert("Upgrade", "websocket"); beast::websocket::detail::maskgen maskgen; - std::string key = beast::websocket::detail::make_sec_ws_key(maskgen); - req.fields.insert("Sec-WebSocket-Key", key); - req.fields.insert("Sec-WebSocket-Version", "13"); - prepare(req, connection::upgrade); + beast::websocket::detail::sec_ws_key_type key; + beast::websocket::detail::make_sec_ws_key(key, maskgen); + req.insert("Sec-WebSocket-Key", key); + req.insert("Sec-WebSocket-Version", "13"); + req.insert(beast::http::field::connection, "upgrade"); return req; } auto makeHTTPRequest( std::string const& host, uint16_t port, - std::string const& body) + std::string const& body, + myFields const& fields) { using namespace boost::asio; using namespace beast::http; request req; - req.url = "/"; + req.target("/"); req.version = 11; - req.fields.insert("Host", host + ":" + to_string(port)); - req.fields.insert("User-Agent", "test"); + for(auto const& f : fields) + req.insert(f.name(), f.value()); + req.insert("Host", host + ":" + std::to_string(port)); + req.insert("User-Agent", "test"); if(body.empty()) { - req.method = "GET"; + req.method(beast::http::verb::get); } else { - req.method = "POST"; - req.fields.insert("Content-Type", "application/json; charset=UTF-8"); + req.method(beast::http::verb::post); + req.insert("Content-Type", "application/json; charset=UTF-8"); req.body = body; } - prepare(req); + req.prepare_payload(); return req; } @@ -131,7 +140,7 @@ class ServerStatus_test : void doRequest( boost::asio::yield_context& yield, - beast::http::request const& req, + beast::http::request&& req, std::string const& host, uint16_t port, bool secure, @@ -142,11 +151,11 @@ class ServerStatus_test : using namespace beast::http; io_service& ios = get_io_service(); ip::tcp::resolver r{ios}; - beast::streambuf sb; + beast::multi_buffer sb; auto it = r.async_resolve( - ip::tcp::resolver::query{host, to_string(port)}, yield[ec]); + ip::tcp::resolver::query{host, std::to_string(port)}, yield[ec]); if(ec) return; @@ -161,7 +170,7 @@ class ServerStatus_test : ss.async_handshake(ssl::stream_base::client, yield[ec]); if(ec) return; - async_write(ss, req, yield[ec]); + beast::http::async_write(ss, req, yield[ec]); if(ec) return; async_read(ss, sb, resp, yield[ec]); @@ -174,7 +183,7 @@ class ServerStatus_test : async_connect(sock, it, yield[ec]); if(ec) return; - async_write(sock, req, yield[ec]); + beast::http::async_write(sock, req, yield[ec]); if(ec) return; async_read(sock, sb, resp, yield[ec]); @@ -195,10 +204,16 @@ class ServerStatus_test : { auto const port = env.app().config()["port_ws"]. get("port"); - auto const ip = env.app().config()["port_ws"]. + auto ip = env.app().config()["port_ws"]. get("ip"); doRequest( - yield, makeWSUpgrade(*ip, *port), *ip, *port, secure, resp, ec); + yield, + makeWSUpgrade(*ip, *port), + *ip, + *port, + secure, + resp, + ec); return; } @@ -209,7 +224,8 @@ class ServerStatus_test : bool secure, beast::http::response& resp, boost::system::error_code& ec, - std::string const& body = "") + std::string const& body = "", + myFields const& fields = {}) { auto const port = env.app().config()["port_rpc"]. get("port"); @@ -217,7 +233,7 @@ class ServerStatus_test : get("ip"); doRequest( yield, - makeHTTPRequest(*ip, *port, body), + makeHTTPRequest(*ip, *port, body, fields), *ip, *port, secure, @@ -269,6 +285,91 @@ class ServerStatus_test : // ------------ // Test Cases // ------------ + + void + testAdminRequest(std::string const& proto, bool admin, bool credentials) + { + testcase << "Admin request over " << proto << + ", config " << (admin ? "enabled" : "disabled") << + ", credentials " << (credentials ? "" : "not ") << "set"; + using namespace jtx; + Env env {*this, makeConfig(proto, admin, credentials)}; + + Json::Value jrr; + auto const proto_ws = boost::starts_with(proto, "w"); + + // the set of checks we do are different depending + // on how the admin config options are set + + if(admin && credentials) + { + auto const user = env.app().config() + [proto_ws ? "port_ws" : "port_rpc"]. + get("admin_user"); + + auto const password = env.app().config() + [proto_ws ? "port_ws" : "port_rpc"]. + get("admin_password"); + + //1 - FAILS with wrong pass + jrr = makeAdminRequest(env, proto, *user, *password + "_") + [jss::result]; + BEAST_EXPECT(jrr["error"] == proto_ws ? "forbidden" : "noPermission"); + BEAST_EXPECT(jrr["error_message"] == + proto_ws ? + "Bad credentials." : + "You don't have permission for this command."); + + //2 - FAILS with password in an object + jrr = makeAdminRequest(env, proto, *user, *password, true)[jss::result]; + BEAST_EXPECT(jrr["error"] == proto_ws ? "forbidden" : "noPermission"); + BEAST_EXPECT(jrr["error_message"] == + proto_ws ? + "Bad credentials." : + "You don't have permission for this command."); + + //3 - FAILS with wrong user + jrr = makeAdminRequest(env, proto, *user + "_", *password)[jss::result]; + BEAST_EXPECT(jrr["error"] == proto_ws ? "forbidden" : "noPermission"); + BEAST_EXPECT(jrr["error_message"] == + proto_ws ? + "Bad credentials." : + "You don't have permission for this command."); + + //4 - FAILS no credentials + jrr = makeAdminRequest(env, proto, "", "")[jss::result]; + BEAST_EXPECT(jrr["error"] == proto_ws ? "forbidden" : "noPermission"); + BEAST_EXPECT(jrr["error_message"] == + proto_ws ? + "Bad credentials." : + "You don't have permission for this command."); + + //5 - SUCCEEDS with proper credentials + jrr = makeAdminRequest(env, proto, *user, *password)[jss::result]; + BEAST_EXPECT(jrr["status"] == "success"); + } + else if(admin) + { + //1 - SUCCEEDS with proper credentials + jrr = makeAdminRequest(env, proto, "u", "p")[jss::result]; + BEAST_EXPECT(jrr["status"] == "success"); + + //2 - SUCCEEDS without proper credentials + jrr = makeAdminRequest(env, proto, "", "")[jss::result]; + BEAST_EXPECT(jrr["status"] == "success"); + } + else + { + //1 - FAILS - admin disabled + jrr = makeAdminRequest(env, proto, "", "")[jss::result]; + BEAST_EXPECT(jrr["error"] == proto_ws ? "forbidden" : "noPermission"); + BEAST_EXPECT(jrr["error_message"] == + proto_ws ? + "Bad credentials." : + "You don't have permission for this command."); + } + } + void testWSClientToHttpServer(boost::asio::yield_context& yield) { @@ -287,7 +388,7 @@ class ServerStatus_test : doWSRequest(env, yield, false, resp, ec); if(! BEAST_EXPECTS(! ec, ec.message())) return; - BEAST_EXPECT(resp.status == 401); + BEAST_EXPECT(resp.result() == beast::http::status::unauthorized); } //secure request @@ -297,7 +398,7 @@ class ServerStatus_test : doWSRequest(env, yield, true, resp, ec); if(! BEAST_EXPECTS(! ec, ec.message())) return; - BEAST_EXPECT(resp.status == 401); + BEAST_EXPECT(resp.result() == beast::http::status::unauthorized); } } @@ -320,7 +421,7 @@ class ServerStatus_test : doHTTPRequest(env, yield, false, resp, ec); if(! BEAST_EXPECTS(! ec, ec.message())) return; - BEAST_EXPECT(resp.status == 200); + BEAST_EXPECT(resp.result() == beast::http::status::ok); } //secure request @@ -330,7 +431,7 @@ class ServerStatus_test : doHTTPRequest(env, yield, true, resp, ec); if(! BEAST_EXPECTS(! ec, ec.message())) return; - BEAST_EXPECT(resp.status == 200); + BEAST_EXPECT(resp.result() == beast::http::status::ok); } }; @@ -362,11 +463,11 @@ class ServerStatus_test : io_service& ios = get_io_service(); ip::tcp::resolver r{ios}; - beast::streambuf sb; + beast::multi_buffer sb; auto it = r.async_resolve( - ip::tcp::resolver::query{*ip, to_string(*port)}, yield[ec]); + ip::tcp::resolver::query{*ip, std::to_string(*port)}, yield[ec]); if(! BEAST_EXPECTS(! ec, ec.message())) return; @@ -422,117 +523,483 @@ class ServerStatus_test : } void - testAdminRequest(std::string const& proto, bool admin, bool credentials) + testAuth(bool secure, boost::asio::yield_context& yield) { - testcase << "Admin request over " << proto << - ", config " << (admin ? "enabled" : "disabled") << - ", credentials " << (credentials ? "" : "not ") << "set"; + testcase << "Server with authorization, " << + (secure ? "secure" : "non-secure"); + + using namespace test::jtx; + Env env {*this, envconfig([secure](std::unique_ptr cfg) { + (*cfg)["port_rpc"].set("user","me"); + (*cfg)["port_rpc"].set("password","secret"); + (*cfg)["port_rpc"].set("protocol", secure ? "https" : "http"); + if (secure) + (*cfg)["port_ws"].set("protocol","http,ws"); + return cfg; + })}; + + Json::Value jr; + jr[jss::method] = "server_info"; + beast::http::response resp; + boost::system::error_code ec; + doHTTPRequest(env, yield, secure, resp, ec, to_string(jr)); + BEAST_EXPECT(resp.result() == beast::http::status::forbidden); + + myFields auth; + auth.insert("Authorization", ""); + doHTTPRequest(env, yield, secure, resp, ec, to_string(jr), auth); + BEAST_EXPECT(resp.result() == beast::http::status::forbidden); + + auth.set("Authorization", "Basic NOT-VALID"); + doHTTPRequest(env, yield, secure, resp, ec, to_string(jr), auth); + BEAST_EXPECT(resp.result() == beast::http::status::forbidden); + + auth.set("Authorization", "Basic " + beast::detail::base64_encode("me:badpass")); + doHTTPRequest(env, yield, secure, resp, ec, to_string(jr), auth); + BEAST_EXPECT(resp.result() == beast::http::status::forbidden); + + auto const user = env.app().config().section("port_rpc"). + get("user").value(); + auto const pass = env.app().config().section("port_rpc"). + get("password").value(); + + // try with the correct user/pass, but not encoded + auth.set("Authorization", "Basic " + user + ":" + pass); + doHTTPRequest(env, yield, secure, resp, ec, to_string(jr), auth); + BEAST_EXPECT(resp.result() == beast::http::status::forbidden); + + // finally if we use the correct user/pass encoded, we should get a 200 + auth.set("Authorization", "Basic " + + beast::detail::base64_encode(user + ":" + pass)); + doHTTPRequest(env, yield, secure, resp, ec, to_string(jr), auth); + BEAST_EXPECT(resp.result() == beast::http::status::ok); + BEAST_EXPECT(! resp.body.empty()); + } + + void + testLimit(boost::asio::yield_context& yield, int limit) + { + testcase << "Server with connection limit of " << limit; + + using namespace test::jtx; + using namespace boost::asio; + using namespace beast::http; + Env env {*this, envconfig([&](std::unique_ptr cfg) { + (*cfg)["port_rpc"].set("limit", to_string(limit)); + return cfg; + })}; + + + auto const port = env.app().config()["port_rpc"]. + get("port").value(); + auto const ip = env.app().config()["port_rpc"]. + get("ip").value(); + + boost::system::error_code ec; + io_service& ios = get_io_service(); + ip::tcp::resolver r{ios}; + + Json::Value jr; + jr[jss::method] = "server_info"; + + auto it = + r.async_resolve( + ip::tcp::resolver::query{ip, to_string(port)}, yield[ec]); + BEAST_EXPECT(! ec); + + std::vector> clients; + int connectionCount {1}; //starts at 1 because the Env already has one + //for JSONRPCCLient + + // for nonzero limits, go one past the limit, although failures happen + // at the limit, so this really leads to the last two clients failing. + // for zero limit, pick an arbitrary nonzero number of clients - all should + // connect fine. + + int testTo = (limit == 0) ? 50 : limit + 1; + while (connectionCount < testTo) + { + clients.emplace_back( + std::make_pair(ip::tcp::socket {ios}, beast::multi_buffer{})); + async_connect(clients.back().first, it, yield[ec]); + BEAST_EXPECT(! ec); + auto req = makeHTTPRequest(ip, port, to_string(jr), {}); + async_write( + clients.back().first, + req, + yield[ec]); + BEAST_EXPECT(! ec); + ++connectionCount; + } + + int readCount = 0; + for (auto& c : clients) + { + beast::http::response resp; + async_read(c.first, c.second, resp, yield[ec]); + ++readCount; + // expect the reads to fail for the clients that connected at or + // above the limit. If limit is 0, all reads should succeed + BEAST_EXPECT((limit == 0 || readCount < limit-1) ? (! ec) : ec); + } + } + + void + testWSHandoff(boost::asio::yield_context& yield) + { + testcase ("Connection with WS handoff"); + + using namespace test::jtx; + Env env {*this, envconfig([](std::unique_ptr cfg) { + (*cfg)["port_ws"].set("protocol","wss"); + return cfg; + })}; + + auto const port = env.app().config()["port_ws"]. + get("port").value(); + auto const ip = env.app().config()["port_ws"]. + get("ip").value(); + beast::http::response resp; + boost::system::error_code ec; + doRequest( + yield, makeWSUpgrade(ip, port), ip, port, true, resp, ec); + BEAST_EXPECT(resp.result() == beast::http::status::switching_protocols); + BEAST_EXPECT(resp.find("Upgrade") != resp.end() && + resp["Upgrade"] == "websocket"); + BEAST_EXPECT(resp.find("Connection") != resp.end() && + resp["Connection"] == "upgrade"); + } + + void + testNoRPC(boost::asio::yield_context& yield) + { + testcase ("Connection to port with no RPC enabled"); + + using namespace test::jtx; + Env env {*this}; + + auto const port = env.app().config()["port_ws"]. + get("port").value(); + auto const ip = env.app().config()["port_ws"]. + get("ip").value(); + beast::http::response resp; + boost::system::error_code ec; + // body content is required here to avoid being + // detected as a status request + doRequest(yield, + makeHTTPRequest(ip, port, "foo", {}), ip, port, false, resp, ec); + BEAST_EXPECT(resp.result() == beast::http::status::forbidden); + BEAST_EXPECT(resp.body == "Forbidden\r\n"); + } + + void + testWSRequests(boost::asio::yield_context& yield) + { + testcase ("WS client sends assorted input"); + + using namespace test::jtx; + using namespace boost::asio; + using namespace beast::http; + Env env {*this}; + + auto const port = env.app().config()["port_ws"]. + get("port").value(); + auto const ip = env.app().config()["port_ws"]. + get("ip").value(); + boost::system::error_code ec; + + io_service& ios = get_io_service(); + ip::tcp::resolver r{ios}; + + auto it = + r.async_resolve( + ip::tcp::resolver::query{ip, to_string(port)}, yield[ec]); + if(! BEAST_EXPECT(! ec)) + return; + + ip::tcp::socket sock{ios}; + async_connect(sock, it, yield[ec]); + if(! BEAST_EXPECT(! ec)) + return; + + beast::websocket::stream ws{sock}; + ws.handshake(ip + ":" + to_string(port), "/"); + + // helper lambda, used below + auto sendAndParse = [&](std::string const& req) -> Json::Value + { + ws.async_write_frame(true, buffer(req), yield[ec]); + if(! BEAST_EXPECT(! ec)) + return Json::objectValue; + + beast::multi_buffer sb; + ws.async_read(sb, yield[ec]); + if(! BEAST_EXPECT(! ec)) + return Json::objectValue; + + Json::Value resp; + Json::Reader jr; + if(! BEAST_EXPECT(jr.parse( + boost::lexical_cast( + beast::buffers(sb.data())), resp))) + return Json::objectValue; + sb.consume(sb.size()); + return resp; + }; + + { // send invalid json + auto resp = sendAndParse("NOT JSON"); + BEAST_EXPECT(resp.isMember(jss::error) && + resp[jss::error] == "jsonInvalid"); + BEAST_EXPECT(! resp.isMember(jss::status)); + } + + { // send incorrect json (method and command fields differ) + Json::Value jv; + jv[jss::command] = "foo"; + jv[jss::method] = "bar"; + auto resp = sendAndParse(to_string(jv)); + BEAST_EXPECT(resp.isMember(jss::error) && + resp[jss::error] == "missingCommand"); + BEAST_EXPECT(resp.isMember(jss::status) && + resp[jss::status] == "error"); + } + + { // send a ping (not an error) + Json::Value jv; + jv[jss::command] = "ping"; + auto resp = sendAndParse(to_string(jv)); + BEAST_EXPECT(resp.isMember(jss::status) && + resp[jss::status] == "success"); + BEAST_EXPECT(resp.isMember(jss::result) && + resp[jss::result].isMember(jss::role) && + resp[jss::result][jss::role] == "admin"); + } + } + + void + testAmendmentBlock(boost::asio::yield_context& yield) + { + testcase("Status request over WS and RPC with/without Amendment Block"); using namespace jtx; - Env env {*this, makeConfig(proto, admin, credentials)}; + using namespace boost::asio; + using namespace beast::http; + Env env {*this, validator( envconfig([](std::unique_ptr cfg) + { + cfg->section("port_rpc").set("protocol", "http,https"); + return cfg; + }), "")}; - auto const user = env.app().config() - [boost::starts_with(proto, "h") ? "port_rpc" : "port_ws"]. - get("admin_user"); + env.close(); - auto const password = env.app().config() - [boost::starts_with(proto, "h") ? "port_rpc" : "port_ws"]. - get("admin_password"); + // advance the ledger so that server status + // sees a published ledger -- without this, we get a status + // failure message about no published ledgers + env.app().getLedgerMaster().tryAdvance(); - Json::Value jrr; + // make an RPC server info request and look for + // amendment_blocked status + auto si = env.rpc("server_info") [jss::result]; + BEAST_EXPECT(! si[jss::info].isMember(jss::amendment_blocked)); + BEAST_EXPECT( + env.app().getOPs().getConsensusInfo()["validating"] == true); - // the set of checks we do are different depending - // on how the admin config options are set + auto const port_ws = env.app().config()["port_ws"]. + get("port"); + auto const ip_ws = env.app().config()["port_ws"]. + get("ip"); - if(admin && credentials) + + boost::system::error_code ec; + response resp; + + doRequest( + yield, + makeHTTPRequest(*ip_ws, *port_ws, "", {}), + *ip_ws, + *port_ws, + false, + resp, + ec); + + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + BEAST_EXPECT(resp.result() == beast::http::status::ok); + BEAST_EXPECT( + resp.body.find("connectivity is working.") != std::string::npos); + + // mark the Network as Amendment Blocked, but still won't fail until + // ELB is enabled (next step) + env.app().getOPs().setAmendmentBlocked (); + env.app().getOPs().beginConsensus(env.closed()->info().hash); + + // consensus now sees validation disabled + BEAST_EXPECT( + env.app().getOPs().getConsensusInfo()["validating"] == false); + + // RPC request server_info again, now AB should be returned + si = env.rpc("server_info") [jss::result]; + BEAST_EXPECT( + si[jss::info].isMember(jss::amendment_blocked) && + si[jss::info][jss::amendment_blocked] == true); + + // but status does not indicate because it still relies on ELB + // being enabled + doRequest( + yield, + makeHTTPRequest(*ip_ws, *port_ws, "", {}), + *ip_ws, + *port_ws, + false, + resp, + ec); + + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + BEAST_EXPECT(resp.result() == beast::http::status::ok); + BEAST_EXPECT( + resp.body.find("connectivity is working.") != std::string::npos); + + env.app().config().ELB_SUPPORT = true; + + doRequest( + yield, + makeHTTPRequest(*ip_ws, *port_ws, "", {}), + *ip_ws, + *port_ws, + false, + resp, + ec); + + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + BEAST_EXPECT(resp.result() == beast::http::status::internal_server_error); + BEAST_EXPECT( + resp.body.find("cannot accept clients:") != std::string::npos); + BEAST_EXPECT( + resp.body.find("Server version too old") != std::string::npos); + } + + void + testRPCRequests(boost::asio::yield_context& yield) + { + testcase ("RPC client sends assorted input"); + + using namespace test::jtx; + Env env {*this}; + + boost::system::error_code ec; { - //1 - FAILS with wrong pass - jrr = makeAdminRequest(env, proto, *user, *password + "_")[jss::result]; - BEAST_EXPECT(jrr["error"] == - boost::starts_with(proto, "h") ? "noPermission" : "forbidden"); - BEAST_EXPECT(jrr["error_message"] == - boost::starts_with(proto, "h") ? - "You don't have permission for this command." : - "Bad credentials."); - - //2 - FAILS with password in an object - jrr = makeAdminRequest(env, proto, *user, *password, true)[jss::result]; - BEAST_EXPECT(jrr["error"] == - boost::starts_with(proto, "h") ? "noPermission" : "forbidden"); - BEAST_EXPECT(jrr["error_message"] == - boost::starts_with(proto, "h") ? - "You don't have permission for this command." : - "Bad credentials."); - - //3 - FAILS with wrong user - jrr = makeAdminRequest(env, proto, *user + "_", *password)[jss::result]; - BEAST_EXPECT(jrr["error"] == - boost::starts_with(proto, "h") ? "noPermission" : "forbidden"); - BEAST_EXPECT(jrr["error_message"] == - boost::starts_with(proto, "h") ? - "You don't have permission for this command." : - "Bad credentials."); - - //4 - FAILS no credentials - jrr = makeAdminRequest(env, proto, "", "")[jss::result]; - BEAST_EXPECT(jrr["error"] == - boost::starts_with(proto, "h") ? "noPermission" : "forbidden"); - BEAST_EXPECT(jrr["error_message"] == - boost::starts_with(proto, "h") ? - "You don't have permission for this command." : - "Bad credentials."); - - //5 - SUCCEEDS with proper credentials - jrr = makeAdminRequest(env, proto, *user, *password)[jss::result]; - BEAST_EXPECT(jrr["status"] == "success"); + beast::http::response resp; + doHTTPRequest(env, yield, false, resp, ec, "{}"); + BEAST_EXPECT(resp.result() == beast::http::status::bad_request); + BEAST_EXPECT(resp.body == "Unable to parse request\r\n"); } - else if(admin) + + Json::Value jv; { - //1 - SUCCEEDS with proper credentials - jrr = makeAdminRequest(env, proto, "u", "p")[jss::result]; - BEAST_EXPECT(jrr["status"] == "success"); + beast::http::response resp; + jv[jss::method] = Json::nullValue; + doHTTPRequest(env, yield, false, resp, ec, to_string(jv)); + BEAST_EXPECT(resp.result() == beast::http::status::bad_request); + BEAST_EXPECT(resp.body == "Null method\r\n"); + } - //2 - SUCCEEDS without proper credentials - jrr = makeAdminRequest(env, proto, "", "")[jss::result]; - BEAST_EXPECT(jrr["status"] == "success"); - } - else { - //1 - FAILS - admin disabled - jrr = makeAdminRequest(env, proto, "", "")[jss::result]; - BEAST_EXPECT(jrr["error"] == - boost::starts_with(proto, "h") ? "noPermission" : "forbidden"); - BEAST_EXPECT(jrr["error_message"] == - boost::starts_with(proto, "h") ? - "You don't have permission for this command." : - "Bad credentials."); + beast::http::response resp; + jv[jss::method] = 1; + doHTTPRequest(env, yield, false, resp, ec, to_string(jv)); + BEAST_EXPECT(resp.result() == beast::http::status::bad_request); + BEAST_EXPECT(resp.body == "method is not string\r\n"); } + + { + beast::http::response resp; + jv[jss::method] = ""; + doHTTPRequest(env, yield, false, resp, ec, to_string(jv)); + BEAST_EXPECT(resp.result() == beast::http::status::bad_request); + BEAST_EXPECT(resp.body == "method is empty\r\n"); + } + + { + beast::http::response resp; + jv[jss::method] = "some_method"; + jv[jss::params] = "params"; + doHTTPRequest(env, yield, false, resp, ec, to_string(jv)); + BEAST_EXPECT(resp.result() == beast::http::status::bad_request); + BEAST_EXPECT(resp.body == "params unparseable\r\n"); + } + + { + beast::http::response resp; + jv[jss::params] = Json::arrayValue; + jv[jss::params][0u] = "not an object"; + doHTTPRequest(env, yield, false, resp, ec, to_string(jv)); + BEAST_EXPECT(resp.result() == beast::http::status::bad_request); + BEAST_EXPECT(resp.body == "params unparseable\r\n"); + } + } + + void + testStatusNotOkay(boost::asio::yield_context& yield) + { + testcase ("Server status not okay"); + + using namespace test::jtx; + Env env {*this, envconfig([](std::unique_ptr cfg) { + cfg->ELB_SUPPORT = true; + return cfg; + })}; + + //raise the fee so that the server is considered overloaded + env.app().getFeeTrack().raiseLocalFee(); + + beast::http::response resp; + boost::system::error_code ec; + doHTTPRequest(env, yield, false, resp, ec); + BEAST_EXPECT(resp.result() == beast::http::status::internal_server_error); + std::regex body {"Server cannot accept clients"}; + BEAST_EXPECT(std::regex_search(resp.body, body)); } public: void run() { - yield_to([&](boost::asio::yield_context& yield) - { - testWSClientToHttpServer(yield); - testStatusRequest(yield); - testTruncatedWSUpgrade(yield); - // these are secure/insecure protocol pairs, i.e. for - // each item, the second value is the secure or insecure equivalent - testCantConnect("ws", "wss", yield); - testCantConnect("ws2", "wss2", yield); - testCantConnect("http", "https", yield); - //THIS HANGS - testCantConnect("wss", "ws", yield); - testCantConnect("wss2", "ws2", yield); - testCantConnect("https", "http", yield); - }); - for (auto it : {"http", "ws", "ws2"}) { - testAdminRequest(it, true, true); - testAdminRequest(it, true, false); - testAdminRequest(it, false, false); + testAdminRequest (it, true, true); + testAdminRequest (it, true, false); + testAdminRequest (it, false, false); } + + yield_to([&](boost::asio::yield_context& yield) + { + testWSClientToHttpServer (yield); + testStatusRequest (yield); + testTruncatedWSUpgrade (yield); + + // these are secure/insecure protocol pairs, i.e. for + // each item, the second value is the secure or insecure equivalent + testCantConnect ("ws", "wss", yield); + testCantConnect ("ws2", "wss2", yield); + testCantConnect ("http", "https", yield); + testCantConnect ("wss", "ws", yield); + testCantConnect ("wss2", "ws2", yield); + testCantConnect ("https", "http", yield); + + testAmendmentBlock(yield); + testAuth (false, yield); + testAuth (true, yield); + testLimit (yield, 5); + testLimit (yield, 0); + testWSHandoff (yield); + testNoRPC (yield); + testWSRequests (yield); + testRPCRequests (yield); + testStatusNotOkay (yield); + }); + }; }; diff --git a/src/test/server/Server_test.cpp b/src/test/server/Server_test.cpp index 4f59e68864..4015b86ba8 100644 --- a/src/test/server/Server_test.cpp +++ b/src/test/server/Server_test.cpp @@ -19,9 +19,13 @@ #include #include +#include #include #include #include +#include +#include +#include #include #include #include @@ -123,7 +127,7 @@ public: onRequest (Session& session) { session.write (std::string ("Hello, world!\n")); - if (is_keep_alive(session.request())) + if (beast::rfc2616::is_keep_alive(session.request())) session.complete(); else session.close (true); @@ -366,11 +370,186 @@ public: } } + /** + * @brief sink for writing all log messages to a stringstream + */ + class CaptureSink : public beast::Journal::Sink + { + std::stringstream& strm_; + public: + CaptureSink(beast::severities::Severity threshold, + std::stringstream& strm) + : beast::Journal::Sink(threshold, false) + , strm_(strm) + { + } + + void + write(beast::severities::Severity level, std::string const& text) override + { + strm_ << text; + } + }; + + /** + * @brief Log manager for CaptureSinks. This class holds the stream + * instance that is written to by the sinks. Upon destruction, all + * contents of the stream are assigned to the string specified in the + * ctor + */ + class CaptureLogs : public Logs + { + std::stringstream strm_; + std::string& result_; + + public: + CaptureLogs(std::string& result) + : Logs (beast::severities::kInfo) + , result_(result) + { + } + + ~CaptureLogs() override + { + result_ = strm_.str(); + } + + std::unique_ptr + makeSink(std::string const& partition, + beast::severities::Severity threshold) override + { + return std::make_unique(threshold, strm_); + } + }; + + void + testBadConfig () + { + testcase ("Server config - invalid options"); + using namespace test::jtx; + + std::string messages; + + except ([&] + { + Env env {*this, + envconfig([](std::unique_ptr cfg) { + (*cfg).deprecatedClearSection("port_rpc"); + return cfg; + }), + std::make_unique(messages)}; + }); + BEAST_EXPECT ( + messages.find ("Missing 'ip' in [port_rpc]") + != std::string::npos); + + except ([&] + { + Env env {*this, + envconfig([](std::unique_ptr cfg) { + (*cfg).deprecatedClearSection("port_rpc"); + (*cfg)["port_rpc"].set("ip", "127.0.0.1"); + return cfg; + }), + std::make_unique(messages)}; + }); + BEAST_EXPECT ( + messages.find ("Missing 'port' in [port_rpc]") + != std::string::npos); + + except ([&] + { + Env env {*this, + envconfig([](std::unique_ptr cfg) { + (*cfg).deprecatedClearSection("port_rpc"); + (*cfg)["port_rpc"].set("ip", "127.0.0.1"); + (*cfg)["port_rpc"].set("port", "0"); + return cfg; + }), + std::make_unique(messages)}; + }); + BEAST_EXPECT ( + messages.find ("Invalid value '0' for key 'port' in [port_rpc]") + != std::string::npos); + + except ([&] + { + Env env {*this, + envconfig([](std::unique_ptr cfg) { + (*cfg).deprecatedClearSection("port_rpc"); + (*cfg)["port_rpc"].set("ip", "127.0.0.1"); + (*cfg)["port_rpc"].set("port", "8081"); + (*cfg)["port_rpc"].set("protocol", ""); + return cfg; + }), + std::make_unique(messages)}; + }); + BEAST_EXPECT ( + messages.find ("Missing 'protocol' in [port_rpc]") + != std::string::npos); + + except ([&] //this creates a standard test config without the server + //section + { + Env env {*this, + envconfig([](std::unique_ptr cfg) { + cfg = std::make_unique(); + cfg->overwrite ( + ConfigSection::nodeDatabase (), "type", "memory"); + cfg->overwrite ( + ConfigSection::nodeDatabase (), "path", "main"); + cfg->deprecatedClearSection ( + ConfigSection::importNodeDatabase ()); + cfg->legacy("database_path", ""); + cfg->setupControl(true, true, true); + (*cfg)["port_peer"].set("ip", "127.0.0.1"); + (*cfg)["port_peer"].set("port", "8080"); + (*cfg)["port_peer"].set("protocol", "peer"); + (*cfg)["port_rpc"].set("ip", "127.0.0.1"); + (*cfg)["port_rpc"].set("port", "8081"); + (*cfg)["port_rpc"].set("protocol", "http,ws2"); + (*cfg)["port_rpc"].set("admin", "127.0.0.1"); + (*cfg)["port_ws"].set("ip", "127.0.0.1"); + (*cfg)["port_ws"].set("port", "8082"); + (*cfg)["port_ws"].set("protocol", "ws"); + (*cfg)["port_ws"].set("admin", "127.0.0.1"); + return cfg; + }), + std::make_unique(messages)}; + }); + BEAST_EXPECT ( + messages.find ("Required section [server] is missing") + != std::string::npos); + + except ([&] //this creates a standard test config without some of the + //port sections + { + Env env {*this, + envconfig([](std::unique_ptr cfg) { + cfg = std::make_unique(); + cfg->overwrite (ConfigSection::nodeDatabase (), "type", "memory"); + cfg->overwrite (ConfigSection::nodeDatabase (), "path", "main"); + cfg->deprecatedClearSection (ConfigSection::importNodeDatabase ()); + cfg->legacy("database_path", ""); + cfg->setupControl(true, true, true); + (*cfg)["server"].append("port_peer"); + (*cfg)["server"].append("port_rpc"); + (*cfg)["server"].append("port_ws"); + return cfg; + }), + std::make_unique(messages)}; + }); + BEAST_EXPECT ( + messages.find ("Missing section: [port_peer]") + != std::string::npos); + } + void run() { basicTests(); stressTest(); + testBadConfig(); } }; diff --git a/src/test/unity/app_test_unity.cpp b/src/test/unity/app_test_unity1.cpp similarity index 72% rename from src/test/unity/app_test_unity.cpp rename to src/test/unity/app_test_unity1.cpp index 232bad362a..9c7e11e0f6 100644 --- a/src/test/unity/app_test_unity.cpp +++ b/src/test/unity/app_test_unity1.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -33,20 +34,3 @@ #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include diff --git a/src/test/unity/app_test_unity2.cpp b/src/test/unity/app_test_unity2.cpp new file mode 100644 index 0000000000..2253e00fd5 --- /dev/null +++ b/src/test/unity/app_test_unity2.cpp @@ -0,0 +1,37 @@ + +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/src/test/unity/basics_test_unity.cpp b/src/test/unity/basics_test_unity.cpp index 19ea146385..fe4357c097 100644 --- a/src/test/unity/basics_test_unity.cpp +++ b/src/test/unity/basics_test_unity.cpp @@ -29,3 +29,4 @@ #include #include #include +#include diff --git a/src/test/unity/beast_test_unity.cpp b/src/test/unity/beast_test_unity1.cpp similarity index 78% rename from src/test/unity/beast_test_unity.cpp rename to src/test/unity/beast_test_unity1.cpp index d799f3de91..272628f323 100644 --- a/src/test/unity/beast_test_unity.cpp +++ b/src/test/unity/beast_test_unity1.cpp @@ -26,12 +26,3 @@ #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include \ No newline at end of file diff --git a/src/test/unity/beast_test_unity2.cpp b/src/test/unity/beast_test_unity2.cpp new file mode 100644 index 0000000000..bcf8d469a7 --- /dev/null +++ b/src/test/unity/beast_test_unity2.cpp @@ -0,0 +1,28 @@ + +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/src/test/unity/consensus_test_unity.cpp b/src/test/unity/consensus_test_unity.cpp index cdc3717ade..926add4878 100644 --- a/src/test/unity/consensus_test_unity.cpp +++ b/src/test/unity/consensus_test_unity.cpp @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012-2016 Ripple Labs Inc. + Copyright (c) 2012-2017 Ripple Labs Inc. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -19,3 +19,4 @@ #include #include +#include diff --git a/src/test/unity/core_test_unity.cpp b/src/test/unity/core_test_unity.cpp index c6fea7810d..2db1329184 100644 --- a/src/test/unity/core_test_unity.cpp +++ b/src/test/unity/core_test_unity.cpp @@ -21,8 +21,8 @@ #include #include #include -#include -#include +#include +#include #include #include #include diff --git a/src/test/unity/jtx_unity1.cpp b/src/test/unity/jtx_unity1.cpp new file mode 100644 index 0000000000..037b90daee --- /dev/null +++ b/src/test/unity/jtx_unity1.cpp @@ -0,0 +1,37 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/src/test/unity/jtx_unity.cpp b/src/test/unity/jtx_unity2.cpp similarity index 74% rename from src/test/unity/jtx_unity.cpp rename to src/test/unity/jtx_unity2.cpp index d441593085..a2be737f99 100644 --- a/src/test/unity/jtx_unity.cpp +++ b/src/test/unity/jtx_unity2.cpp @@ -19,17 +19,6 @@ #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include #include @@ -45,9 +34,4 @@ #include #include #include - -#include -#include #include -#include -#include diff --git a/src/test/unity/rpc_test_unity.cpp b/src/test/unity/rpc_test_unity.cpp index 5c24489747..3431d8bc22 100644 --- a/src/test/unity/rpc_test_unity.cpp +++ b/src/test/unity/rpc_test_unity.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -36,6 +37,7 @@ #include #include #include +#include #include #include #include diff --git a/src/test/unity/server_status_test_unity.cpp b/src/test/unity/server_status_test_unity.cpp new file mode 100644 index 0000000000..2a87ee13e2 --- /dev/null +++ b/src/test/unity/server_status_test_unity.cpp @@ -0,0 +1,21 @@ + +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include diff --git a/src/test/unity/server_test_unity.cpp b/src/test/unity/server_test_unity.cpp index 1cb2570f1e..557797d748 100644 --- a/src/test/unity/server_test_unity.cpp +++ b/src/test/unity/server_test_unity.cpp @@ -19,4 +19,3 @@ //============================================================================== #include -#include