diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 21:11:59 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 21:11:59 +0000 |
commit | 3cd01b932e1c85394272ae64fae67ebeda92fb00 (patch) | |
tree | c5a3115d710afc1879ddea5349362a2bc651733c /ext | |
parent | Initial commit. (diff) | |
download | dnsdist-3cd01b932e1c85394272ae64fae67ebeda92fb00.tar.xz dnsdist-3cd01b932e1c85394272ae64fae67ebeda92fb00.zip |
Adding upstream version 1.8.3.upstream/1.8.3
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ext')
45 files changed, 14195 insertions, 0 deletions
diff --git a/ext/ipcrypt/LICENSE b/ext/ipcrypt/LICENSE new file mode 100644 index 0000000..0a199e5 --- /dev/null +++ b/ext/ipcrypt/LICENSE @@ -0,0 +1,14 @@ +Copyright (c) 2015-2018, Frank Denis <j at pureftpd dot org> + +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. + diff --git a/ext/ipcrypt/Makefile.am b/ext/ipcrypt/Makefile.am new file mode 100644 index 0000000..cd66bfc --- /dev/null +++ b/ext/ipcrypt/Makefile.am @@ -0,0 +1,8 @@ +EXTRA_DIST = \ + LICENSE + +noinst_LTLIBRARIES = libipcrypt.la + +libipcrypt_la_SOURCES = \ + ipcrypt.c \ + ipcrypt.h diff --git a/ext/ipcrypt/Makefile.in b/ext/ipcrypt/Makefile.in new file mode 100644 index 0000000..19a00e6 --- /dev/null +++ b/ext/ipcrypt/Makefile.in @@ -0,0 +1,722 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = ext/ipcrypt +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_pthread_set_name.m4 \ + $(top_srcdir)/m4/ax_arg_default_enable_disable.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx_17.m4 \ + $(top_srcdir)/m4/ax_python_module.m4 $(top_srcdir)/m4/boost.m4 \ + $(top_srcdir)/m4/dnsdist_enable_dnscrypt.m4 \ + $(top_srcdir)/m4/dnsdist_enable_doh.m4 \ + $(top_srcdir)/m4/dnsdist_enable_tls_providers.m4 \ + $(top_srcdir)/m4/dnsdist_with_cdb.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 \ + $(top_srcdir)/m4/pdns_check_clock_gettime.m4 \ + $(top_srcdir)/m4/pdns_check_dnstap.m4 \ + $(top_srcdir)/m4/pdns_check_libcrypto.m4 \ + $(top_srcdir)/m4/pdns_check_libedit.m4 \ + $(top_srcdir)/m4/pdns_check_libh2o_evloop.m4 \ + $(top_srcdir)/m4/pdns_check_lmdb.m4 \ + $(top_srcdir)/m4/pdns_check_lua_hpp.m4 \ + $(top_srcdir)/m4/pdns_check_network_libs.m4 \ + $(top_srcdir)/m4/pdns_check_os.m4 \ + $(top_srcdir)/m4/pdns_check_pthread_np.m4 \ + $(top_srcdir)/m4/pdns_check_python_venv.m4 \ + $(top_srcdir)/m4/pdns_check_ragel.m4 \ + $(top_srcdir)/m4/pdns_check_secure_memset.m4 \ + $(top_srcdir)/m4/pdns_d_fortify_source.m4 \ + $(top_srcdir)/m4/pdns_enable_ipcipher.m4 \ + $(top_srcdir)/m4/pdns_enable_lto.m4 \ + $(top_srcdir)/m4/pdns_enable_sanitizers.m4 \ + $(top_srcdir)/m4/pdns_enable_tls.m4 \ + $(top_srcdir)/m4/pdns_enable_unit_tests.m4 \ + $(top_srcdir)/m4/pdns_init_auto_vars.m4 \ + $(top_srcdir)/m4/pdns_param_ssp_buffer_size.m4 \ + $(top_srcdir)/m4/pdns_pie.m4 $(top_srcdir)/m4/pdns_relro.m4 \ + $(top_srcdir)/m4/pdns_stack_protector.m4 \ + $(top_srcdir)/m4/pdns_with_ebpf.m4 \ + $(top_srcdir)/m4/pdns_with_gnutls.m4 \ + $(top_srcdir)/m4/pdns_with_libcap.m4 \ + $(top_srcdir)/m4/pdns_with_libsodium.m4 \ + $(top_srcdir)/m4/pdns_with_libssl.m4 \ + $(top_srcdir)/m4/pdns_with_lua.m4 \ + $(top_srcdir)/m4/pdns_with_net_snmp.m4 \ + $(top_srcdir)/m4/pdns_with_nghttp2.m4 \ + $(top_srcdir)/m4/pdns_with_re2.m4 \ + $(top_srcdir)/m4/pdns_with_service_user.m4 \ + $(top_srcdir)/m4/systemd.m4 $(top_srcdir)/m4/warnings.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +LTLIBRARIES = $(noinst_LTLIBRARIES) +libipcrypt_la_LIBADD = +am_libipcrypt_la_OBJECTS = ipcrypt.lo +libipcrypt_la_OBJECTS = $(am_libipcrypt_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/ipcrypt.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(libipcrypt_la_SOURCES) +DIST_SOURCES = $(libipcrypt_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_CPPFLAGS = @AM_CPPFLAGS@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_CPPFLAGS = @BOOST_CPPFLAGS@ +BOOST_LDPATH = @BOOST_LDPATH@ +BOOST_ROOT = @BOOST_ROOT@ +BOOST_UNIT_TEST_FRAMEWORK_LDFLAGS = @BOOST_UNIT_TEST_FRAMEWORK_LDFLAGS@ +BOOST_UNIT_TEST_FRAMEWORK_LDPATH = @BOOST_UNIT_TEST_FRAMEWORK_LDPATH@ +BOOST_UNIT_TEST_FRAMEWORK_LIBS = @BOOST_UNIT_TEST_FRAMEWORK_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_CFLAGS = @CDB_CFLAGS@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +DYNLINKFLAGS = @DYNLINKFLAGS@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FSTRM_CFLAGS = @FSTRM_CFLAGS@ +FSTRM_LIBS = @FSTRM_LIBS@ +GNUTLS_CFLAGS = @GNUTLS_CFLAGS@ +GNUTLS_LIBS = @GNUTLS_LIBS@ +GREP = @GREP@ +HAVE_CXX17 = @HAVE_CXX17@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +IPCRYPT_CFLAGS = @IPCRYPT_CFLAGS@ +IPCRYPT_LIBS = @IPCRYPT_LIBS@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBCAP_CFLAGS = @LIBCAP_CFLAGS@ +LIBCAP_LIBS = @LIBCAP_LIBS@ +LIBCRYPTO_INCLUDES = @LIBCRYPTO_INCLUDES@ +LIBCRYPTO_LDFLAGS = @LIBCRYPTO_LDFLAGS@ +LIBCRYPTO_LIBS = @LIBCRYPTO_LIBS@ +LIBEDIT_CFLAGS = @LIBEDIT_CFLAGS@ +LIBEDIT_LIBS = @LIBEDIT_LIBS@ +LIBH2OEVLOOP_CFLAGS = @LIBH2OEVLOOP_CFLAGS@ +LIBH2OEVLOOP_LIBS = @LIBH2OEVLOOP_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBSSL_CFLAGS = @LIBSSL_CFLAGS@ +LIBSSL_LIBS = @LIBSSL_LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LMDB_CFLAGS = @LMDB_CFLAGS@ +LMDB_LIBS = @LMDB_LIBS@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NET_SNMP_LIBS = @NET_SNMP_LIBS@ +NGHTTP2_CFLAGS = @NGHTTP2_CFLAGS@ +NGHTTP2_LIBS = @NGHTTP2_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGEVERSION = @PACKAGEVERSION@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +PROGRAM_LDFLAGS = @PROGRAM_LDFLAGS@ +PYTHON = @PYTHON@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_VERSION = @PYTHON_VERSION@ +RAGEL = @RAGEL@ +RANLIB = @RANLIB@ +RE2_CFLAGS = @RE2_CFLAGS@ +RE2_LIBS = @RE2_LIBS@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RT_LIBS = @RT_LIBS@ +SANITIZER_FLAGS = @SANITIZER_FLAGS@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYSTEMCTL = @SYSTEMCTL@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_DIR = @SYSTEMD_DIR@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +SYSTEMD_MODULES_LOAD = @SYSTEMD_MODULES_LOAD@ +THREADFLAGS = @THREADFLAGS@ +VERSION = @VERSION@ +WARN_CFLAGS = @WARN_CFLAGS@ +YAHTTP_CFLAGS = @YAHTTP_CFLAGS@ +YAHTTP_LIBS = @YAHTTP_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +service_group = @service_group@ +service_user = @service_user@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +systemd = @systemd@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +EXTRA_DIST = \ + LICENSE + +noinst_LTLIBRARIES = libipcrypt.la +libipcrypt_la_SOURCES = \ + ipcrypt.c \ + ipcrypt.h + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign ext/ipcrypt/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign ext/ipcrypt/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstLTLIBRARIES: + -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) + @list='$(noinst_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libipcrypt.la: $(libipcrypt_la_OBJECTS) $(libipcrypt_la_DEPENDENCIES) $(EXTRA_libipcrypt_la_DEPENDENCIES) + $(AM_V_CCLD)$(LINK) $(libipcrypt_la_OBJECTS) $(libipcrypt_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ipcrypt.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ +@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/ipcrypt.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/ipcrypt.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-noinstLTLIBRARIES \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/ext/ipcrypt/ipcrypt.c b/ext/ipcrypt/ipcrypt.c new file mode 100644 index 0000000..6ef464a --- /dev/null +++ b/ext/ipcrypt/ipcrypt.c @@ -0,0 +1,87 @@ + +#include "ipcrypt.h" + +#define ROTL(X, R) (X) = (unsigned char) ((X) << (R)) | ((X) >> (8 - (R))) + +static void +arx_fwd(unsigned char state[4]) +{ + state[0] += state[1]; + state[2] += state[3]; + ROTL(state[1], 2); + ROTL(state[3], 5); + state[1] ^= state[0]; + state[3] ^= state[2]; + ROTL(state[0], 4); + state[0] += state[3]; + state[2] += state[1]; + ROTL(state[1], 3); + ROTL(state[3], 7); + state[1] ^= state[2]; + state[3] ^= state[0]; + ROTL(state[2], 4); +} + +static void +arx_bwd(unsigned char state[4]) +{ + ROTL(state[2], 4); + state[1] ^= state[2]; + state[3] ^= state[0]; + ROTL(state[1], 5); + ROTL(state[3], 1); + state[0] -= state[3]; + state[2] -= state[1]; + ROTL(state[0], 4); + state[1] ^= state[0]; + state[3] ^= state[2]; + ROTL(state[1], 6); + ROTL(state[3], 3); + state[0] -= state[1]; + state[2] -= state[3]; +} + +static inline void +xor4(unsigned char *out, const unsigned char *x, const unsigned char *y) +{ + out[0] = x[0] ^ y[0]; + out[1] = x[1] ^ y[1]; + out[2] = x[2] ^ y[2]; + out[3] = x[3] ^ y[3]; +} + +int +ipcrypt_encrypt(unsigned char out[IPCRYPT_BYTES], + const unsigned char in[IPCRYPT_BYTES], + const unsigned char key[IPCRYPT_KEYBYTES]) +{ + unsigned char state[4]; + + xor4(state, in, key); + arx_fwd(state); + xor4(state, state, key + 4); + arx_fwd(state); + xor4(state, state, key + 8); + arx_fwd(state); + xor4(out, state, key + 12); + + return 0; +} + +int +ipcrypt_decrypt(unsigned char out[IPCRYPT_BYTES], + const unsigned char in[IPCRYPT_BYTES], + const unsigned char key[IPCRYPT_KEYBYTES]) +{ + unsigned char state[4]; + + xor4(state, in, key + 12); + arx_bwd(state); + xor4(state, state, key + 8); + arx_bwd(state); + xor4(state, state, key + 4); + arx_bwd(state); + xor4(out, state, key); + + return 0; +} diff --git a/ext/ipcrypt/ipcrypt.h b/ext/ipcrypt/ipcrypt.h new file mode 100644 index 0000000..76b94f5 --- /dev/null +++ b/ext/ipcrypt/ipcrypt.h @@ -0,0 +1,24 @@ + +#ifndef ipcrypt_H +#define ipcrypt_H + +#define IPCRYPT_BYTES 4 +#define IPCRYPT_KEYBYTES 16 + +#ifdef __cplusplus +extern "C" { +#endif + +int ipcrypt_encrypt(unsigned char out[IPCRYPT_BYTES], + const unsigned char in[IPCRYPT_BYTES], + const unsigned char key[IPCRYPT_KEYBYTES]); + +int ipcrypt_decrypt(unsigned char out[IPCRYPT_BYTES], + const unsigned char in[IPCRYPT_BYTES], + const unsigned char key[IPCRYPT_KEYBYTES]); + +#ifdef __cplusplus +} /* End of the 'extern "C"' block */ +#endif + +#endif diff --git a/ext/json11/json11.cpp b/ext/json11/json11.cpp new file mode 100644 index 0000000..a0ed964 --- /dev/null +++ b/ext/json11/json11.cpp @@ -0,0 +1,784 @@ +/* Copyright (c) 2013 Dropbox, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "json11.hpp" +#include <cassert> +#include <cmath> +#include <cstdlib> +#include <cstdio> +#include <limits> +#include <string> + +namespace json11 { + +static const int max_depth = 200; + +using std::string; +using std::vector; +using std::map; +using std::make_shared; +using std::initializer_list; +using std::move; + +/* Helper for representing null - just a do-nothing struct, plus comparison + * operators so the helpers in JsonValue work. We can't use nullptr_t because + * it may not be orderable. + */ +struct NullStruct { + bool operator==(NullStruct /*unused*/) const { return true; } + bool operator<(NullStruct /*unused*/) const { return false; } +}; + +/* * * * * * * * * * * * * * * * * * * * + * Serialization + */ + +static void dump(NullStruct /*unused*/, string &out) { + out += "null"; +} + +static void dump(double value, string &out) { + if (std::isfinite(value)) { + char buf[32]; + snprintf(buf, sizeof buf, "%.17g", value); + out += buf; + } else { + out += "null"; + } +} + +static void dump(int value, string &out) { + char buf[32]; + snprintf(buf, sizeof buf, "%d", value); + out += buf; +} + +static void dump(bool value, string &out) { + out += value ? "true" : "false"; +} + +static void dump(const string &value, string &out) { + out += '"'; + for (size_t i = 0; i < value.length(); i++) { + const char ch = value[i]; + if (ch == '\\') { + out += "\\\\"; + } else if (ch == '"') { + out += "\\\""; + } else if (ch == '\b') { + out += "\\b"; + } else if (ch == '\f') { + out += "\\f"; + } else if (ch == '\n') { + out += "\\n"; + } else if (ch == '\r') { + out += "\\r"; + } else if (ch == '\t') { + out += "\\t"; + } else if (static_cast<uint8_t>(ch) <= 0x1f) { + char buf[8]; + snprintf(buf, sizeof buf, "\\u%04x", ch); + out += buf; + } else if (static_cast<uint8_t>(ch) == 0xe2 && static_cast<uint8_t>(value[i+1]) == 0x80 + && static_cast<uint8_t>(value[i+2]) == 0xa8) { + out += "\\u2028"; + i += 2; + } else if (static_cast<uint8_t>(ch) == 0xe2 && static_cast<uint8_t>(value[i+1]) == 0x80 + && static_cast<uint8_t>(value[i+2]) == 0xa9) { + out += "\\u2029"; + i += 2; + } else { + out += ch; + } + } + out += '"'; +} + +static void dump(const Json::array &values, string &out) { + bool first = true; + out += "["; + for (const auto &value : values) { + if (!first) + out += ", "; + value.dump(out); + first = false; + } + out += "]"; +} + +static void dump(const Json::object &values, string &out) { + bool first = true; + out += "{"; + for (const auto &kv : values) { + if (!first) + out += ", "; + dump(kv.first, out); + out += ": "; + kv.second.dump(out); + first = false; + } + out += "}"; +} + +void Json::dump(string &out) const { + m_ptr->dump(out); +} + +/* * * * * * * * * * * * * * * * * * * * + * Value wrappers + */ + +template <Json::Type tag, typename T> +class Value : public JsonValue { +protected: + + // Constructors + explicit Value(const T &value) : m_value(value) {} + explicit Value(T &&value) : m_value(std::move(value)) {} + + // Get type tag + Json::Type type() const override { + return tag; + } + + // Comparisons + bool equals(const JsonValue * other) const override { + return m_value == static_cast<const Value<tag, T> *>(other)->m_value; + } + bool less(const JsonValue * other) const override { + return m_value < static_cast<const Value<tag, T> *>(other)->m_value; + } + + const T m_value; + void dump(string &out) const override { json11::dump(m_value, out); } +}; + +class JsonDouble final : public Value<Json::NUMBER, double> { + double number_value() const override { return m_value; } + int int_value() const override { return static_cast<int>(m_value); } + bool equals(const JsonValue * other) const override { return m_value == other->number_value(); } + bool less(const JsonValue * other) const override { return m_value < other->number_value(); } +public: + explicit JsonDouble(double value) : Value(value) {} +}; + +class JsonInt final : public Value<Json::NUMBER, int> { + double number_value() const override { return m_value; } + int int_value() const override { return m_value; } + bool equals(const JsonValue * other) const override { return m_value == other->number_value(); } + bool less(const JsonValue * other) const override { return m_value < other->number_value(); } +public: + explicit JsonInt(int value) : Value(value) {} +}; + +class JsonBoolean final : public Value<Json::BOOL, bool> { + bool bool_value() const override { return m_value; } +public: + explicit JsonBoolean(bool value) : Value(value) {} +}; + +class JsonString final : public Value<Json::STRING, string> { + const string &string_value() const override { return m_value; } +public: + explicit JsonString(const string &value) : Value(value) {} + explicit JsonString(string &&value) : Value(std::move(value)) {} +}; + +class JsonArray final : public Value<Json::ARRAY, Json::array> { + const Json::array &array_items() const override { return m_value; } + const Json & operator[](size_t i) const override; +public: + explicit JsonArray(const Json::array &value) : Value(value) {} + explicit JsonArray(Json::array &&value) : Value(std::move(value)) {} +}; + +class JsonObject final : public Value<Json::OBJECT, Json::object> { + const Json::object &object_items() const override { return m_value; } + const Json & operator[](const string &key) const override; +public: + explicit JsonObject(const Json::object &value) : Value(value) {} + explicit JsonObject(Json::object &&value) : Value(std::move(value)) {} +}; + +class JsonNull final : public Value<Json::NUL, NullStruct> { +public: + JsonNull() : Value({}) {} +}; + +/* * * * * * * * * * * * * * * * * * * * + * Static globals - static-init-safe + */ +struct Statics { + const std::shared_ptr<JsonValue> null = make_shared<JsonNull>(); + const std::shared_ptr<JsonValue> t = make_shared<JsonBoolean>(true); + const std::shared_ptr<JsonValue> f = make_shared<JsonBoolean>(false); + const string empty_string; + const vector<Json> empty_vector; + const map<string, Json> empty_map; + Statics() {} +}; + +static const Statics & statics() { + static const Statics s {}; + return s; +} + +static const Json & static_null() { + // This has to be separate, not in Statics, because Json() accesses statics().null. + static const Json json_null; + return json_null; +} + +/* * * * * * * * * * * * * * * * * * * * + * Constructors + */ + +Json::Json() noexcept : m_ptr(statics().null) {} +Json::Json(std::nullptr_t) noexcept : m_ptr(statics().null) {} +Json::Json(double value) : m_ptr(make_shared<JsonDouble>(value)) {} +Json::Json(int value) : m_ptr(make_shared<JsonInt>(value)) {} +Json::Json(bool value) : m_ptr(value ? statics().t : statics().f) {} +Json::Json(const string &value) : m_ptr(make_shared<JsonString>(value)) {} +Json::Json(string &&value) : m_ptr(make_shared<JsonString>(std::move(value))) {} +Json::Json(const char * value) : m_ptr(make_shared<JsonString>(value)) {} +Json::Json(const Json::array &values) : m_ptr(make_shared<JsonArray>(values)) {} +Json::Json(Json::array &&values) : m_ptr(make_shared<JsonArray>(std::move(values))) {} +Json::Json(const Json::object &values) : m_ptr(make_shared<JsonObject>(values)) {} +Json::Json(Json::object &&values) : m_ptr(make_shared<JsonObject>(std::move(values))) {} + +/* * * * * * * * * * * * * * * * * * * * + * Accessors + */ + +Json::Type Json::type() const { return m_ptr->type(); } +double Json::number_value() const { return m_ptr->number_value(); } +int Json::int_value() const { return m_ptr->int_value(); } +bool Json::bool_value() const { return m_ptr->bool_value(); } +const string & Json::string_value() const { return m_ptr->string_value(); } +const vector<Json> & Json::array_items() const { return m_ptr->array_items(); } +const map<string, Json> & Json::object_items() const { return m_ptr->object_items(); } +const Json & Json::operator[] (size_t i) const { return (*m_ptr)[i]; } +const Json & Json::operator[] (const string &key) const { return (*m_ptr)[key]; } + +double JsonValue::number_value() const { return 0; } +int JsonValue::int_value() const { return 0; } +bool JsonValue::bool_value() const { return false; } +const string & JsonValue::string_value() const { return statics().empty_string; } +const vector<Json> & JsonValue::array_items() const { return statics().empty_vector; } +const map<string, Json> & JsonValue::object_items() const { return statics().empty_map; } +const Json & JsonValue::operator[] (size_t) const { return static_null(); } +const Json & JsonValue::operator[] (const string &) const { return static_null(); } + +const Json & JsonObject::operator[] (const string &key) const { + auto iter = m_value.find(key); + return (iter == m_value.end()) ? static_null() : iter->second; +} +const Json & JsonArray::operator[] (size_t i) const { + if (i >= m_value.size()) return static_null(); + else return m_value[i]; +} + +/* * * * * * * * * * * * * * * * * * * * + * Comparison + */ + +bool Json::operator== (const Json &other) const { + if (m_ptr->type() != other.m_ptr->type()) + return false; + + return m_ptr->equals(other.m_ptr.get()); +} + +bool Json::operator< (const Json &other) const { + if (m_ptr->type() != other.m_ptr->type()) + return m_ptr->type() < other.m_ptr->type(); + + return m_ptr->less(other.m_ptr.get()); +} + +/* * * * * * * * * * * * * * * * * * * * + * Parsing + */ + +/* esc(c) + * + * Format char c suitable for printing in an error message. + */ +static inline string esc(char c) { + char buf[12]; + if (static_cast<uint8_t>(c) >= 0x20 && static_cast<uint8_t>(c) <= 0x7f) { + snprintf(buf, sizeof buf, "'%c' (%d)", c, c); + } else { + snprintf(buf, sizeof buf, "(%d)", c); + } + return string(buf); +} + +static inline bool in_range(long x, long lower, long upper) { + return (x >= lower && x <= upper); +} + +namespace { +/* JsonParser + * + * Object that tracks all state of an in-progress parse. + */ +struct JsonParser final { + + /* State + */ + const string &str; + size_t i; + string &err; + bool failed; + const JsonParse strategy; + + /* fail(msg, err_ret = Json()) + * + * Mark this parse as failed. + */ + Json fail(string &&msg) { + return fail(std::move(msg), Json()); + } + + template <typename T> + T fail(string &&msg, const T err_ret) { + if (!failed) + err = std::move(msg); + failed = true; + return err_ret; + } + + /* consume_whitespace() + * + * Advance until the current character is non-whitespace. + */ + void consume_whitespace() { + while (str[i] == ' ' || str[i] == '\r' || str[i] == '\n' || str[i] == '\t') + i++; + } + + /* consume_comment() + * + * Advance comments (c-style inline and multiline). + */ + bool consume_comment() { + bool comment_found = false; + if (str[i] == '/') { + i++; + if (i == str.size()) + return fail("unexpected end of input inside comment", false); + if (str[i] == '/') { // inline comment + i++; + if (i == str.size()) + return fail("unexpected end of input inside inline comment", false); + // advance until next line + while (str[i] != '\n') { + i++; + if (i == str.size()) + return fail("unexpected end of input inside inline comment", false); + } + comment_found = true; + } + else if (str[i] == '*') { // multiline comment + i++; + if (i > str.size()-2) + return fail("unexpected end of input inside multi-line comment", false); + // advance until closing tokens + while (!(str[i] == '*' && str[i+1] == '/')) { + i++; + if (i > str.size()-2) + return fail( + "unexpected end of input inside multi-line comment", false); + } + i += 2; + if (i == str.size()) + return fail( + "unexpected end of input inside multi-line comment", false); + comment_found = true; + } + else + return fail("malformed comment", false); + } + return comment_found; + } + + /* consume_garbage() + * + * Advance until the current character is non-whitespace and non-comment. + */ + void consume_garbage() { + consume_whitespace(); + if(strategy == JsonParse::COMMENTS) { + bool comment_found = false; + do { + comment_found = consume_comment(); + consume_whitespace(); + } + while(comment_found); + } + } + + /* get_next_token() + * + * Return the next non-whitespace character. If the end of the input is reached, + * flag an error and return 0. + */ + char get_next_token() { + consume_garbage(); + if (i == str.size()) + return fail("unexpected end of input", (char)0); + + return str[i++]; + } + + /* encode_utf8(pt, out) + * + * Encode pt as UTF-8 and add it to out. + */ + void encode_utf8(long pt, string & out) { + if (pt < 0) + return; + + if (pt < 0x80) { + out += static_cast<char>(pt); + } else if (pt < 0x800) { + out += static_cast<char>((pt >> 6) | 0xC0); + out += static_cast<char>((pt & 0x3F) | 0x80); + } else if (pt < 0x10000) { + out += static_cast<char>((pt >> 12) | 0xE0); + out += static_cast<char>(((pt >> 6) & 0x3F) | 0x80); + out += static_cast<char>((pt & 0x3F) | 0x80); + } else { + out += static_cast<char>((pt >> 18) | 0xF0); + out += static_cast<char>(((pt >> 12) & 0x3F) | 0x80); + out += static_cast<char>(((pt >> 6) & 0x3F) | 0x80); + out += static_cast<char>((pt & 0x3F) | 0x80); + } + } + + /* parse_string() + * + * Parse a string, starting at the current position. + */ + string parse_string() { + string out; + long last_escaped_codepoint = -1; + while (true) { + if (i == str.size()) + return fail("unexpected end of input in string", ""); + + char ch = str[i++]; + + if (ch == '"') { + encode_utf8(last_escaped_codepoint, out); + return out; + } + + if (in_range(ch, 0, 0x1f)) + return fail("unescaped " + esc(ch) + " in string", ""); + + // The usual case: non-escaped characters + if (ch != '\\') { + encode_utf8(last_escaped_codepoint, out); + last_escaped_codepoint = -1; + out += ch; + continue; + } + + // Handle escapes + if (i == str.size()) + return fail("unexpected end of input in string", ""); + + ch = str[i++]; + + if (ch == 'u') { + // Extract 4-byte escape sequence + string esc = str.substr(i, 4); + // Explicitly check length of the substring. The following loop + // relies on std::string returning the terminating NUL when + // accessing str[length]. Checking here reduces brittleness. + if (esc.length() < 4) { + return fail("bad \\u escape: " + esc, ""); + } + for (size_t j = 0; j < 4; j++) { + if (!in_range(esc[j], 'a', 'f') && !in_range(esc[j], 'A', 'F') + && !in_range(esc[j], '0', '9')) + return fail("bad \\u escape: " + esc, ""); + } + + long codepoint = strtol(esc.data(), nullptr, 16); + + // JSON specifies that characters outside the BMP shall be encoded as a pair + // of 4-hex-digit \u escapes encoding their surrogate pair components. Check + // whether we're in the middle of such a beast: the previous codepoint was an + // escaped lead (high) surrogate, and this is a trail (low) surrogate. + if (in_range(last_escaped_codepoint, 0xD800, 0xDBFF) + && in_range(codepoint, 0xDC00, 0xDFFF)) { + // Reassemble the two surrogate pairs into one astral-plane character, per + // the UTF-16 algorithm. + encode_utf8((((last_escaped_codepoint - 0xD800) << 10) + | (codepoint - 0xDC00)) + 0x10000, out); + last_escaped_codepoint = -1; + } else { + encode_utf8(last_escaped_codepoint, out); + last_escaped_codepoint = codepoint; + } + + i += 4; + continue; + } + + encode_utf8(last_escaped_codepoint, out); + last_escaped_codepoint = -1; + + if (ch == 'b') { + out += '\b'; + } else if (ch == 'f') { + out += '\f'; + } else if (ch == 'n') { + out += '\n'; + } else if (ch == 'r') { + out += '\r'; + } else if (ch == 't') { + out += '\t'; + } else if (ch == '"' || ch == '\\' || ch == '/') { + out += ch; + } else { + return fail("invalid escape character " + esc(ch), ""); + } + } + } + + /* parse_number() + * + * Parse a double. + */ + Json parse_number() { + size_t start_pos = i; + + if (str[i] == '-') + i++; + + // Integer part + if (str[i] == '0') { + i++; + if (in_range(str[i], '0', '9')) + return fail("leading 0s not permitted in numbers"); + } else if (in_range(str[i], '1', '9')) { + i++; + while (in_range(str[i], '0', '9')) + i++; + } else { + return fail("invalid " + esc(str[i]) + " in number"); + } + + if (str[i] != '.' && str[i] != 'e' && str[i] != 'E' + && (i - start_pos) <= static_cast<size_t>(std::numeric_limits<int>::digits10)) { + return std::atoi(str.c_str() + start_pos); + } + + // Decimal part + if (str[i] == '.') { + i++; + if (!in_range(str[i], '0', '9')) + return fail("at least one digit required in fractional part"); + + while (in_range(str[i], '0', '9')) + i++; + } + + // Exponent part + if (str[i] == 'e' || str[i] == 'E') { + i++; + + if (str[i] == '+' || str[i] == '-') + i++; + + if (!in_range(str[i], '0', '9')) + return fail("at least one digit required in exponent"); + + while (in_range(str[i], '0', '9')) + i++; + } + + return std::strtod(str.c_str() + start_pos, nullptr); + } + + /* expect(str, res) + * + * Expect that 'str' starts at the character that was just read. If it does, advance + * the input and return res. If not, flag an error. + */ + Json expect(const string &expected, Json res) { + assert(i != 0); + i--; + if (str.compare(i, expected.length(), expected) == 0) { + i += expected.length(); + return res; + } else { + return fail("parse error: expected " + expected + ", got " + str.substr(i, expected.length())); + } + } + + /* parse_json() + * + * Parse a JSON object. + */ + Json parse_json(int depth) { + if (depth > max_depth) { + return fail("exceeded maximum nesting depth"); + } + + char ch = get_next_token(); + if (failed) + return Json(); + + if (ch == '-' || (ch >= '0' && ch <= '9')) { + i--; + return parse_number(); + } + + if (ch == 't') + return expect("true", true); + + if (ch == 'f') + return expect("false", false); + + if (ch == 'n') + return expect("null", Json()); + + if (ch == '"') + return parse_string(); + + if (ch == '{') { + map<string, Json> data; + ch = get_next_token(); + if (ch == '}') + return data; + + while (1) { + if (ch != '"') + return fail("expected '\"' in object, got " + esc(ch)); + + string key = parse_string(); + if (failed) + return Json(); + + ch = get_next_token(); + if (ch != ':') + return fail("expected ':' in object, got " + esc(ch)); + + data[std::move(key)] = parse_json(depth + 1); + if (failed) + return Json(); + + ch = get_next_token(); + if (ch == '}') + break; + if (ch != ',') + return fail("expected ',' in object, got " + esc(ch)); + + ch = get_next_token(); + } + return data; + } + + if (ch == '[') { + vector<Json> data; + ch = get_next_token(); + if (ch == ']') + return data; + + while (1) { + i--; + data.push_back(parse_json(depth + 1)); + if (failed) + return Json(); + + ch = get_next_token(); + if (ch == ']') + break; + if (ch != ',') + return fail("expected ',' in list, got " + esc(ch)); + + ch = get_next_token(); + (void)ch; + } + return data; + } + + return fail("expected value, got " + esc(ch)); + } +}; +}//namespace { + +Json Json::parse(const string &in, string &err, JsonParse strategy) { + JsonParser parser { in, 0, err, false, strategy }; + Json result = parser.parse_json(0); + + // Check for any trailing garbage + parser.consume_garbage(); + if (parser.i != in.size()) + return parser.fail("unexpected trailing " + esc(in[parser.i])); + + return result; +} + +// Documented in json11.hpp +vector<Json> Json::parse_multi(const string &in, + std::string::size_type &parser_stop_pos, + string &err, + JsonParse strategy) { + JsonParser parser { in, 0, err, false, strategy }; + parser_stop_pos = 0; + vector<Json> json_vec; + while (parser.i != in.size() && !parser.failed) { + json_vec.push_back(parser.parse_json(0)); + // Check for another object + parser.consume_garbage(); + if (!parser.failed) + parser_stop_pos = parser.i; + } + return json_vec; +} + +/* * * * * * * * * * * * * * * * * * * * + * Shape-checking + */ + +bool Json::has_shape(const shape & types, string & err) const { + if (!is_object()) { + err = "expected JSON object, got " + dump(); + return false; + } + + for (auto & item : types) { + if ((*this)[item.first].type() != item.second) { + err = "bad type for " + item.first + " in " + dump(); + return false; + } + } + + return true; +} + +} // namespace json11 diff --git a/ext/json11/json11.hpp b/ext/json11/json11.hpp new file mode 100644 index 0000000..a68394b --- /dev/null +++ b/ext/json11/json11.hpp @@ -0,0 +1,232 @@ +/* json11 + * + * json11 is a tiny JSON library for C++11, providing JSON parsing and serialization. + * + * The core object provided by the library is json11::Json. A Json object represents any JSON + * value: null, bool, number (int or double), string (std::string), array (std::vector), or + * object (std::map). + * + * Json objects act like values: they can be assigned, copied, moved, compared for equality or + * order, etc. There are also helper methods Json::dump, to serialize a Json to a string, and + * Json::parse (static) to parse a std::string as a Json object. + * + * Internally, the various types of Json object are represented by the JsonValue class + * hierarchy. + * + * A note on numbers - JSON specifies the syntax of number formatting but not its semantics, + * so some JSON implementations distinguish between integers and floating-point numbers, while + * some don't. In json11, we choose the latter. Because some JSON implementations (namely + * JavaScript itself) treat all numbers as the same type, distinguishing the two leads + * to JSON that will be *silently* changed by a round-trip through those implementations. + * Dangerous! To avoid that risk, json11 stores all numbers as double internally, but also + * provides integer helpers. + * + * Fortunately, double-precision IEEE754 ('double') can precisely store any integer in the + * range +/-2^53, which includes every 'int' on most systems. (Timestamps often use int64 + * or long long to avoid the Y2038K problem; a double storing microseconds since some epoch + * will be exact for +/- 275 years.) + */ + +/* Copyright (c) 2013 Dropbox, Inc. + * + * 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. + */ + +#pragma once + +#include <string> +#include <vector> +#include <map> +#include <memory> +#include <initializer_list> + +#ifdef _MSC_VER + #if _MSC_VER <= 1800 // VS 2013 + #ifndef noexcept + #define noexcept throw() + #endif + + #ifndef snprintf + #define snprintf _snprintf_s + #endif + #endif +#endif + +namespace json11 { + +enum JsonParse { + STANDARD, COMMENTS +}; + +class JsonValue; + +class Json final { +public: + // Types + enum Type { + NUL, NUMBER, BOOL, STRING, ARRAY, OBJECT + }; + + // Array and object typedefs + typedef std::vector<Json> array; + typedef std::map<std::string, Json> object; + + // Constructors for the various types of JSON value. + Json() noexcept; // NUL + Json(std::nullptr_t) noexcept; // NUL + Json(double value); // NUMBER + Json(int value); // NUMBER + Json(bool value); // BOOL + Json(const std::string &value); // STRING + Json(std::string &&value); // STRING + Json(const char * value); // STRING + Json(const array &values); // ARRAY + Json(array &&values); // ARRAY + Json(const object &values); // OBJECT + Json(object &&values); // OBJECT + + // Implicit constructor: anything with a to_json() function. + template <class T, class = decltype(&T::to_json)> + Json(const T & t) : Json(t.to_json()) {} + + // Implicit constructor: map-like objects (std::map, std::unordered_map, etc) + template <class M, typename std::enable_if< + std::is_constructible<std::string, typename M::key_type>::value + && std::is_constructible<Json, typename M::mapped_type>::value, + int>::type = 0> + Json(const M & m) : Json(object(m.begin(), m.end())) {} + + // Implicit constructor: vector-like objects (std::list, std::vector, std::set, etc) + template <class V, typename std::enable_if< + std::is_constructible<Json, typename V::value_type>::value, + int>::type = 0> + Json(const V & v) : Json(array(v.begin(), v.end())) {} + + // This prevents Json(some_pointer) from accidentally producing a bool. Use + // Json(bool(some_pointer)) if that behavior is desired. + Json(void *) = delete; + + // Accessors + Type type() const; + + bool is_null() const { return type() == NUL; } + bool is_number() const { return type() == NUMBER; } + bool is_bool() const { return type() == BOOL; } + bool is_string() const { return type() == STRING; } + bool is_array() const { return type() == ARRAY; } + bool is_object() const { return type() == OBJECT; } + + // Return the enclosed value if this is a number, 0 otherwise. Note that json11 does not + // distinguish between integer and non-integer numbers - number_value() and int_value() + // can both be applied to a NUMBER-typed object. + double number_value() const; + int int_value() const; + + // Return the enclosed value if this is a boolean, false otherwise. + bool bool_value() const; + // Return the enclosed string if this is a string, "" otherwise. + const std::string &string_value() const; + // Return the enclosed std::vector if this is an array, or an empty vector otherwise. + const array &array_items() const; + // Return the enclosed std::map if this is an object, or an empty map otherwise. + const object &object_items() const; + + // Return a reference to arr[i] if this is an array, Json() otherwise. + const Json & operator[](size_t i) const; + // Return a reference to obj[key] if this is an object, Json() otherwise. + const Json & operator[](const std::string &key) const; + + // Serialize. + void dump(std::string &out) const; + std::string dump() const { + std::string out; + dump(out); + return out; + } + + // Parse. If parse fails, return Json() and assign an error message to err. + static Json parse(const std::string & in, + std::string & err, + JsonParse strategy = JsonParse::STANDARD); + static Json parse(const char * in, + std::string & err, + JsonParse strategy = JsonParse::STANDARD) { + if (in) { + return parse(std::string(in), err, strategy); + } else { + err = "null input"; + return nullptr; + } + } + // Parse multiple objects, concatenated or separated by whitespace + static std::vector<Json> parse_multi( + const std::string & in, + std::string::size_type & parser_stop_pos, + std::string & err, + JsonParse strategy = JsonParse::STANDARD); + + static inline std::vector<Json> parse_multi( + const std::string & in, + std::string & err, + JsonParse strategy = JsonParse::STANDARD) { + std::string::size_type parser_stop_pos; + return parse_multi(in, parser_stop_pos, err, strategy); + } + + bool operator== (const Json &rhs) const; + bool operator< (const Json &rhs) const; + bool operator!= (const Json &rhs) const { return !(*this == rhs); } + bool operator<= (const Json &rhs) const { return !(rhs < *this); } + bool operator> (const Json &rhs) const { return (rhs < *this); } + bool operator>= (const Json &rhs) const { return !(*this < rhs); } + + /* has_shape(types, err) + * + * Return true if this is a JSON object and, for each item in types, has a field of + * the given type. If not, return false and set err to a descriptive message. + */ + typedef std::initializer_list<std::pair<std::string, Type>> shape; + bool has_shape(const shape & types, std::string & err) const; + +private: + std::shared_ptr<JsonValue> m_ptr; +}; + +// Internal class hierarchy - JsonValue objects are not exposed to users of this API. +class JsonValue { +protected: + friend class Json; + friend class JsonInt; + friend class JsonDouble; + virtual Json::Type type() const = 0; + virtual bool equals(const JsonValue * other) const = 0; + virtual bool less(const JsonValue * other) const = 0; + virtual void dump(std::string &out) const = 0; + virtual double number_value() const; + virtual int int_value() const; + virtual bool bool_value() const; + virtual const std::string &string_value() const; + virtual const Json::array &array_items() const; + virtual const Json &operator[](size_t i) const; + virtual const Json::object &object_items() const; + virtual const Json &operator[](const std::string &key) const; + virtual ~JsonValue() {} +}; + +} // namespace json11 diff --git a/ext/libbpf/libbpf.h b/ext/libbpf/libbpf.h new file mode 100644 index 0000000..2fc7281 --- /dev/null +++ b/ext/libbpf/libbpf.h @@ -0,0 +1,210 @@ +/* eBPF mini library */ +#ifndef __LIBBPF_H +#define __LIBBPF_H + +#ifdef __cplusplus +extern "C" { +#endif + +struct bpf_insn; + +int bpf_create_map(enum bpf_map_type map_type, int key_size, int value_size, + int max_entries, int map_flags); +int bpf_update_elem(int fd, void *key, void *value, unsigned long long flags); +int bpf_lookup_elem(int fd, void *key, void *value); +int bpf_delete_elem(int fd, void *key); +int bpf_get_next_key(int fd, void *key, void *next_key); + +int bpf_prog_load(enum bpf_prog_type prog_type, + const struct bpf_insn *insns, int insn_len, + const char *license, int kern_version); + +int bpf_obj_pin(int fd, const char *pathname); +int bpf_obj_get(const char *pathname); + +#define LOG_BUF_SIZE 65536 +extern char bpf_log_buf[LOG_BUF_SIZE]; + +/* ALU ops on registers, bpf_add|sub|...: dst_reg += src_reg */ + +#define BPF_ALU64_REG(OP, DST, SRC) \ + ((struct bpf_insn) { \ + .code = BPF_ALU64 | BPF_OP(OP) | BPF_X, \ + .dst_reg = DST, \ + .src_reg = SRC, \ + .off = 0, \ + .imm = 0 }) + +#define BPF_ALU32_REG(OP, DST, SRC) \ + ((struct bpf_insn) { \ + .code = BPF_ALU | BPF_OP(OP) | BPF_X, \ + .dst_reg = DST, \ + .src_reg = SRC, \ + .off = 0, \ + .imm = 0 }) + +/* ALU ops on immediates, bpf_add|sub|...: dst_reg += imm32 */ + +#define BPF_ALU64_IMM(OP, DST, IMM) \ + ((struct bpf_insn) { \ + .code = BPF_ALU64 | BPF_OP(OP) | BPF_K, \ + .dst_reg = DST, \ + .src_reg = 0, \ + .off = 0, \ + .imm = IMM }) + +#define BPF_ALU32_IMM(OP, DST, IMM) \ + ((struct bpf_insn) { \ + .code = BPF_ALU | BPF_OP(OP) | BPF_K, \ + .dst_reg = DST, \ + .src_reg = 0, \ + .off = 0, \ + .imm = IMM }) + +/* Short form of mov, dst_reg = src_reg */ + +#define BPF_MOV64_REG(DST, SRC) \ + ((struct bpf_insn) { \ + .code = BPF_ALU64 | BPF_MOV | BPF_X, \ + .dst_reg = DST, \ + .src_reg = SRC, \ + .off = 0, \ + .imm = 0 }) + +#define BPF_MOV32_REG(DST, SRC) \ + ((struct bpf_insn) { \ + .code = BPF_ALU | BPF_MOV | BPF_X, \ + .dst_reg = DST, \ + .src_reg = SRC, \ + .off = 0, \ + .imm = 0 }) + +/* Short form of mov, dst_reg = imm32 */ + +#define BPF_MOV64_IMM(DST, IMM) \ + ((struct bpf_insn) { \ + .code = BPF_ALU64 | BPF_MOV | BPF_K, \ + .dst_reg = DST, \ + .src_reg = 0, \ + .off = 0, \ + .imm = IMM }) + +/* BPF_LD_IMM64 macro encodes single 'load 64-bit immediate' insn */ +#define BPF_LD_IMM64(DST, IMM) \ + BPF_LD_IMM64_RAW(DST, 0, IMM) + +#define BPF_LD_IMM64_RAW(DST, SRC, IMM) \ + ((struct bpf_insn) { \ + .code = BPF_LD | BPF_DW | BPF_IMM, \ + .dst_reg = DST, \ + .src_reg = SRC, \ + .off = 0, \ + .imm = (__s32) (IMM) }), \ + ((struct bpf_insn) { \ + .code = 0, /* zero is reserved opcode */ \ + .dst_reg = 0, \ + .src_reg = 0, \ + .off = 0, \ + .imm = (__s32)(((__u64) (IMM)) >> 32) }) + +#ifndef BPF_PSEUDO_MAP_FD +# define BPF_PSEUDO_MAP_FD 1 +#endif + +/* pseudo BPF_LD_IMM64 insn used to refer to process-local map_fd */ +#define BPF_LD_MAP_FD(DST, MAP_FD) \ + BPF_LD_IMM64_RAW(DST, BPF_PSEUDO_MAP_FD, MAP_FD) + + +/* Direct packet access, R0 = *(uint *) (skb->data + imm32) */ + +#define BPF_LD_ABS(SIZE, IMM) \ + ((struct bpf_insn) { \ + .code = BPF_LD | BPF_SIZE(SIZE) | BPF_ABS, \ + .dst_reg = 0, \ + .src_reg = 0, \ + .off = 0, \ + .imm = IMM }) + +/* Memory load, dst_reg = *(uint *) (src_reg + off16) */ + +#define BPF_LDX_MEM(SIZE, DST, SRC, OFF) \ + ((struct bpf_insn) { \ + .code = BPF_LDX | BPF_SIZE(SIZE) | BPF_MEM, \ + .dst_reg = DST, \ + .src_reg = SRC, \ + .off = OFF, \ + .imm = 0 }) + +/* Memory store, *(uint *) (dst_reg + off16) = src_reg */ + +#define BPF_STX_MEM(SIZE, DST, SRC, OFF) \ + ((struct bpf_insn) { \ + .code = BPF_STX | BPF_SIZE(SIZE) | BPF_MEM, \ + .dst_reg = DST, \ + .src_reg = SRC, \ + .off = OFF, \ + .imm = 0 }) + +/* Memory store, *(uint *) (dst_reg + off16) = imm32 */ + +#define BPF_ST_MEM(SIZE, DST, OFF, IMM) \ + ((struct bpf_insn) { \ + .code = BPF_ST | BPF_SIZE(SIZE) | BPF_MEM, \ + .dst_reg = DST, \ + .src_reg = 0, \ + .off = OFF, \ + .imm = IMM }) + +/* Conditional jumps against registers, if (dst_reg 'op' src_reg) goto pc + off16 */ + +#define BPF_JMP_REG(OP, DST, SRC, OFF) \ + ((struct bpf_insn) { \ + .code = BPF_JMP | BPF_OP(OP) | BPF_X, \ + .dst_reg = DST, \ + .src_reg = SRC, \ + .off = OFF, \ + .imm = 0 }) + +/* Conditional jumps against immediates, if (dst_reg 'op' imm32) goto pc + off16 */ + +#define BPF_JMP_IMM(OP, DST, IMM, OFF) \ + ((struct bpf_insn) { \ + .code = BPF_JMP | BPF_OP(OP) | BPF_K, \ + .dst_reg = DST, \ + .src_reg = 0, \ + .off = OFF, \ + .imm = IMM }) + +/* Raw code statement block */ + +#define BPF_RAW_INSN(CODE, DST, SRC, OFF, IMM) \ + ((struct bpf_insn) { \ + .code = CODE, \ + .dst_reg = DST, \ + .src_reg = SRC, \ + .off = OFF, \ + .imm = IMM }) + +/* Program exit */ + +#define BPF_EXIT_INSN() \ + ((struct bpf_insn) { \ + .code = BPF_JMP | BPF_EXIT, \ + .dst_reg = 0, \ + .src_reg = 0, \ + .off = 0, \ + .imm = 0 }) + +/* create RAW socket and bind to interface 'name' */ +int open_raw_sock(const char *name); + +struct perf_event_attr; +int perf_event_open(struct perf_event_attr *attr, int pid, int cpu, + int group_fd, unsigned long flags); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ext/lmdb-safe/lmdb-safe.cc b/ext/lmdb-safe/lmdb-safe.cc new file mode 100644 index 0000000..33c3d45 --- /dev/null +++ b/ext/lmdb-safe/lmdb-safe.cc @@ -0,0 +1,427 @@ +#include "lmdb-safe.hh" +#include <fcntl.h> +#include <mutex> +#include <memory> +#include <sys/stat.h> +#include <string.h> +#include <map> + +#ifndef DNSDIST +#include "../../pdns/gettime.hh" +#endif + +using std::string; +using std::runtime_error; +using std::tuple; +using std::weak_ptr; + +static string MDBError(int rc) +{ + return mdb_strerror(rc); +} + +#ifndef DNSDIST + +namespace LMDBLS { + // this also returns a pointer to the string's data. Do not hold on to it too long! + const LSheader* LSassertFixedHeaderSize(std::string_view val) { + // cerr<<"val.size()="<<val.size()<<endl; + if (val.size() < LS_MIN_HEADER_SIZE) { + throw std::runtime_error("LSheader too short"); + } + + return reinterpret_cast<const LSheader*>(val.data()); + } + + size_t LScheckHeaderAndGetSize(std::string_view val, size_t datasize) { + const LSheader* lsh = LSassertFixedHeaderSize(val); + + if (lsh->d_version != 0) { + throw std::runtime_error("LSheader has wrong version (not zero)"); + } + + size_t headersize = LS_MIN_HEADER_SIZE; + + unsigned char* tmp = (unsigned char*)val.data(); + uint16_t numextra = (tmp[LS_NUMEXTRA_OFFSET] << 8) + tmp[LS_NUMEXTRA_OFFSET+1]; + + headersize += numextra * LS_BLOCK_SIZE; + + if (val.size() < headersize) { + throw std::runtime_error("LSheader too short for promised extra data"); + } + + if (datasize && val.size() < (headersize+datasize)) { + throw std::runtime_error("Trailing data after LSheader has wrong size"); + } + + return headersize; + } + + size_t LScheckHeaderAndGetSize(const MDBOutVal *val, size_t datasize) { + return LScheckHeaderAndGetSize(val->getNoStripHeader<string_view>(), datasize); + } + + bool LSisDeleted(std::string_view val) { + const LSheader* lsh = LSassertFixedHeaderSize(val); + + return (lsh->d_flags & LS_FLAG_DELETED) != 0; + } + + bool s_flag_deleted{false}; +} + +#endif /* #ifndef DNSDIST */ + +MDBDbi::MDBDbi(MDB_env* env, MDB_txn* txn, const string_view dbname, int flags) +{ + // A transaction that uses this function must finish (either commit or abort) before any other transaction in the process may use this function. + + int rc = mdb_dbi_open(txn, dbname.empty() ? 0 : &dbname[0], flags, &d_dbi); + if(rc) + throw std::runtime_error("Unable to open named database: " + MDBError(rc)); + + // Database names are keys in the unnamed database, and may be read but not written. +} + +MDBEnv::MDBEnv(const char* fname, int flags, int mode, uint64_t mapsizeMB) +{ + mdb_env_create(&d_env); + if(mdb_env_set_mapsize(d_env, mapsizeMB * 1048576)) + throw std::runtime_error("setting map size"); + /* +Various other options may also need to be set before opening the handle, e.g. mdb_env_set_mapsize(), mdb_env_set_maxreaders(), mdb_env_set_maxdbs(), + */ + + mdb_env_set_maxdbs(d_env, 128); + + // we need MDB_NOTLS since we rely on its semantics + if(int rc=mdb_env_open(d_env, fname, flags | MDB_NOTLS, mode)) { + // If this function fails, mdb_env_close() must be called to discard the MDB_env handle. + mdb_env_close(d_env); + throw std::runtime_error("Unable to open database file "+std::string(fname)+": " + MDBError(rc)); + } + + if ((flags & MDB_RDONLY) == 0) { + // Check for stale readers to prevent unbridled database growth. + // Only do this when in RW mode since it affects the file. + mdb_reader_check(d_env, nullptr); + } +} + +void MDBEnv::incROTX() +{ + std::lock_guard<std::mutex> l(d_countmutex); + ++d_ROtransactionsOut[std::this_thread::get_id()]; +} + +void MDBEnv::decROTX() +{ + std::lock_guard<std::mutex> l(d_countmutex); + --d_ROtransactionsOut[std::this_thread::get_id()]; +} + +void MDBEnv::incRWTX() +{ + std::lock_guard<std::mutex> l(d_countmutex); + ++d_RWtransactionsOut[std::this_thread::get_id()]; +} + +void MDBEnv::decRWTX() +{ + std::lock_guard<std::mutex> l(d_countmutex); + --d_RWtransactionsOut[std::this_thread::get_id()]; +} + +int MDBEnv::getRWTX() +{ + std::lock_guard<std::mutex> l(d_countmutex); + return d_RWtransactionsOut[std::this_thread::get_id()]; +} +int MDBEnv::getROTX() +{ + std::lock_guard<std::mutex> l(d_countmutex); + return d_ROtransactionsOut[std::this_thread::get_id()]; +} + + +std::shared_ptr<MDBEnv> getMDBEnv(const char* fname, int flags, int mode, uint64_t mapsizeMB) +{ + struct Value + { + weak_ptr<MDBEnv> wp; + int flags; + }; + + static std::map<tuple<dev_t, ino_t>, Value> s_envs; + static std::mutex mut; + + struct stat statbuf; + if(stat(fname, &statbuf)) { + if(errno != ENOENT) + throw std::runtime_error("Unable to stat prospective mdb database: "+string(strerror(errno))); + else { + std::lock_guard<std::mutex> l(mut); + auto fresh = std::make_shared<MDBEnv>(fname, flags, mode, mapsizeMB); + if(stat(fname, &statbuf)) + throw std::runtime_error("Unable to stat prospective mdb database: "+string(strerror(errno))); + auto key = std::tie(statbuf.st_dev, statbuf.st_ino); + s_envs[key] = {fresh, flags}; + return fresh; + } + } + + std::lock_guard<std::mutex> l(mut); + auto key = std::tie(statbuf.st_dev, statbuf.st_ino); + auto iter = s_envs.find(key); + if(iter != s_envs.end()) { + auto sp = iter->second.wp.lock(); + if(sp) { + if(iter->second.flags != flags) + throw std::runtime_error("Can't open mdb with differing flags"); + + return sp; + } + else { + s_envs.erase(iter); // useful if make_shared fails + } + } + + auto fresh = std::make_shared<MDBEnv>(fname, flags, mode, mapsizeMB); + s_envs[key] = {fresh, flags}; + + return fresh; +} + + +MDBDbi MDBEnv::openDB(const string_view dbname, int flags) +{ + unsigned int envflags; + mdb_env_get_flags(d_env, &envflags); + /* + This function must not be called from multiple concurrent transactions in the same process. A transaction that uses this function must finish (either commit or abort) before any other transaction in the process may use this function. + */ + std::lock_guard<std::mutex> l(d_openmut); + + if(!(envflags & MDB_RDONLY)) { + auto rwt = getRWTransaction(); + MDBDbi ret = rwt->openDB(dbname, flags); + rwt->commit(); + return ret; + } + + MDBDbi ret; + { + auto rwt = getROTransaction(); + ret = rwt->openDB(dbname, flags); + } + return ret; +} + +MDBRWTransactionImpl::MDBRWTransactionImpl(MDBEnv *parent, MDB_txn *txn): + MDBROTransactionImpl(parent, txn) + +{ + +} + +MDB_txn *MDBRWTransactionImpl::openRWTransaction(MDBEnv *env, MDB_txn *parent, int flags) +{ + MDB_txn *result; + if(env->getROTX() || env->getRWTX()) + throw std::runtime_error("Duplicate RW transaction"); + + if(int rc=mdb_txn_begin(env->d_env, parent, flags, &result)) + throw std::runtime_error("Unable to start RW transaction: "+std::string(mdb_strerror(rc))); + + env->incRWTX(); + return result; +} + +MDBRWTransactionImpl::MDBRWTransactionImpl(MDBEnv* parent, int flags): + MDBRWTransactionImpl(parent, openRWTransaction(parent, nullptr, flags)) +{ +#ifndef DNSDIST + struct timespec tp; + + gettime(&tp, true); + + d_txtime = tp.tv_sec * (1000 * 1000 * 1000) + tp.tv_nsec; +#endif +} + +MDBRWTransactionImpl::~MDBRWTransactionImpl() +{ + abort(); +} + +void MDBRWTransactionImpl::commit() +{ + closeRORWCursors(); + if (!d_txn) { + return; + } + + if(int rc = mdb_txn_commit(d_txn)) { + throw std::runtime_error("committing: " + std::string(mdb_strerror(rc))); + } + environment().decRWTX(); + d_txn = nullptr; +} + +void MDBRWTransactionImpl::abort() +{ + closeRORWCursors(); + if (!d_txn) { + return; + } + + mdb_txn_abort(d_txn); + // prevent the RO destructor from cleaning up the transaction itself + environment().decRWTX(); + d_txn = nullptr; +} + +MDBROTransactionImpl::MDBROTransactionImpl(MDBEnv *parent, MDB_txn *txn): + d_parent(parent), + d_cursors(), + d_txn(txn) +{ + +} + +MDB_txn *MDBROTransactionImpl::openROTransaction(MDBEnv *env, MDB_txn *parent, int flags) +{ + if(env->getRWTX()) + throw std::runtime_error("Duplicate RO transaction"); + + /* + A transaction and its cursors must only be used by a single thread, and a thread may only have a single transaction at a time. If MDB_NOTLS is in use, this does not apply to read-only transactions. */ + MDB_txn *result = nullptr; + + if(int rc=mdb_txn_begin(env->d_env, parent, MDB_RDONLY | flags, &result)) + throw std::runtime_error("Unable to start RO transaction: "+string(mdb_strerror(rc))); + + env->incROTX(); + + return result; +} + +void MDBROTransactionImpl::closeROCursors() +{ + // we need to move the vector away to ensure that the cursors don’t mess with our iteration. + std::vector<MDBROCursor*> buf; + std::swap(d_cursors, buf); + for (auto &cursor: buf) { + cursor->close(); + } +} + +MDBROTransactionImpl::MDBROTransactionImpl(MDBEnv *parent, int flags): + MDBROTransactionImpl(parent, openROTransaction(parent, nullptr, flags)) +{ + +} + +MDBROTransactionImpl::~MDBROTransactionImpl() +{ + // this is safe because C++ will not call overrides of virtual methods in destructors. + MDBROTransactionImpl::commit(); +} + +void MDBROTransactionImpl::abort() +{ + closeROCursors(); + // if d_txn is non-nullptr here, either the transaction object was invalidated earlier (e.g. by moving from it), or it is an RW transaction which has already cleaned up the d_txn pointer (with an abort). + if (d_txn) { + d_parent->decROTX(); + mdb_txn_abort(d_txn); // this appears to work better than abort for r/o database opening + d_txn = nullptr; + } +} + +void MDBROTransactionImpl::commit() +{ + closeROCursors(); + // if d_txn is non-nullptr here, either the transaction object was invalidated earlier (e.g. by moving from it), or it is an RW transaction which has already cleaned up the d_txn pointer (with an abort). + if (d_txn) { + d_parent->decROTX(); + mdb_txn_commit(d_txn); // this appears to work better than abort for r/o database opening + d_txn = nullptr; + } +} + + + +void MDBRWTransactionImpl::clear(MDB_dbi dbi) +{ + if(int rc = mdb_drop(d_txn, dbi, 0)) { + throw runtime_error("Error clearing database: " + MDBError(rc)); + } +} + +MDBRWCursor MDBRWTransactionImpl::getRWCursor(const MDBDbi& dbi) +{ + MDB_cursor *cursor; + int rc= mdb_cursor_open(d_txn, dbi, &cursor); + if(rc) { + throw std::runtime_error("Error creating RW cursor: "+std::string(mdb_strerror(rc))); + } + + return MDBRWCursor(d_rw_cursors, cursor, d_txn, d_txtime); +} + +MDBRWCursor MDBRWTransactionImpl::getCursor(const MDBDbi &dbi) +{ + return getRWCursor(dbi); +} + +MDBRWTransaction MDBRWTransactionImpl::getRWTransaction() +{ + MDB_txn *txn; + if (int rc = mdb_txn_begin(environment(), *this, 0, &txn)) { + throw std::runtime_error(std::string("failed to start child transaction: ")+mdb_strerror(rc)); + } + // we need to increase the counter here because commit/abort on the child transaction will decrease it + environment().incRWTX(); + return MDBRWTransaction(new MDBRWTransactionImpl(&environment(), txn)); +} + +MDBROTransaction MDBRWTransactionImpl::getROTransaction() +{ + return getRWTransaction(); +} + +MDBROTransaction MDBEnv::getROTransaction() +{ + return MDBROTransaction(new MDBROTransactionImpl(this)); +} +MDBRWTransaction MDBEnv::getRWTransaction() +{ + return MDBRWTransaction(new MDBRWTransactionImpl(this)); +} + + +void MDBRWTransactionImpl::closeRWCursors() +{ + decltype(d_rw_cursors) buf; + std::swap(d_rw_cursors, buf); + for (auto &cursor: buf) { + cursor->close(); + } +} + +MDBROCursor MDBROTransactionImpl::getCursor(const MDBDbi& dbi) +{ + return getROCursor(dbi); +} + +MDBROCursor MDBROTransactionImpl::getROCursor(const MDBDbi &dbi) +{ + MDB_cursor *cursor; + int rc= mdb_cursor_open(d_txn, dbi, &cursor); + if(rc) { + throw std::runtime_error("Error creating RO cursor: "+std::string(mdb_strerror(rc))); + } + return MDBROCursor(d_cursors, cursor); +} diff --git a/ext/lmdb-safe/lmdb-safe.hh b/ext/lmdb-safe/lmdb-safe.hh new file mode 100644 index 0000000..2d5983b --- /dev/null +++ b/ext/lmdb-safe/lmdb-safe.hh @@ -0,0 +1,898 @@ +#pragma once +#include <string_view> +#include <lmdb.h> +#include <iostream> +#include <fstream> +#include <set> +#include <map> +#include <thread> +#include <memory> +#include <string> +#include <string.h> +#include <mutex> +#include <vector> +#include <algorithm> + +#include "config.h" + +#ifndef DNSDIST +#include <boost/range/detail/common.hpp> +#include <stdint.h> +#include <netinet/in.h> +#include <stdexcept> +#include "../../pdns/misc.hh" +#endif + +using std::string_view; + +/* open issues: + * + * - missing convenience functions (string_view, string) + */ + +/* +The error strategy. Anything that "should never happen" turns into an exception. But things like 'duplicate entry' or 'no such key' are for you to deal with. + */ + +/* + Thread safety: we are as safe as lmdb. You can talk to MDBEnv from as many threads as you want +*/ + +/** MDBDbi is our only 'value type' object, as 1) a dbi is actually an integer + and 2) per LMDB documentation, we never close it. */ +class MDBDbi +{ +public: + MDBDbi(): d_dbi(-1) + { + } + explicit MDBDbi(MDB_env* env, MDB_txn* txn, string_view dbname, int flags); + + operator const MDB_dbi&() const + { + return d_dbi; + } + + MDB_dbi d_dbi; +}; + +class MDBRWTransactionImpl; +class MDBROTransactionImpl; + +using MDBROTransaction = std::unique_ptr<MDBROTransactionImpl>; +using MDBRWTransaction = std::unique_ptr<MDBRWTransactionImpl>; + +class MDBEnv +{ +public: + MDBEnv(const char* fname, int flags, int mode, uint64_t mapsizeMB); + + ~MDBEnv() + { + // Only a single thread may call this function. All transactions, databases, and cursors must already be closed before calling this function + mdb_env_close(d_env); + // but, elsewhere, docs say database handles do not need to be closed? + } + + MDBDbi openDB(const string_view dbname, int flags); + + MDBRWTransaction getRWTransaction(); + MDBROTransaction getROTransaction(); + + operator MDB_env*& () + { + return d_env; + } + MDB_env* d_env; + + int getRWTX(); + void incRWTX(); + void decRWTX(); + int getROTX(); + void incROTX(); + void decROTX(); +private: + std::mutex d_openmut; + std::mutex d_countmutex; + std::map<std::thread::id, int> d_RWtransactionsOut; + std::map<std::thread::id, int> d_ROtransactionsOut; +}; + +std::shared_ptr<MDBEnv> getMDBEnv(const char* fname, int flags, int mode, uint64_t mapsizeMB=(sizeof(void *)==4) ? 100 : 16000); + +#ifndef DNSDIST + +#if !defined(__BYTE_ORDER__) || !defined(__ORDER_LITTLE_ENDIAN__) || !defined(__ORDER_BIG_ENDIAN__) +#error "your compiler did not define byte order macros" +#endif + +// FIXME do something more portable than __builtin_bswap64 +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define _LMDB_SAFE_BSWAP64MAYBE(x) __builtin_bswap64(x) +#else +#define _LMDB_SAFE_BSWAP64MAYBE(x) (x) +#endif + +struct MDBOutVal; // forward declaration because of how the functions below tie in with MDBOutVal + +namespace LMDBLS { + class __attribute__((__packed__)) LSheader { + public: + uint64_t d_timestamp; + uint64_t d_txnid; + uint8_t d_version; + uint8_t d_flags; + uint32_t d_reserved; + uint16_t d_numextra; + + LSheader(uint64_t timestamp, uint64_t txnid, uint8_t flags=0, uint8_t version=0, uint8_t numextra=0): + d_timestamp(_LMDB_SAFE_BSWAP64MAYBE(timestamp)), + d_txnid(_LMDB_SAFE_BSWAP64MAYBE(txnid)), + d_version(version), + d_flags(flags), + d_reserved(0), + d_numextra(htons(numextra)) + { + + } + + std::string toString() { + return std::string((char*)this, sizeof(*this)) + std::string(ntohs(d_numextra)*8, '\0'); + } + + + }; + + static_assert(sizeof(LSheader)==24, "LSheader size is wrong"); + + const size_t LS_MIN_HEADER_SIZE = sizeof(LSheader); + const size_t LS_BLOCK_SIZE = 8; + const size_t LS_NUMEXTRA_OFFSET = 22; + const uint8_t LS_FLAG_DELETED = 0x01; + + const LSheader* LSassertFixedHeaderSize(std::string_view val); + size_t LScheckHeaderAndGetSize(std::string_view val, size_t datasize=0); + size_t LScheckHeaderAndGetSize(const MDBOutVal *val, size_t datasize=0); + bool LSisDeleted(std::string_view val); + + extern bool s_flag_deleted; +} + +#undef _LMDB_SAFE_BSWAP64MAYBE + +#endif /* ifndef DNSDIST */ + + +struct MDBOutVal +{ + operator MDB_val&() + { + return d_mdbval; + } + +#ifndef DNSDIST + template <class T, + typename std::enable_if<std::is_integral<T>::value, + T>::type* = nullptr> const + T get() + { + T ret; + + size_t offset = LMDBLS::LScheckHeaderAndGetSize(this, sizeof(T)); + + memcpy(&ret, reinterpret_cast<const char *>(d_mdbval.mv_data)+offset, sizeof(T)); + + static_assert(sizeof(T) == 4, "this code currently only supports 32 bit integers"); + ret = ntohl(ret); + return ret; + } + + template <class T, + typename std::enable_if<std::is_integral<T>::value, + T>::type* = nullptr> const + T getNoStripHeader() + { + T ret; + if(d_mdbval.mv_size != sizeof(T)) + throw std::runtime_error("MDB data has wrong length for type"); + + memcpy(&ret, d_mdbval.mv_data, sizeof(T)); + + static_assert(sizeof(T) == 4, "this code currently only supports 32 bit integers"); + ret = ntohl(ret); + return ret; + } + +#endif /* ifndef DNSDIST */ + + template <class T, + typename std::enable_if<std::is_class<T>::value,T>::type* = nullptr> + T get() const; + + +#ifndef DNSDIST + template <class T, + typename std::enable_if<std::is_class<T>::value,T>::type* = nullptr> + T getNoStripHeader() const; +#endif + + MDB_val d_mdbval; +}; + +template<> inline std::string MDBOutVal::get<std::string>() const +{ +#ifndef DNSDIST + size_t offset = LMDBLS::LScheckHeaderAndGetSize(this); + + return std::string((char*)d_mdbval.mv_data+offset, d_mdbval.mv_size-offset); +} + +template<> inline std::string MDBOutVal::getNoStripHeader<std::string>() const +{ +#endif + return std::string((char*)d_mdbval.mv_data, d_mdbval.mv_size); +} + +template<> inline string_view MDBOutVal::get<string_view>() const +{ +#ifndef DNSDIST + size_t offset = LMDBLS::LScheckHeaderAndGetSize(this); + + return string_view((char*)d_mdbval.mv_data+offset, d_mdbval.mv_size-offset); +} + +template<> inline string_view MDBOutVal::getNoStripHeader<string_view>() const +{ +#endif + return string_view((char*)d_mdbval.mv_data, d_mdbval.mv_size); +} + +class MDBInVal +{ +public: + MDBInVal(const MDBOutVal& rhs): d_mdbval(rhs.d_mdbval) + { + } + +#ifndef DNSDIST + template <class T, + typename std::enable_if<std::is_integral<T>::value, + T>::type* = nullptr> + MDBInVal(T i) + { + static_assert(sizeof(T) == 4, "this code currently only supports 32 bit integers"); + auto j = htonl(i); // all actual usage in our codebase is 32 bits. If that ever changes, this will break the build and avoid runtime surprises + memcpy(&d_memory[0], &j, sizeof(j)); + + d_mdbval.mv_size = sizeof(T); + d_mdbval.mv_data = d_memory;; + } +#endif + + MDBInVal(const char* s) + { + d_mdbval.mv_size = strlen(s); + d_mdbval.mv_data = (void*)s; + } + + MDBInVal(const string_view& v) + { + d_mdbval.mv_size = v.size(); + d_mdbval.mv_data = (void*)&v[0]; + } + + MDBInVal(const std::string& v) + { + d_mdbval.mv_size = v.size(); + d_mdbval.mv_data = (void*)&v[0]; + } + + + template<typename T> + static MDBInVal fromStruct(const T& t) + { + MDBInVal ret; + ret.d_mdbval.mv_size = sizeof(T); + ret.d_mdbval.mv_data = (void*)&t; + return ret; + } + + operator MDB_val&() + { + return d_mdbval; + } + MDB_val d_mdbval; +private: + MDBInVal(){} +#ifndef DNSDIST + char d_memory[sizeof(double)]; +#endif +}; + +class MDBROCursor; + +class MDBROTransactionImpl +{ +protected: + MDBROTransactionImpl(MDBEnv *parent, MDB_txn *txn); + +private: + static MDB_txn *openROTransaction(MDBEnv *env, MDB_txn *parent, int flags=0); + + MDBEnv* d_parent; + std::vector<MDBROCursor*> d_cursors; + +protected: + MDB_txn* d_txn; + + void closeROCursors(); + +public: + explicit MDBROTransactionImpl(MDBEnv* parent, int flags=0); + + MDBROTransactionImpl(const MDBROTransactionImpl& src) = delete; + MDBROTransactionImpl &operator=(const MDBROTransactionImpl& src) = delete; + + // The move constructor/operator cannot be made safe due to Object Slicing with MDBRWTransaction. + MDBROTransactionImpl(MDBROTransactionImpl&& rhs) = delete; + MDBROTransactionImpl &operator=(MDBROTransactionImpl &&rhs) = delete; + + virtual ~MDBROTransactionImpl(); + + virtual void abort(); + virtual void commit(); + + int get(MDB_dbi dbi, const MDBInVal& key, MDBOutVal& val) + { + if(!d_txn) + throw std::runtime_error("Attempt to use a closed RO transaction for get"); + + int rc = mdb_get(d_txn, dbi, const_cast<MDB_val*>(&key.d_mdbval), + const_cast<MDB_val*>(&val.d_mdbval)); + + if(rc && rc != MDB_NOTFOUND) { + throw std::runtime_error("getting data: " + std::string(mdb_strerror(rc))); + } + +#ifndef DNSDIST + if(rc != MDB_NOTFOUND) { // key was found, value was retrieved + std::string sval = val.getNoStripHeader<std::string>(); + if (LMDBLS::LSisDeleted(sval)) { // but it was deleted + rc = MDB_NOTFOUND; + } + } +#endif + + return rc; + } + + int get(MDB_dbi dbi, const MDBInVal& key, string_view& val) + { + MDBOutVal out; + int rc = get(dbi, key, out); + if(!rc) + val = out.get<string_view>(); + return rc; + } + + + // this is something you can do, readonly + MDBDbi openDB(string_view dbname, int flags) + { + return MDBDbi( d_parent->d_env, d_txn, dbname, flags); + } + + MDBROCursor getCursor(const MDBDbi&); + MDBROCursor getROCursor(const MDBDbi&); + + operator MDB_txn*() + { + return d_txn; + } + + inline operator bool() const { + return d_txn; + } + + inline MDBEnv &environment() + { + return *d_parent; + } +}; + +/* + A cursor in a read-only transaction must be closed explicitly, before or after its transaction ends. It can be reused with mdb_cursor_renew() before finally closing it. + + "If the parent transaction commits, the cursor must not be used again." +*/ + +template<class Transaction, class T> +class MDBGenCursor +{ +private: + std::vector<T*> *d_registry; + MDB_cursor* d_cursor{nullptr}; +public: + MDB_txn* d_txn{nullptr}; // ew, public + uint64_t d_txtime{0}; + + MDBGenCursor(): + d_registry(nullptr), + d_cursor(nullptr), + d_txn(nullptr) + { + + } + + MDBGenCursor(std::vector<T*> ®istry, MDB_cursor *cursor, MDB_txn *txn=nullptr, uint64_t txtime=0): + d_registry(®istry), + d_cursor(cursor), + d_txn(txn), + d_txtime(txtime) + { + registry.emplace_back(static_cast<T*>(this)); + } + +private: + void move_from(MDBGenCursor *src) + { + if (!d_registry) { + return; + } + + auto iter = std::find(d_registry->begin(), + d_registry->end(), + src); + if (iter != d_registry->end()) { + *iter = static_cast<T*>(this); + } else { + d_registry->emplace_back(static_cast<T*>(this)); + } + } + +public: + MDBGenCursor(const MDBGenCursor &src) = delete; + + MDBGenCursor(MDBGenCursor &&src) noexcept: + d_registry(src.d_registry), + d_cursor(src.d_cursor) + { + move_from(&src); + src.d_registry = nullptr; + src.d_cursor = nullptr; + } + + MDBGenCursor &operator=(const MDBGenCursor &src) = delete; + + MDBGenCursor &operator=(MDBGenCursor &&src) noexcept + { + d_registry = src.d_registry; + d_cursor = src.d_cursor; + move_from(&src); + src.d_registry = nullptr; + src.d_cursor = nullptr; + return *this; + } + + ~MDBGenCursor() + { + close(); + } + + /* + to support (skip) entries marked deleted=1 in the LS header, we need to do some magic here + this table notes, for each cursor op: + * the maximum number of entries we may need to look at (1 or inf) + * the subsequent op that needs to be done to skip over a deleted entry (or MDB_NOTFOUND to give up and say no) + (table partially copied from http://www.lmdb.tech/doc/group__mdb.html#ga1206b2af8b95e7f6b0ef6b28708c9127 which I hope is a stable URL) + (ops only relevant for DUPSORT/DUPFIXED have been omitted) + (table is grouped by "skip op") + + | base op | maxentries | skip op | doc description of base op + | MDB_FIRST | inf | MDB_NEXT | Position at first key/data item + | MDB_NEXT | inf | MDB_NEXT | Position at next data item + | MDB_SET_RANGE | inf | MDB_NEXT | Position at first key greater than or equal to specified key. + | MDB_LAST | inf | MDB_PREV | Position at last key/data item + | MDB_PREV | inf | MDB_PREV | Position at previous data item + | MDB_GET_CURRENT | 1 | MDB_NOTFOUND | Return key/data at current cursor position + | MDB_SET | 1 | MDB_NOTFOUND | Position at specified key + | MDB_SET_KEY | 1 | MDB_NOTFOUND | Position at specified key, return key + data + */ + +private: + int skipDeleted(MDBOutVal& key, MDBOutVal& data, MDB_cursor_op op, int rc) + { +#ifndef DNSDIST + // when we get here + // * mdb_cursor_get has been called once + // * it did not return an error, but it might have returned MDB_NOTFOUND + // * if it returned MDB_NOTFOUND, there is nothing for us to do and we pass that on + + if (rc == MDB_NOTFOUND) { + return rc; + } + + // when we get here + // * mdb_cursor_get has been called at least once + // * it found an entry, as far as LMDB is concerned, so key+data contain something + // * but that might be a LS deleted=1 entry + // * we know the cursor op that got us here + + while (true) { + auto sval = data.getNoStripHeader<std::string_view>(); + + if (!LMDBLS::LSisDeleted(sval)) { + // done! + + return rc; + } + + // the found entry is set deleted, so we need to do something + + // if this was a 1-entry op, this is the end + if (op == MDB_GET_CURRENT || op == MDB_SET || op == MDB_SET_KEY) { + return MDB_NOTFOUND; + } + + // otherwise, we need to try to carry on + // all ops that do not map to NOTFOUND map to NEXT or PREV, including NEXT and PREV themselves + // so we just override the op to NEXT or PREV + if (op == MDB_FIRST || op == MDB_NEXT || op == MDB_SET_RANGE) { + op = MDB_NEXT; + } + else if (op == MDB_LAST || op == MDB_PREV) { + op = MDB_PREV; + } + else { + throw std::runtime_error("got unsupported mdb cursor op"); + } + + rc = mdb_cursor_get(d_cursor, &key.d_mdbval, &data.d_mdbval, op); + if(rc && rc != MDB_NOTFOUND) { + throw std::runtime_error("Unable to get from cursor: " + std::string(mdb_strerror(rc))); + } + + if (rc == MDB_NOTFOUND) { + // we ended up finding nothing, so tell the caller + return rc; + } + + // when we get here + // * the situation is just like the last time I wrote "when we get here" + // * except mdb_cursor_get has been called at least twice + // * so let's go back + } +#else /* ifndef DNSDIST */ + return rc; +#endif + } + +public: + int get(MDBOutVal& key, MDBOutVal& data, MDB_cursor_op op) + { + int rc = mdb_cursor_get(d_cursor, &key.d_mdbval, &data.d_mdbval, op); + if(rc && rc != MDB_NOTFOUND) + throw std::runtime_error("Unable to get from cursor: " + std::string(mdb_strerror(rc))); + return skipDeleted(key, data, op, rc); + } + + int find(const MDBInVal& in, MDBOutVal& key, MDBOutVal& data) + { + key.d_mdbval = in.d_mdbval; + int rc=mdb_cursor_get(d_cursor, const_cast<MDB_val*>(&key.d_mdbval), &data.d_mdbval, MDB_SET); + if(rc && rc != MDB_NOTFOUND) + throw std::runtime_error("Unable to find from cursor: " + std::string(mdb_strerror(rc))); + return skipDeleted(key, data, MDB_SET, rc); + } + + int lower_bound(const MDBInVal& in, MDBOutVal& key, MDBOutVal& data) + { + key.d_mdbval = in.d_mdbval; + + int rc = mdb_cursor_get(d_cursor, const_cast<MDB_val*>(&key.d_mdbval), &data.d_mdbval, MDB_SET_RANGE); + if(rc && rc != MDB_NOTFOUND) + throw std::runtime_error("Unable to lower_bound from cursor: " + std::string(mdb_strerror(rc))); + return skipDeleted(key, data, MDB_SET_RANGE, rc); + } + + + int nextprev(MDBOutVal& key, MDBOutVal& data, MDB_cursor_op op) + { + int rc = mdb_cursor_get(d_cursor, const_cast<MDB_val*>(&key.d_mdbval), &data.d_mdbval, op); + if(rc && rc != MDB_NOTFOUND) + throw std::runtime_error("Unable to prevnext from cursor: " + std::string(mdb_strerror(rc))); + return skipDeleted(key, data, op, rc); + } + + int next(MDBOutVal& key, MDBOutVal& data) + { + return nextprev(key, data, MDB_NEXT); + } + + int prev(MDBOutVal& key, MDBOutVal& data) + { + return nextprev(key, data, MDB_PREV); + } + + int currentlast(MDBOutVal& key, MDBOutVal& data, MDB_cursor_op op) + { + int rc = mdb_cursor_get(d_cursor, const_cast<MDB_val*>(&key.d_mdbval), &data.d_mdbval, op); + if(rc && rc != MDB_NOTFOUND) + throw std::runtime_error("Unable to next from cursor: " + std::string(mdb_strerror(rc))); + return skipDeleted(key, data, op, rc); + } + + int current(MDBOutVal& key, MDBOutVal& data) + { + return currentlast(key, data, MDB_GET_CURRENT); + } + int last(MDBOutVal& key, MDBOutVal& data) + { + return currentlast(key, data, MDB_LAST); + } + int first(MDBOutVal& key, MDBOutVal& data) + { + return currentlast(key, data, MDB_FIRST); + } + + operator MDB_cursor*() + { + return d_cursor; + } + + operator bool() const + { + return d_cursor; + } + + void close() + { + if (d_registry) { + auto iter = std::find(d_registry->begin(), + d_registry->end(), + static_cast<T*>(this)); + if (iter != d_registry->end()) { + d_registry->erase(iter); + } + d_registry = nullptr; + } + if (d_cursor) { + mdb_cursor_close(d_cursor); + d_cursor = nullptr; + } + } +}; + +class MDBROCursor : public MDBGenCursor<MDBROTransactionImpl, MDBROCursor> +{ +public: + MDBROCursor() = default; + using MDBGenCursor<MDBROTransactionImpl, MDBROCursor>::MDBGenCursor; + MDBROCursor(const MDBROCursor &src) = delete; + MDBROCursor(MDBROCursor &&src) = default; + MDBROCursor &operator=(const MDBROCursor &src) = delete; + MDBROCursor &operator=(MDBROCursor &&src) = default; + ~MDBROCursor() = default; + +}; + +class MDBRWCursor; + +class MDBRWTransactionImpl: public MDBROTransactionImpl +{ +protected: + MDBRWTransactionImpl(MDBEnv* parent, MDB_txn* txn); + +private: + static MDB_txn *openRWTransaction(MDBEnv* env, MDB_txn *parent, int flags); + +private: + std::vector<MDBRWCursor*> d_rw_cursors; + + uint64_t d_txtime{0}; + + void closeRWCursors(); + inline void closeRORWCursors() { + closeROCursors(); + closeRWCursors(); + } + +public: + explicit MDBRWTransactionImpl(MDBEnv* parent, int flags=0); + + MDBRWTransactionImpl(const MDBRWTransactionImpl& rhs) = delete; + MDBRWTransactionImpl(MDBRWTransactionImpl&& rhs) = delete; + MDBRWTransactionImpl &operator=(const MDBRWTransactionImpl& rhs) = delete; + MDBRWTransactionImpl &operator=(MDBRWTransactionImpl&& rhs) = delete; + + ~MDBRWTransactionImpl() override; + + void commit() override; + void abort() override; + + void clear(MDB_dbi dbi); + +#ifndef DNSDIST + void put(MDB_dbi dbi, const MDBInVal& key, const MDBInVal& val, int flags=0) + { + if(!d_txn) + throw std::runtime_error("Attempt to use a closed RW transaction for put"); + int rc; + + size_t txid = mdb_txn_id(d_txn); + + if (d_txtime == 0) { throw std::runtime_error("got zero txtime"); } + + std::string ins = + LMDBLS::LSheader(d_txtime, txid).toString()+ + std::string((const char*)val.d_mdbval.mv_data, val.d_mdbval.mv_size); + + MDBInVal pval = ins; + + if((rc=mdb_put(d_txn, dbi, + const_cast<MDB_val*>(&key.d_mdbval), + const_cast<MDB_val*>(&pval.d_mdbval), flags))) { + throw std::runtime_error("putting data: " + std::string(mdb_strerror(rc))); + } + } +#else + void put(MDB_dbi dbi, const MDBInVal& key, const MDBInVal& val, int flags=0) + { + if(!d_txn) + throw std::runtime_error("Attempt to use a closed RW transaction for put"); + int rc; + if((rc=mdb_put(d_txn, dbi, + const_cast<MDB_val*>(&key.d_mdbval), + const_cast<MDB_val*>(&val.d_mdbval), flags))) + throw std::runtime_error("putting data: " + std::string(mdb_strerror(rc))); + } +#endif + + int del(MDBDbi& dbi, const MDBInVal& key) + { + int rc; + rc=mdb_del(d_txn, dbi, (MDB_val*)&key.d_mdbval, 0); + if(rc && rc != MDB_NOTFOUND) + throw std::runtime_error("deleting data: " + std::string(mdb_strerror(rc))); +#ifndef DNSDIST + if(rc != MDB_NOTFOUND && LMDBLS::s_flag_deleted) { + // if it did exist, we need to mark it as deleted now + + size_t txid = mdb_txn_id(d_txn); + if (d_txtime == 0) { throw std::runtime_error("got zero txtime"); } + + std::string ins = + // std::string((const char*)&txid, sizeof(txid)) + + LMDBLS::LSheader(d_txtime, txid, LMDBLS::LS_FLAG_DELETED).toString(); + + MDBInVal pval = ins; + + if((rc=mdb_put(d_txn, dbi, + const_cast<MDB_val*>(&key.d_mdbval), + const_cast<MDB_val*>(&pval.d_mdbval), 0))) { + throw std::runtime_error("marking data deleted: " + std::string(mdb_strerror(rc))); + } + } +#endif + return rc; + } + + + int get(MDBDbi& dbi, const MDBInVal& key, MDBOutVal& val) + { + if(!d_txn) + throw std::runtime_error("Attempt to use a closed RW transaction for get"); + + int rc = mdb_get(d_txn, dbi, const_cast<MDB_val*>(&key.d_mdbval), + const_cast<MDB_val*>(&val.d_mdbval)); + if(rc && rc != MDB_NOTFOUND) { + throw std::runtime_error("getting data: " + std::string(mdb_strerror(rc))); + } + +#ifndef DNSDIST + if(rc != MDB_NOTFOUND) { // key was found, value was retrieved + auto sval = val.getNoStripHeader<std::string_view>(); + if (LMDBLS::LSisDeleted(sval)) { // but it was deleted + rc = MDB_NOTFOUND; + } + } +#endif + + return rc; + } + + MDBDbi openDB(string_view dbname, int flags) + { + return MDBDbi(environment().d_env, d_txn, dbname, flags); + } + + MDBRWCursor getRWCursor(const MDBDbi&); + MDBRWCursor getCursor(const MDBDbi&); + + MDBRWTransaction getRWTransaction(); + MDBROTransaction getROTransaction(); + +}; + +/* "A cursor in a write-transaction can be closed before its transaction ends, and will otherwise be closed when its transaction ends" + This is a problem for us since it may means we are closing the cursor twice, which is bad +*/ +class MDBRWCursor : public MDBGenCursor<MDBRWTransactionImpl, MDBRWCursor> +{ +public: + MDBRWCursor() = default; + using MDBGenCursor<MDBRWTransactionImpl, MDBRWCursor>::MDBGenCursor; + MDBRWCursor(const MDBRWCursor &src) = delete; + MDBRWCursor(MDBRWCursor &&src) = default; + MDBRWCursor &operator=(const MDBRWCursor &src) = delete; + MDBRWCursor &operator=(MDBRWCursor &&src) = default; + ~MDBRWCursor() = default; + +#ifndef DNSDIST + void put(const MDBOutVal& key, const MDBInVal& data) + { + size_t txid = mdb_txn_id(this->d_txn); + + if (d_txtime == 0) { throw std::runtime_error("got zero txtime"); } + + std::string ins = + LMDBLS::LSheader(d_txtime, txid).toString()+ + std::string((const char*)data.d_mdbval.mv_data, data.d_mdbval.mv_size); + + MDBInVal pval = ins; + + int rc = mdb_cursor_put(*this, + const_cast<MDB_val*>(&key.d_mdbval), + const_cast<MDB_val*>(&pval.d_mdbval), MDB_CURRENT); + if(rc) + throw std::runtime_error("mdb_cursor_put: " + std::string(mdb_strerror(rc))); + } +#else + void put(const MDBOutVal& key, const MDBInVal& data) + { + int rc = mdb_cursor_put(*this, + const_cast<MDB_val*>(&key.d_mdbval), + const_cast<MDB_val*>(&data.d_mdbval), MDB_CURRENT); + if(rc) + throw std::runtime_error("mdb_cursor_put: " + std::string(mdb_strerror(rc))); + } +#endif + +#ifndef DNSDIST + int del(int flags=0) + { + MDBOutVal key, val; + + if (LMDBLS::s_flag_deleted) { + int rc_get = mdb_cursor_get (*this, &key.d_mdbval, &val.d_mdbval, MDB_GET_CURRENT); + + if(rc_get) { + throw std::runtime_error("getting key to mark data as deleted: " + std::string(mdb_strerror(rc_get))); + } + + size_t txid = mdb_txn_id(d_txn); + if (d_txtime == 0) { throw std::runtime_error("got zero txtime"); } + + std::string ins = + LMDBLS::LSheader(d_txtime, txid, LMDBLS::LS_FLAG_DELETED).toString(); + + std::string skey((const char*)key.d_mdbval.mv_data, key.d_mdbval.mv_size); + + MDBInVal pkey = MDBInVal(skey); + MDBInVal pval = ins; + + int rc_put = mdb_cursor_put(*this, + const_cast<MDB_val*>(&pkey.d_mdbval), + const_cast<MDB_val*>(&pval.d_mdbval), 0 /* MDB_CURRENT */); + if(rc_put) { + throw std::runtime_error("marking data deleted: " + std::string(mdb_strerror(rc_put))); + } + return rc_put; + } + else { + // do a normal delete + return mdb_cursor_del(*this, flags); + } + } +#endif +}; diff --git a/ext/luawrapper/include/LuaContext.hpp b/ext/luawrapper/include/LuaContext.hpp new file mode 100644 index 0000000..ad6c86e --- /dev/null +++ b/ext/luawrapper/include/LuaContext.hpp @@ -0,0 +1,3009 @@ +/* +Copyright (c) 2013, Pierre KRIEGER +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the <organization> nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef INCLUDE_LUACONTEXT_HPP +#define INCLUDE_LUACONTEXT_HPP + +#include <algorithm> +#include <array> +#include <cassert> +#include <cmath> +#include <cstring> +#include <functional> +#include <limits> +#include <list> +#include <map> +#include <memory> +#include <random> +#include <set> +#include <stdexcept> +#include <string> +#include <sstream> +#include <tuple> +#include <type_traits> +#include <unordered_map> +#include <boost/any.hpp> +#include <boost/format.hpp> +#include <boost/mpl/distance.hpp> +#include <boost/mpl/transform.hpp> +#include <boost/optional.hpp> +#include <boost/variant.hpp> +#include <boost/type_traits.hpp> +#include <lua.hpp> + +#if defined(_MSC_VER) && _MSC_VER < 1900 +# include "misc/exception.hpp" +#endif + +#ifdef __GNUC__ +# define ATTR_UNUSED __attribute__((unused)) +#else +# define ATTR_UNUSED +#endif + +#define LUACONTEXT_GLOBAL_EQ "e5ddced079fc405aa4937b386ca387d2" +#define EQ_FUNCTION_NAME "__eq" +#define TOSTRING_FUNCTION_NAME "__tostring" + +/** + * Defines a Lua context + * A Lua context is used to interpret Lua code. Since everything in Lua is a variable (including functions), + * we only provide few functions like readVariable and writeVariable. + * + * You can also write variables with C++ functions so that they are callable by Lua. Note however that you HAVE TO convert + * your function to std::function (not directly std::bind or a lambda function) so the class can detect which argument types + * it wants. These arguments may only be of basic types (int, float, etc.) or std::string. + */ + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + +class LuaContext { + struct ValueInRegistry; + template<typename TFunctionObject, typename TFirstParamType> struct Binder; + template<typename T> struct IsOptional; + enum Globals_t { Globals }; // tag for "global variables" +public: + /** + * @param openDefaultLibs True if luaL_openlibs should be called + */ + explicit LuaContext(bool openDefaultLibs = true) + { + // luaL_newstate can return null if allocation failed + mState = luaL_newstate(); + if (mState == nullptr) + throw std::bad_alloc(); + + // setting the panic function + lua_atpanic(mState, [](lua_State* state) -> int { + const std::string str = lua_tostring(state, -1); + lua_pop(state, 1); + assert(false && "lua_atpanic triggered"); + exit(0); + }); + + // opening default library if required to do so + if (openDefaultLibs) + luaL_openlibs(mState); + + writeGlobalEq(); + } + + void writeGlobalEq() { + const auto eqFunction = [](lua_State* lua) -> int { + try { + lua_pushstring(lua, "__eq"); + lua_gettable(lua, -2); + /* if not found, return false */ + if (lua_isnil(lua, -1)) { + lua_pop(lua, -2); + lua_pushboolean(lua, false); + return 1; + } + lua_insert(lua, lua_gettop(lua)-2); + return callRaw(lua, PushedObject{lua, 3}, 1).release(); + } catch(...) { + Pusher<std::exception_ptr>::push(lua, std::current_exception()).release(); + luaError(lua); + } + }; + lua_pushcfunction(mState, eqFunction); + lua_setglobal(mState, LUACONTEXT_GLOBAL_EQ); + }; + + /** + * Move constructor + */ + LuaContext(LuaContext&& s) : + mState(s.mState) + { + s.mState = luaL_newstate(); + } + + /** + * Move operator + */ + LuaContext& operator=(LuaContext&& s) noexcept + { + std::swap(mState, s.mState); + return *this; + } + + /** + * Copy is forbidden + */ + LuaContext(const LuaContext&) = delete; + + /** + * Copy is forbidden + */ + LuaContext& operator=(const LuaContext&) = delete; + + /** + * Destructor + */ + ~LuaContext() noexcept + { + assert(mState); + lua_close(mState); + } + + /** + * Thrown when an error happens during execution of lua code (like not enough parameters for a function) + */ + class ExecutionErrorException : public std::runtime_error + { + public: + ExecutionErrorException(const std::string& msg) : + std::runtime_error(msg) + { + } + }; + + /** + * Thrown when a syntax error happens in a lua script + */ + class SyntaxErrorException : public std::runtime_error + { + public: + SyntaxErrorException(const std::string& msg) : + std::runtime_error(msg) + { + } + }; + + /** + * Thrown when trying to cast a Lua variable to an unvalid type, eg. trying to read a number when the variable is a string + */ + class WrongTypeException : public std::runtime_error + { + public: + WrongTypeException(const std::string& luaType_, const std::type_info& destination_) : + std::runtime_error("Trying to cast a lua variable from \"" + luaType_ + "\" to \"" + destination_.name() + "\""), + luaType(luaType_), + destination(destination_) + { + } + + std::string luaType; + const std::type_info& destination; + }; + + /** + * Function object that can call a function stored by Lua + * This type is copiable and movable, but not constructible. It can only be created through readVariable. + * @tparam TFunctionType Function type (eg. "int (int, bool)") + */ + template<typename TFunctionType> + class LuaFunctionCaller; + + /** + * Opaque type that identifies a Lua object + */ + struct LuaObject { + LuaObject() = default; + LuaObject(lua_State* state, int index=-1) { + this->objectInRegistry = std::make_shared<LuaContext::ValueInRegistry>(state, index); + } + std::shared_ptr<LuaContext::ValueInRegistry> objectInRegistry; + }; + + /** + * Opaque type that identifies a Lua thread + */ + struct ThreadID { + ThreadID() = default; + ThreadID(ThreadID&& o) : state(o.state), threadInRegistry(std::move(o.threadInRegistry)) { } + ThreadID& operator=(ThreadID&& o) { std::swap(state, o.state); std::swap(threadInRegistry, o.threadInRegistry); return *this; } + public: + friend LuaContext; + lua_State* state; + std::unique_ptr<ValueInRegistry> threadInRegistry; + }; + + /** + * Type that is considered as an empty array + */ + enum EmptyArray_t { EmptyArray }; + + /** + * Type for a metatable + */ + enum Metatable_t { Metatable }; + + /** + * Executes lua code from the stream + * @param code A stream that Lua will read its code from + */ + void executeCode(std::istream& code) + { + auto toCall = load(mState, code); + call<std::tuple<>>(mState, std::move(toCall)); + } + + /** + * Executes lua code from the stream and returns a value + * @param code A stream that Lua will read its code from + * @tparam TType The type that the executing code should return + */ + template<typename TType> + auto executeCode(std::istream& code) + -> TType + { + auto toCall = load(mState, code); + return call<TType>(mState, std::move(toCall)); + } + + /** + * Executes lua code given as parameter + * @param code A string containing code that will be executed by Lua + */ + void executeCode(const std::string& code) + { + executeCode(code.c_str()); + } + + /* + * Executes Lua code from the stream and returns a value + * @param code A string containing code that will be executed by Lua + * @tparam TType The type that the executing code should return + */ + template<typename TType> + auto executeCode(const std::string& code) + -> TType + { + return executeCode<TType>(code.c_str()); + } + + /** + * Executes Lua code + * @param code A string containing code that will be executed by Lua + */ + void executeCode(const char* code) + { + auto toCall = load(mState, code); + call<std::tuple<>>(mState, std::move(toCall)); + } + + /* + * Executes Lua code from the stream and returns a value + * @param code A string containing code that will be executed by Lua + * @tparam TType The type that the executing code should return + */ + template<typename TType> + auto executeCode(const char* code) + -> TType + { + auto toCall = load(mState, code); + return call<TType>(mState, std::move(toCall)); + } + + /** + * Executes lua code from the stream + * @param code A stream that Lua will read its code from + */ + void executeCode(const ThreadID& thread, std::istream& code) + { + auto toCall = load(thread.state, code); + call<std::tuple<>>(thread.state, std::move(toCall)); + } + + /** + * Executes lua code from the stream and returns a value + * @param code A stream that Lua will read its code from + * @tparam TType The type that the executing code should return + */ + template<typename TType> + auto executeCode(const ThreadID& thread, std::istream& code) + -> TType + { + auto toCall = load(thread.state, code); + return call<TType>(thread.state, std::move(toCall)); + } + + /** + * Executes lua code given as parameter + * @param code A string containing code that will be executed by Lua + */ + void executeCode(const ThreadID& thread, const std::string& code) + { + executeCode(thread, code.c_str()); + } + + /* + * Executes Lua code from the stream and returns a value + * @param code A string containing code that will be executed by Lua + * @tparam TType The type that the executing code should return + */ + template<typename TType> + auto executeCode(const ThreadID& thread, const std::string& code) + -> TType + { + return executeCode<TType>(thread, code.c_str()); + } + + /** + * Executes Lua code + * @param code A string containing code that will be executed by Lua + */ + void executeCode(const ThreadID& thread, const char* code) + { + auto toCall = load(thread.state, code); + call<std::tuple<>>(thread.state, std::move(toCall)); + } + + /* + * Executes Lua code from the stream and returns a value + * @param code A string containing code that will be executed by Lua + * @tparam TType The type that the executing code should return + */ + template<typename TType> + auto executeCode(const ThreadID& thread, const char* code) + -> TType + { + auto toCall = load(thread.state, code); + return call<TType>(thread.state, std::move(toCall)); + } + + /** + * Tells that Lua will be allowed to access an object's function + * This is the version "registerFunction(name, &Foo::function)" + */ + template<typename TPointerToMemberFunction> + auto registerFunction(const std::string& name, TPointerToMemberFunction pointer) + -> typename std::enable_if<std::is_member_function_pointer<TPointerToMemberFunction>::value>::type + { + registerFunctionImpl(name, std::mem_fn(pointer), tag<TPointerToMemberFunction>{}); + } + + /** + * Tells that Lua will be allowed to access an object's function + * This is the version with an explicit template parameter: "registerFunction<void (Foo::*)()>(name, [](Foo&) { })" + * @param fn Function object which takes as first parameter a reference to the object + * @tparam TFunctionType Pointer-to-member function type + */ + template<typename TFunctionType, typename TType> + void registerFunction(const std::string& functionName, TType fn) + { + static_assert(std::is_member_function_pointer<TFunctionType>::value, "registerFunction must take a member function pointer type as template parameter"); + registerFunctionImpl(functionName, std::move(fn), tag<TFunctionType>{}); + } + + /** + * Tells that Lua will be allowed to access an object's function + * This is the alternative version with an explicit template parameter: "registerFunction<Foo, void (*)()>(name, [](Foo&) { })" + * @param fn Function object which takes as first parameter a reference to the object + * @tparam TObject Object to register this function to + * @tparam TFunctionType Function type + */ + template<typename TObject, typename TFunctionType, typename TType> + void registerFunction(const std::string& functionName, TType fn) + { + static_assert(std::is_function<TFunctionType>::value, "registerFunction must take a function type as template parameter"); + registerFunctionImpl(functionName, std::move(fn), tag<TObject>{}, tag<TFunctionType>{}); + } + + /** + * Wrappers for registering "__eq" function in case we want to change this to something else some day + */ + + template<typename TPointerToMemberFunction> + auto registerEqFunction(TPointerToMemberFunction pointer) + -> typename std::enable_if<std::is_member_function_pointer<TPointerToMemberFunction>::value>::type + { + registerFunctionImpl(EQ_FUNCTION_NAME, std::mem_fn(pointer), tag<TPointerToMemberFunction>{}); + } + + template<typename TFunctionType, typename TType> + void registerEqFunction(TType fn) + { + static_assert(std::is_member_function_pointer<TFunctionType>::value, "registerFunction must take a member function pointer type as template parameter"); + registerFunctionImpl(EQ_FUNCTION_NAME, std::move(fn), tag<TFunctionType>{}); + } + + template<typename TObject, typename TFunctionType, typename TType> + void registerEqFunction(TType fn) + { + static_assert(std::is_function<TFunctionType>::value, "registerFunction must take a function type as template parameter"); + registerFunctionImpl(EQ_FUNCTION_NAME, std::move(fn), tag<TObject>{}, tag<TFunctionType>{}); + } + + /** + * Wrappers for registering "__tostring" function in case we want to change this to something else some day + */ + + template<typename TPointerToMemberFunction> + auto registerToStringFunction(TPointerToMemberFunction pointer) + -> typename std::enable_if<std::is_member_function_pointer<TPointerToMemberFunction>::value>::type + { + registerFunctionImpl(TOSTRING_FUNCTION_NAME, std::mem_fn(pointer), tag<TPointerToMemberFunction>{}); + } + + template<typename TFunctionType, typename TType> + void registerToStringFunction(TType fn) + { + static_assert(std::is_member_function_pointer<TFunctionType>::value, "registerFunction must take a member function pointer type as template parameter"); + registerFunctionImpl(TOSTRING_FUNCTION_NAME, std::move(fn), tag<TFunctionType>{}); + } + + template<typename TObject, typename TFunctionType, typename TType> + void registerToStringFunction(TType fn) + { + static_assert(std::is_function<TFunctionType>::value, "registerFunction must take a function type as template parameter"); + registerFunctionImpl(TOSTRING_FUNCTION_NAME, std::move(fn), tag<TObject>{}, tag<TFunctionType>{}); + } + + /** + * Inverse operation of registerFunction + * @tparam TType Type whose function belongs to + */ + template<typename TType> + void unregisterFunction(const std::string& /*functionName*/) + { + lua_pushlightuserdata(mState, const_cast<std::type_info*>(&typeid(TType))); + lua_pushnil(mState); + lua_settable(mState, LUA_REGISTRYINDEX); + checkTypeRegistration(mState, &typeid(TType)); + + lua_pushlightuserdata(mState, const_cast<std::type_info*>(&typeid(TType*))); + lua_pushnil(mState); + lua_settable(mState, LUA_REGISTRYINDEX); + checkTypeRegistration(mState, &typeid(TType*)); + + lua_pushlightuserdata(mState, const_cast<std::type_info*>(&typeid(std::shared_ptr<TType>))); + lua_pushnil(mState); + lua_settable(mState, LUA_REGISTRYINDEX); + checkTypeRegistration(mState, &typeid(std::shared_ptr<TType>)); + } + + /** + * Registers a member variable + * This is the version "registerMember(name, &Foo::member)" + */ + template<typename TObject, typename TVarType> + void registerMember(const std::string& name, TVarType TObject::*member) + { + // implementation simply calls the custom member with getter and setter + const auto getter = [=](const TObject& obj) -> TVarType { return obj.*member; }; + const auto setter = [=](TObject& obj, const TVarType& value) { obj.*member = value; }; + registerMember<TVarType (TObject::*)>(name, getter, setter); + } + + /** + * Registers a member variable + * This is the version "registerMember<Foo, int>(name, getter, setter)" + * @tparam TObject Type to register the member to + * @tparam TVarType Type of the member + * @param name Name of the member to register + * @param readFunction Function of type "TVarType (const TObject&)" + * @param writeFunction_ Function of type "void (TObject&, const TVarType&)" + */ + template<typename TObject, typename TVarType, typename TReadFunction, typename TWriteFunction> + void registerMember(const std::string& name, TReadFunction readFunction, TWriteFunction writeFunction_) + { + registerMemberImpl<TObject,TVarType>(name, std::move(readFunction), std::move(writeFunction_)); + } + + /** + * Registers a member variable + * This is the version "registerMember<int (Foo::*)>(name, getter, setter)" + * @tparam TMemberType Pointer to member object representing the type + * @param name Name of the member to register + * @param readFunction Function of type "TVarType (const TObject&)" + * @param writeFunction_ Function of type "void (TObject&, const TVarType&)" + */ + template<typename TMemberType, typename TReadFunction, typename TWriteFunction> + void registerMember(const std::string& name, TReadFunction readFunction, TWriteFunction writeFunction_) + { + static_assert(std::is_member_object_pointer<TMemberType>::value, "registerMember must take a member object pointer type as template parameter"); + registerMemberImpl(tag<TMemberType>{}, name, std::move(readFunction), std::move(writeFunction_)); + } + + /** + * Registers a non-modifiable member variable + * This is the version "registerMember<Foo, int>(name, getter)" + * @tparam TObject Type to register the member to + * @tparam TVarType Type of the member + * @param name Name of the member to register + * @param readFunction Function of type "TVarType (const TObject&)" + */ + template<typename TObject, typename TVarType, typename TReadFunction> + void registerMember(const std::string& name, TReadFunction readFunction) + { + registerMemberImpl<TObject,TVarType>(name, std::move(readFunction)); + } + + /** + * Registers a non-modifiable member variable + * This is the version "registerMember<int (Foo::*)>(name, getter)" + * @tparam TMemberType Pointer to member object representing the type + * @param name Name of the member to register + * @param readFunction Function of type "TVarType (const TObject&)" + */ + template<typename TMemberType, typename TReadFunction> + void registerMember(const std::string& name, TReadFunction readFunction) + { + static_assert(std::is_member_object_pointer<TMemberType>::value, "registerMember must take a member object pointer type as template parameter"); + registerMemberImpl(tag<TMemberType>{}, name, std::move(readFunction)); + } + + /** + * Registers a dynamic member variable + * This is the version "registerMember<Foo, int>(getter, setter)" + * @tparam TObject Type to register the member to + * @tparam TVarType Type of the member + * @param readFunction Function of type "TVarType (const TObject&, const std::string&)" + * @param writeFunction_ Function of type "void (TObject&, const std::string&, const TVarType&)" + */ + template<typename TObject, typename TVarType, typename TReadFunction, typename TWriteFunction> + void registerMember(TReadFunction readFunction, TWriteFunction writeFunction_) + { + registerMemberImpl<TObject,TVarType>(std::move(readFunction), std::move(writeFunction_)); + } + + /** + * Registers a dynamic member variable + * This is the version "registerMember<int (Foo::*)>(getter, setter)" + * @tparam TMemberType Pointer to member object representing the type + * @param readFunction Function of type "TVarType (const TObject&, const std::string&)" + * @param writeFunction_ Function of type "void (TObject&, const std::string&, const TVarType&)" + */ + template<typename TMemberType, typename TReadFunction, typename TWriteFunction> + void registerMember(TReadFunction readFunction, TWriteFunction writeFunction_) + { + static_assert(std::is_member_object_pointer<TMemberType>::value, "registerMember must take a member object pointer type as template parameter"); + registerMemberImpl(tag<TMemberType>{}, std::move(readFunction), std::move(writeFunction_)); + } + + /** + * Registers a dynamic non-modifiable member variable + * This is the version "registerMember<Foo, int>(getter)" + * @tparam TObject Type to register the member to + * @tparam TVarType Type of the member + * @param readFunction Function of type "TVarType (const TObject&, const std::string&)" + */ + template<typename TObject, typename TVarType, typename TReadFunction> + void registerMember(TReadFunction readFunction) + { + registerMemberImpl<TObject, TVarType>(std::move(readFunction)); + } + + /** + * Registers a dynamic non-modifiable member variable + * This is the version "registerMember<int (Foo::*)>(getter)" + * @tparam TMemberType Pointer to member object representing the type + * @param readFunction Function of type "TVarType (const TObject&, const std::string&)" + */ + template<typename TMemberType, typename TReadFunction> + void registerMember(TReadFunction readFunction) + { + static_assert(std::is_member_object_pointer<TMemberType>::value, "registerMember must take a member object pointer type as template parameter"); + registerMemberImpl(tag<TMemberType>{}, std::move(readFunction)); + } + + /** + * Creates a new thread + * A Lua thread is not really a thread, but rather an "execution stack". + * You can destroy the thread by calling destroyThread + * @sa destroyThread + */ + auto createThread() + -> ThreadID + { + ThreadID result; + + result.state = lua_newthread(mState); + result.threadInRegistry = std::unique_ptr<ValueInRegistry>(new ValueInRegistry(mState)); + lua_pop(mState, 1); + + return result; + } + + /** + * Destroys a thread created with createThread + * @sa createThread + */ + void destroyThread(ThreadID& id) + { + id.threadInRegistry.reset(); + } + + /** + * Reads the content of a Lua variable + * + * @tparam TType Type requested for the read + * @throw WrongTypeException When the variable is not convertible to the requested type + * @sa writeVariable + * + * Readable types are all types accepted by writeVariable except nullptr, std::unique_ptr and function pointers + * Additionally supported: + * - LuaFunctionCaller<FunctionType>, which is an alternative to std::function + * - references to custom objects, in which case it will return the object in-place + * + * After the variable name, you can add other parameters. + * If the variable is an array, it will instead get the element of that array whose offset is the second parameter. + * Same applies for third, fourth, etc. parameters. + */ + template<typename TType, typename... TTypes> + TType readVariable(const std::string& name, TTypes&&... elements) const + { + lua_getglobal(mState, name.c_str()); + lookIntoStackTop(mState, std::forward<TTypes>(elements)...); + return readTopAndPop<TType>(mState, PushedObject{mState, 1}); + } + + /** + * @sa readVariable + */ + template<typename TType, typename... TTypes> + TType readVariable(const char* name, TTypes&&... elements) const + { + lua_getglobal(mState, name); + lookIntoStackTop(mState, std::forward<TTypes>(elements)...); + return readTopAndPop<TType>(mState, PushedObject{mState, 1}); + } + + /** + * @sa readVariable + */ + template<typename TType, typename... TTypes> + TType readVariable(const ThreadID& thread, const std::string& name, TTypes&&... elements) const + { + lua_getglobal(thread.state, name.c_str()); + lookIntoStackTop(thread.state, std::forward<TTypes>(elements)...); + return readTopAndPop<TType>(thread.state, PushedObject{thread.state, 1}); + } + + /** + * @sa readVariable + */ + template<typename TType, typename... TTypes> + TType readVariable(const ThreadID& thread, const char* name, TTypes&&... elements) const + { + lua_getglobal(thread.state, name); + lookIntoStackTop(thread.state, std::forward<TTypes>(elements)...); + return readTopAndPop<TType>(thread.state, PushedObject{thread.state, 1}); + } + + /** + * Changes the content of a Lua variable + * + * Accepted values are: + * - all base types (char, short, int, float, double, bool) + * - std::string + * - enums + * - std::vector<> + * - std::vector<std::pair<>>, std::map<> and std::unordered_map<> (the key and value must also be accepted values) + * - std::function<> (all parameters must be accepted values, and return type must be either an accepted value for readVariable or a tuple) + * - std::shared_ptr<> (std::unique_ptr<> are converted to std::shared_ptr<>) + * - nullptr (writes nil) + * - any object + * + * All objects are passed by copy and destroyed by the garbage collector if necessary. + */ + template<typename... TData> + void writeVariable(TData&&... data) noexcept { + static_assert(sizeof...(TData) >= 2, "You must pass at least a variable name and a value to writeVariable"); + typedef typename std::decay<typename std::tuple_element<sizeof...(TData) - 1,std::tuple<TData...>>::type>::type + RealDataType; + static_assert(!std::is_same<typename Tupleizer<RealDataType>::type,RealDataType>::value, "Error: you can't use LuaContext::writeVariable with a tuple"); + + setTable<RealDataType>(mState, Globals, std::forward<TData>(data)...); + } + + /** + * Equivalent to writeVariable(varName, ..., std::function<TFunctionType>(data)); + * This version is more efficient than writeVariable if you want to write functions + */ + template<typename TFunctionType, typename... TData> + void writeFunction(TData&&... data) noexcept { + static_assert(sizeof...(TData) >= 2, "You must pass at least a variable name and a value to writeFunction"); + + setTable<TFunctionType>(mState, Globals, std::forward<TData>(data)...); + } + + /** + * Same as the other writeFunction, except that the template parameter is automatically detected + * This only works if the data is either a native function pointer, or contains one operator() (this is the case for lambdas) + */ + template<typename... TData> + void writeFunction(TData&&... data) noexcept { + static_assert(sizeof...(TData) >= 2, "You must pass at least a variable name and a value to writeFunction"); + typedef typename std::decay<typename std::tuple_element<sizeof...(TData) - 1,std::tuple<TData...>>::type>::type + RealDataType; + typedef typename FunctionTypeDetector<RealDataType>::type + DetectedFunctionType; + + return writeFunction<DetectedFunctionType>(std::forward<TData>(data)...); + } + + +private: + // the state is the most important variable in the class since it is our interface with Lua + // - registered members and functions are stored in tables at offset &typeid(type) of the registry + // each table has its getter functions at offset 0, getter members at offset 1, default getter at offset 2 + // offset 3 is unused, setter members at offset 4, default setter at offset 5 + lua_State* mState; + + + /**************************************************/ + /* PUSH OBJECT */ + /**************************************************/ + struct PushedObject { + PushedObject(lua_State* state_, int num_ = 1) : state(state_), num(num_) {} + ~PushedObject() { assert(lua_gettop(state) >= num); if (num >= 1) lua_pop(state, num); } + + PushedObject& operator=(const PushedObject&) = delete; + PushedObject(const PushedObject&) = delete; + PushedObject& operator=(PushedObject&& other) { std::swap(state, other.state); std::swap(num, other.num); return *this; } + PushedObject(PushedObject&& other) : state(other.state), num(other.num) { other.num = 0; } + + PushedObject operator+(PushedObject&& other) && { PushedObject obj(state, num + other.num); num = 0; other.num = 0; return obj; } + void operator+=(PushedObject&& other) { assert(state == other.state); num += other.num; other.num = 0; } + + auto getState() const -> lua_State* { return state; } + auto getNum() const -> int { return num; } + + int release() { const auto n = num; num = 0; return n; } + void pop() { if (num >= 1) lua_pop(state, num); num = 0; } + void pop(int n) { assert(num >= n); lua_pop(state, n); num -= n; } + + private: + lua_State* state; + int num = 0; + }; + + + /**************************************************/ + /* MISC */ + /**************************************************/ + // type used as a tag + template<typename T> + struct tag {}; + + // tag for "the registry" + enum RegistryTag { Registry }; + + // this function takes a value representing the offset to look into + // it will look into the top element of the stack and replace the element by its content at the given index + template<typename OffsetType1, typename... OffsetTypeOthers> + static void lookIntoStackTop(lua_State* state, OffsetType1&& offset1, OffsetTypeOthers&&... offsetOthers) { + static_assert(Pusher<typename std::decay<OffsetType1>::type>::minSize == 1 && Pusher<typename std::decay<OffsetType1>::type>::maxSize == 1, "Impossible to have a multiple-values index"); + auto p1 = Pusher<typename std::decay<OffsetType1>::type>::push(state, offset1); + lua_gettable(state, -2); + lua_remove(state, -2); + p1.release(); + + lookIntoStackTop(state, std::forward<OffsetTypeOthers>(offsetOthers)...); + } + + template<typename... OffsetTypeOthers> + static void lookIntoStackTop(lua_State* state, Metatable_t, OffsetTypeOthers&&... offsetOthers) { + lua_getmetatable(state, -1); + lua_remove(state, -2); + + lookIntoStackTop(state, std::forward<OffsetTypeOthers>(offsetOthers)...); + } + + static void lookIntoStackTop(lua_State*) { + } + + // equivalent of lua_settable with t[k]=n, where t is the value at the index in the template parameter, k is the second parameter, n is the last parameter, and n is pushed by the function in the first parameter + // if there are more than 3 parameters, parameters 3 to n-1 are considered as sub-indices into the array + // the dataPusher MUST push only one thing on the stack + // TTableIndex must be either LUA_REGISTRYINDEX, LUA_GLOBALSINDEX, LUA_ENVINDEX, or the position of the element on the stack + template<typename TDataType, typename TIndex, typename TData> + static void setTable(lua_State* state, const PushedObject&, TIndex&& index, TData&& data) noexcept + { + static_assert(Pusher<typename std::decay<TIndex>::type>::minSize == 1 && Pusher<typename std::decay<TIndex>::type>::maxSize == 1, "Impossible to have a multiple-values index"); + static_assert(Pusher<typename std::decay<TDataType>::type>::minSize == 1 && Pusher<typename std::decay<TDataType>::type>::maxSize == 1, "Impossible to have a multiple-values data"); + + auto p1 = Pusher<typename std::decay<TIndex>::type>::push(state, index); + auto p2 = Pusher<typename std::decay<TDataType>::type>::push(state, std::forward<TData>(data)); + + lua_settable(state, -3); + p1.release(); + p2.release(); + } + + template<typename TDataType, typename TData> + static void setTable(lua_State* state, const PushedObject&, const std::string& index, TData&& data) noexcept + { + static_assert(Pusher<typename std::decay<TDataType>::type>::minSize == 1 && Pusher<typename std::decay<TDataType>::type>::maxSize == 1, "Impossible to have a multiple-values data"); + + auto p1 = Pusher<typename std::decay<TDataType>::type>::push(state, std::forward<TData>(data)); + lua_setfield(state, -2, index.c_str()); + p1.release(); + } + + template<typename TDataType, typename TData> + static void setTable(lua_State* state, const PushedObject&, const char* index, TData&& data) noexcept + { + static_assert(Pusher<typename std::decay<TDataType>::type>::minSize == 1 && Pusher<typename std::decay<TDataType>::type>::maxSize == 1, "Impossible to have a multiple-values data"); + + auto p1 = Pusher<typename std::decay<TDataType>::type>::push(state, std::forward<TData>(data)); + lua_setfield(state, -2, index); + p1.release(); + } + + template<typename TDataType, typename TData> + static void setTable(lua_State* state, const PushedObject&, Metatable_t, TData&& data) noexcept + { + static_assert(Pusher<typename std::decay<TDataType>::type>::minSize == 1 && Pusher<typename std::decay<TDataType>::type>::maxSize == 1, "Impossible to have a multiple-values data"); + + auto p1 = Pusher<typename std::decay<TDataType>::type>::push(state, std::forward<TData>(data)); + lua_setmetatable(state, -2); + p1.release(); + } + + template<typename TDataType, typename TIndex1, typename TIndex2, typename TIndex3, typename... TIndices> + static auto setTable(lua_State* state, PushedObject&, TIndex1&& index1, TIndex2&& index2, TIndex3&& index3, TIndices&&... indices) noexcept + -> typename std::enable_if<!std::is_same<typename std::decay<TIndex1>::type, Metatable_t>::value>::type + { + static_assert(Pusher<typename std::decay<TIndex1>::type>::minSize == 1 && Pusher<typename std::decay<TIndex1>::type>::maxSize == 1, "Impossible to have a multiple-values index"); + + auto p1 = Pusher<typename std::decay<TIndex1>::type>::push(state, std::forward<TIndex1>(index1)); + lua_gettable(state, -2); + + setTable<TDataType>(state, std::move(p1), std::forward<TIndex2>(index2), std::forward<TIndex3>(index3), std::forward<TIndices>(indices)...); + } + + template<typename TDataType, typename TIndex1, typename TIndex2, typename TIndex3, typename... TIndices> + static auto setTable(lua_State* state, PushedObject&& pushedTable, TIndex1&& index1, TIndex2&& index2, TIndex3&& index3, TIndices&&... indices) noexcept + -> typename std::enable_if<!std::is_same<typename std::decay<TIndex1>::type, Metatable_t>::value>::type + { + static_assert(Pusher<typename std::decay<TIndex1>::type>::minSize == 1 && Pusher<typename std::decay<TIndex1>::type>::maxSize == 1, "Impossible to have a multiple-values index"); + + auto p1 = Pusher<typename std::decay<TIndex1>::type>::push(state, std::forward<TIndex1>(index1)) + std::move(pushedTable); + lua_gettable(state, -2); + + setTable<TDataType>(state, std::move(p1), std::forward<TIndex2>(index2), std::forward<TIndex3>(index3), std::forward<TIndices>(indices)...); + } + + template<typename TDataType, typename TIndex2, typename TIndex3, typename... TIndices> + static void setTable(lua_State* state, PushedObject& pushedObject, Metatable_t, TIndex2&& index2, TIndex3&& index3, TIndices&&... indices) noexcept + { + if (lua_getmetatable(state, -1) == 0) + { + lua_newtable(state); + PushedObject p1{state, 1}; + + setTable<TDataType>(state, p1, std::forward<TIndex2>(index2), std::forward<TIndex3>(index3), std::forward<TIndices>(indices)...); + + lua_setmetatable(state, -2); + p1.release(); + } + else + { + setTable<TDataType>(state, pushedObject, std::forward<TIndex2>(index2), std::forward<TIndex3>(index3), std::forward<TIndices>(indices)...); + } + } + + template<typename TDataType, typename TIndex2, typename TIndex3, typename... TIndices> + static void setTable(lua_State* state, PushedObject&& pushedObject, Metatable_t, TIndex2&& index2, TIndex3&& index3, TIndices&&... indices) noexcept + { + if (lua_getmetatable(state, -1) == 0) + { + lua_newtable(state); + PushedObject p1{state, 1}; + + setTable<TDataType>(state, p1, std::forward<TIndex2>(index2), std::forward<TIndex3>(index3), std::forward<TIndices>(indices)...); + + lua_setmetatable(state, -2); + p1.release(); + } + else + { + setTable<TDataType>(state, std::move(pushedObject), std::forward<TIndex2>(index2), std::forward<TIndex3>(index3), std::forward<TIndices>(indices)...); + } + } + + template<typename TDataType, typename TIndex, typename TData> + static void setTable(lua_State* state, RegistryTag, TIndex&& index, TData&& data) noexcept + { + static_assert(Pusher<typename std::decay<TIndex>::type>::minSize == 1 && Pusher<typename std::decay<TIndex>::type>::maxSize == 1, "Impossible to have a multiple-values index"); + static_assert(Pusher<typename std::decay<TDataType>::type>::minSize == 1 && Pusher<typename std::decay<TDataType>::type>::maxSize == 1, "Impossible to have a multiple-values data"); + + auto p1 = Pusher<typename std::decay<TIndex>::type>::push(state, index); + auto p2 = Pusher<typename std::decay<TDataType>::type>::push(state, std::forward<TData>(data)); + + lua_settable(state, LUA_REGISTRYINDEX); + p1.release(); + p2.release(); + } + + template<typename TDataType, typename TData> + static void setTable(lua_State* state, RegistryTag, const std::string& index, TData&& data) noexcept + { + static_assert(Pusher<typename std::decay<TDataType>::type>::minSize == 1 && Pusher<typename std::decay<TDataType>::type>::maxSize == 1, "Impossible to have a multiple-values data"); + + auto p1 = Pusher<typename std::decay<TDataType>::type>::push(state, std::forward<TData>(data)); + lua_setfield(state, LUA_REGISTRYINDEX, index.c_str()); + p1.release(); + } + + template<typename TDataType, typename TData> + static void setTable(lua_State* state, RegistryTag, const char* index, TData&& data) noexcept + { + static_assert(Pusher<typename std::decay<TDataType>::type>::minSize == 1 && Pusher<typename std::decay<TDataType>::type>::maxSize == 1, "Impossible to have a multiple-values data"); + + auto p1 = Pusher<typename std::decay<TDataType>::type>::push(state, std::forward<TData>(data)); + lua_setfield(state, LUA_REGISTRYINDEX, index); + p1.release(); + } + + template<typename TDataType, typename TIndex1, typename TIndex2, typename TIndex3, typename... TIndices> + static void setTable(lua_State* state, RegistryTag, TIndex1&& index1, TIndex2&& index2, TIndex3&& index3, TIndices&&... indices) noexcept + { + static_assert(Pusher<typename std::decay<TIndex1>::type>::minSize == 1 && Pusher<typename std::decay<TIndex1>::type>::maxSize == 1, "Impossible to have a multiple-values index"); + + auto p1 = Pusher<typename std::decay<TIndex1>::type>::push(state, std::forward<TIndex1>(index1)); + lua_gettable(state, LUA_REGISTRYINDEX); + + setTable<TDataType>(state, std::move(p1), std::forward<TIndex2>(index2), std::forward<TIndex3>(index3), std::forward<TIndices>(indices)...); + } + + template<typename TDataType, typename TIndex, typename TData> + static void setTable(lua_State* state, Globals_t, TIndex&& index, TData&& data) noexcept + { + static_assert(Pusher<typename std::decay<TIndex>::type>::minSize == 1 && Pusher<typename std::decay<TIndex>::type>::maxSize == 1, "Impossible to have a multiple-values index"); + static_assert(Pusher<typename std::decay<TDataType>::type>::minSize == 1 && Pusher<typename std::decay<TDataType>::type>::maxSize == 1, "Impossible to have a multiple-values data"); + + +# if LUA_VERSION_NUM >= 502 + + lua_pushglobaltable(state); + PushedObject p3{state, 1}; + auto p1 = Pusher<typename std::decay<TIndex>::type>::push(state, index); + auto p2 = Pusher<typename std::decay<TDataType>::type>::push(state, std::forward<TData>(data)); + lua_settable(state, -3); + +# else + + auto p1 = Pusher<typename std::decay<TIndex>::type>::push(state, index); + auto p2 = Pusher<typename std::decay<TDataType>::type>::push(state, std::forward<TData>(data)); + lua_settable(state, LUA_GLOBALSINDEX); + +# endif + + p1.release(); + p2.release(); + } + + template<typename TDataType, typename TData> + static void setTable(lua_State* state, Globals_t, const std::string& index, TData&& data) noexcept + { + static_assert(Pusher<typename std::decay<TDataType>::type>::minSize == 1 && Pusher<typename std::decay<TDataType>::type>::maxSize == 1, "Impossible to have a multiple-values data"); + + auto p1 = Pusher<typename std::decay<TDataType>::type>::push(state, std::forward<TData>(data)); + lua_setglobal(state, index.c_str()); + p1.release(); + } + + template<typename TDataType, typename TData> + static void setTable(lua_State* state, Globals_t, const char* index, TData&& data) noexcept + { + static_assert(Pusher<typename std::decay<TDataType>::type>::minSize == 1 && Pusher<typename std::decay<TDataType>::type>::maxSize == 1, "Impossible to have a multiple-values data"); + + auto p1 = Pusher<typename std::decay<TDataType>::type>::push(state, std::forward<TData>(data)); + lua_setglobal(state, index); + p1.release(); + } + + template<typename TDataType, typename TIndex1, typename TIndex2, typename TIndex3, typename... TIndices> + static void setTable(lua_State* state, Globals_t, TIndex1&& index1, TIndex2&& index2, TIndex3&& index3, TIndices&&... indices) noexcept + { + static_assert(Pusher<typename std::decay<TIndex1>::type>::minSize == 1 && Pusher<typename std::decay<TIndex1>::type>::maxSize == 1, "Impossible to have a multiple-values index"); + +# if LUA_VERSION_NUM >= 502 + + lua_pushglobaltable(state); + auto p1 = Pusher<typename std::decay<TIndex1>::type>::push(state, std::forward<TIndex1>(index1)) + PushedObject{state, 1}; + lua_gettable(state, -2); + +# else + + auto p1 = Pusher<typename std::decay<TIndex1>::type>::push(state, std::forward<TIndex1>(index1)); + lua_gettable(state, LUA_GLOBALSINDEX); + +# endif + + setTable<TDataType>(state, std::move(p1), std::forward<TIndex2>(index2), std::forward<TIndex3>(index3), std::forward<TIndices>(indices)...); + } + + // TODO: g++ reports "ambiguous overload" + /*template<typename TDataType, typename TIndex2, typename TIndex3, typename... TIndices> + static void setTable(lua_State* state, Globals_t, const char* index, TIndex2&& index2, TIndex3&& index3, TIndices&&... indices) noexcept + { + lua_getglobal(state, index); + PushedObject p1{state, 1}; + + setTable<TDataType>(state, std::move(p1), std::forward<TIndex2>(index2), std::forward<TIndex3>(index3), std::forward<TIndices>(indices)...); + } + + template<typename TDataType, typename TIndex2, typename TIndex3, typename... TIndices> + static void setTable(lua_State* state, Globals_t, const std::string& index, TIndex2&& index2, TIndex3&& index3, TIndices&&... indices) noexcept + { + lua_getglobal(state, index.c_str()); + PushedObject p1{state, 1}; + + setTable<TDataType>(state, std::move(p1), std::forward<TIndex2>(index2), std::forward<TIndex3>(index3), std::forward<TIndices>(indices)...); + }*/ + + // simple function that reads the "nb" first top elements of the stack, pops them, and returns the value + // warning: first parameter is the number of parameters, not the parameter index + // if read generates an exception, stack is poped anyway + template<typename TReturnType> + static auto readTopAndPop(lua_State* state, PushedObject object) + -> TReturnType + { + auto val = Reader<typename std::decay<TReturnType>::type>::read(state, -object.getNum()); + if (!val.is_initialized()) + throw WrongTypeException{lua_typename(state, lua_type(state, -object.getNum())), typeid(TReturnType)}; + return val.get(); + } + + // checks that the offsets for a type's registrations are set in the registry + static void checkTypeRegistration(lua_State* state, const std::type_info* type) + { + lua_pushlightuserdata(state, const_cast<std::type_info*>(type)); + lua_gettable(state, LUA_REGISTRYINDEX); + if (!lua_isnil(state, -1)) { + lua_pop(state, 1); + return; + } + lua_pop(state, 1); + + lua_pushlightuserdata(state, const_cast<std::type_info*>(type)); + lua_newtable(state); + + lua_pushinteger(state, 0); + lua_newtable(state); + lua_settable(state, -3); + + lua_pushinteger(state, 1); + lua_newtable(state); + lua_settable(state, -3); + + lua_pushinteger(state, 3); + lua_newtable(state); + lua_settable(state, -3); + + lua_pushinteger(state, 4); + lua_newtable(state); + lua_settable(state, -3); + + lua_settable(state, LUA_REGISTRYINDEX); + } + + // +# ifdef _MSC_VER + __declspec(noreturn) +# else + [[noreturn]] +# endif + static void luaError(lua_State* state) + { + lua_error(state); + assert(false); + std::terminate(); // removes compilation warning + } + + + /**************************************************/ + /* FUNCTIONS REGISTRATION */ + /**************************************************/ + // the "registerFunction" public functions call this one + template<typename TFunctionType, typename TRetValue, typename TObject, typename... TOtherParams> + void registerFunctionImpl(const std::string& functionName, TFunctionType function, tag<TObject>, tag<TRetValue (TOtherParams...)>) + { + static_assert(std::is_class<TObject>::value || std::is_pointer<TObject>::value || std::is_union<TObject>::value , "registerFunction can only be used for a class a union or a pointer"); + + checkTypeRegistration(mState, &typeid(TObject)); + setTable<TRetValue(TObject&, TOtherParams...)>(mState, Registry, &typeid(TObject), 0, functionName, function); + + checkTypeRegistration(mState, &typeid(TObject*)); + setTable<TRetValue(TObject*, TOtherParams...)>(mState, Registry, &typeid(TObject*), 0, functionName, [=](TObject* obj, TOtherParams... rest) { assert(obj); return function(*obj, std::forward<TOtherParams>(rest)...); }); + + checkTypeRegistration(mState, &typeid(std::shared_ptr<TObject>)); + setTable<TRetValue(std::shared_ptr<TObject>, TOtherParams...)>(mState, Registry, &typeid(std::shared_ptr<TObject>), 0, functionName, [=](const std::shared_ptr<TObject>& obj, TOtherParams... rest) { assert(obj); return function(*obj, std::forward<TOtherParams>(rest)...); }); + } + + template<typename TFunctionType, typename TRetValue, typename TObject, typename... TOtherParams> + void registerFunctionImpl(const std::string& functionName, TFunctionType function, tag<const TObject>, tag<TRetValue (TOtherParams...)> fTypeTag) + { + registerFunctionImpl(functionName, function, tag<TObject>{}, fTypeTag); + + checkTypeRegistration(mState, &typeid(TObject const*)); + setTable<TRetValue(TObject const*, TOtherParams...)>(mState, Registry, &typeid(TObject const*), 0, functionName, [=](TObject const* obj, TOtherParams... rest) { assert(obj); return function(*obj, std::forward<TOtherParams>(rest)...); }); + + checkTypeRegistration(mState, &typeid(std::shared_ptr<TObject const>)); + setTable<TRetValue(std::shared_ptr<TObject const>, TOtherParams...)>(mState, Registry, &typeid(std::shared_ptr<TObject const>), 0, functionName, [=](const std::shared_ptr<TObject const>& obj, TOtherParams... rest) { assert(obj); return function(*obj, std::forward<TOtherParams>(rest)...); }); + } + + template<typename TFunctionType, typename TRetValue, typename TObject, typename... TOtherParams> + void registerFunctionImpl(const std::string& functionName, TFunctionType function, tag<TRetValue (TObject::*)(TOtherParams...)>) + { + registerFunctionImpl(functionName, std::move(function), tag<TObject>{}, tag<TRetValue (TOtherParams...)>{}); + } + + template<typename TFunctionType, typename TRetValue, typename TObject, typename... TOtherParams> + void registerFunctionImpl(const std::string& functionName, TFunctionType function, tag<TRetValue (TObject::*)(TOtherParams...) const>) + { + registerFunctionImpl(functionName, std::move(function), tag<const TObject>{}, tag<TRetValue (TOtherParams...)>{}); + } + + template<typename TFunctionType, typename TRetValue, typename TObject, typename... TOtherParams> + void registerFunctionImpl(const std::string& functionName, TFunctionType function, tag<TRetValue (TObject::*)(TOtherParams...) volatile>) + { + registerFunctionImpl(functionName, std::move(function), tag<TObject>{}, tag<TRetValue (TOtherParams...)>{}); + } + + template<typename TFunctionType, typename TRetValue, typename TObject, typename... TOtherParams> + void registerFunctionImpl(const std::string& functionName, TFunctionType function, tag<TRetValue (TObject::*)(TOtherParams...) const volatile>) + { + registerFunctionImpl(functionName, std::move(function), tag<const TObject>{}, tag<TRetValue (TOtherParams...)>{}); + } + + // the "registerMember" public functions call this one + template<typename TObject, typename TVarType, typename TReadFunction> + void registerMemberImpl(const std::string& name, TReadFunction readFunction) + { + static_assert(std::is_class<TObject>::value || std::is_pointer<TObject>::value, "registerMember can only be called on a class or a pointer"); + + checkTypeRegistration(mState, &typeid(TObject)); + setTable<TVarType (TObject&)>(mState, Registry, &typeid(TObject), 1, name, [readFunction](TObject const& object) { + return readFunction(object); + }); + + checkTypeRegistration(mState, &typeid(TObject*)); + setTable<TVarType (TObject*)>(mState, Registry, &typeid(TObject*), 1, name, [readFunction](TObject const* object) { + assert(object); + return readFunction(*object); + }); + + checkTypeRegistration(mState, &typeid(TObject const*)); + setTable<TVarType (TObject const*)>(mState, Registry, &typeid(TObject const*), 1, name, [readFunction](TObject const* object) { + assert(object); + return readFunction(*object); + }); + + checkTypeRegistration(mState, &typeid(std::shared_ptr<TObject>)); + setTable<TVarType (std::shared_ptr<TObject>)>(mState, Registry, &typeid(std::shared_ptr<TObject>), 1, name, [readFunction](const std::shared_ptr<TObject>& object) { + assert(object); + return readFunction(*object); + }); + + checkTypeRegistration(mState, &typeid(std::shared_ptr<TObject const>)); + setTable<TVarType (std::shared_ptr<TObject const>)>(mState, Registry, &typeid(std::shared_ptr<TObject const>), 1, name, [readFunction](const std::shared_ptr<TObject const>& object) { + assert(object); + return readFunction(*object); + }); + } + + template<typename TObject, typename TVarType, typename TReadFunction, typename TWriteFunction> + void registerMemberImpl(const std::string& name, TReadFunction readFunction, TWriteFunction writeFunction_) + { + registerMemberImpl<TObject,TVarType>(name, readFunction); + + setTable<void (TObject&, TVarType)>(mState, Registry, &typeid(TObject), 4, name, [writeFunction_](TObject& object, const TVarType& value) { + writeFunction_(object, value); + }); + + setTable<void (TObject*, TVarType)>(mState, Registry, &typeid(TObject*), 4, name, [writeFunction_](TObject* object, const TVarType& value) { + assert(object); + writeFunction_(*object, value); + }); + + setTable<void (std::shared_ptr<TObject>, TVarType)>(mState, Registry, &typeid(std::shared_ptr<TObject>), 4, name, [writeFunction_](std::shared_ptr<TObject> object, const TVarType& value) { + assert(object); + writeFunction_(*object, value); + }); + } + + template<typename TObject, typename TVarType, typename TReadFunction, typename TWriteFunction> + void registerMemberImpl(tag<TVarType (TObject::*)>, const std::string& name, TReadFunction readFunction, TWriteFunction writeFunction_) + { + registerMemberImpl<TObject,TVarType>(name, std::move(readFunction), std::move(writeFunction_)); + } + + template<typename TObject, typename TVarType, typename TReadFunction> + void registerMemberImpl(tag<TVarType(TObject::*)>, const std::string& name, TReadFunction readFunction) + { + registerMemberImpl<TObject, TVarType>(name, std::move(readFunction)); + } + + // the "registerMember" public functions call this one + template<typename TObject, typename TVarType, typename TReadFunction> + void registerMemberImpl(TReadFunction readFunction) + { + checkTypeRegistration(mState, &typeid(TObject)); + setTable<TVarType (TObject const&, std::string)>(mState, Registry, &typeid(TObject), 2, [readFunction](TObject const& object, const std::string& name) { + return readFunction(object, name); + }); + + checkTypeRegistration(mState, &typeid(TObject*)); + setTable<TVarType (TObject*, std::string)>(mState, Registry, &typeid(TObject*), 2, [readFunction](TObject const* object, const std::string& name) { + assert(object); + return readFunction(*object, name); + }); + + checkTypeRegistration(mState, &typeid(TObject const*)); + setTable<TVarType (TObject const*, std::string)>(mState, Registry, &typeid(TObject const*), 2, [readFunction](TObject const* object, const std::string& name) { + assert(object); + return readFunction(*object, name); + }); + + checkTypeRegistration(mState, &typeid(std::shared_ptr<TObject>)); + setTable<TVarType (std::shared_ptr<TObject>, std::string)>(mState, Registry, &typeid(std::shared_ptr<TObject>), 2, [readFunction](const std::shared_ptr<TObject>& object, const std::string& name) { + assert(object); + return readFunction(*object, name); + }); + + checkTypeRegistration(mState, &typeid(std::shared_ptr<TObject const>)); + setTable<TVarType (std::shared_ptr<TObject const>, std::string)>(mState, Registry, &typeid(std::shared_ptr<TObject const>), 2, [readFunction](const std::shared_ptr<TObject const>& object, const std::string& name) { + assert(object); + return readFunction(*object, name); + }); + } + + template<typename TObject, typename TVarType, typename TReadFunction, typename TWriteFunction> + void registerMemberImpl(TReadFunction readFunction, TWriteFunction writeFunction_) + { + registerMemberImpl<TObject,TVarType>(readFunction); + + setTable<void (TObject&, std::string, TVarType)>(mState, Registry, &typeid(TObject), 5, [writeFunction_](TObject& object, const std::string& name, const TVarType& value) { + writeFunction_(object, name, value); + }); + + setTable<void (TObject*, std::string, TVarType)>(mState, Registry, &typeid(TObject*), 2, [writeFunction_](TObject* object, const std::string& name, const TVarType& value) { + assert(object); + writeFunction_(*object, name, value); + }); + + setTable<void (std::shared_ptr<TObject>, std::string, TVarType)>(mState, Registry, &typeid(std::shared_ptr<TObject>), 2, [writeFunction_](const std::shared_ptr<TObject>& object, const std::string& name, const TVarType& value) { + assert(object); + writeFunction_(*object, name, value); + }); + } + + template<typename TObject, typename TVarType, typename TReadFunction, typename TWriteFunction> + void registerMemberImpl(tag<TVarType (TObject::*)>, TReadFunction readFunction, TWriteFunction writeFunction_) + { + registerMemberImpl<TObject,TVarType>(std::move(readFunction), std::move(writeFunction_)); + } + + template<typename TObject, typename TVarType, typename TReadFunction> + void registerMemberImpl(tag<TVarType(TObject::*)>, TReadFunction readFunction) + { + registerMemberImpl<TObject, TVarType>(std::move(readFunction)); + } + + + /**************************************************/ + /* LOADING AND CALLING */ + /**************************************************/ + // this function loads data from the stream and pushes a function at the top of the stack + // throws in case of syntax error + static PushedObject load(lua_State* state, std::istream& code) { + // since the lua_load function requires a static function, we use this structure + // the Reader structure is at the same time an object storing an istream and a buffer, + // and a static function provider + struct Reader { + Reader(std::istream& str) : stream(str) {} + std::istream& stream; + std::array<char,512> buffer; + + // read function ; "data" must be an instance of Reader + static const char* read(lua_State* /*l*/, void* data, size_t* size) { + assert(size != nullptr); + assert(data != nullptr); + Reader& me = *static_cast<Reader*>(data); + if (me.stream.eof()) { *size = 0; return nullptr; } + + me.stream.read(me.buffer.data(), me.buffer.size()); + *size = static_cast<size_t>(me.stream.gcount()); // gcount could return a value larger than a size_t, but its maximum is me.buffer.size() so there's no problem + return me.buffer.data(); + } + }; + + // we create an instance of Reader, and we call lua_load + Reader reader{code}; + const auto loadReturnValue = lua_load(state, &Reader::read, &reader, "chunk" +# if LUA_VERSION_NUM >= 502 + , nullptr +# endif + ); + + // now we have to check return value + if (loadReturnValue != 0) { + // there was an error during loading, an error message was pushed on the stack + const std::string errorMsg = lua_tostring(state, -1); + lua_pop(state, 1); + if (loadReturnValue == LUA_ERRMEM) + throw std::bad_alloc(); + else if (loadReturnValue == LUA_ERRSYNTAX) + throw SyntaxErrorException{errorMsg}; + throw std::runtime_error("Error while calling lua_load: " + errorMsg); + } + + return PushedObject{state, 1}; + } + + // this function loads data and pushes a function at the top of the stack + // throws in case of syntax error + static PushedObject load(lua_State* state, const char* code) { + auto loadReturnValue = luaL_loadstring(state, code); + + // now we have to check return value + if (loadReturnValue != 0) { + // there was an error during loading, an error message was pushed on the stack + const std::string errorMsg = lua_tostring(state, -1); + lua_pop(state, 1); + if (loadReturnValue == LUA_ERRMEM) + throw std::bad_alloc(); + else if (loadReturnValue == LUA_ERRSYNTAX) + throw SyntaxErrorException{errorMsg}; + throw std::runtime_error("Error while calling lua_load: " + errorMsg); + } + + return PushedObject{state, 1}; + } + + // this function calls what is on the top of the stack and removes it (just like lua_call) + // if an exception is triggered, the top of the stack will be removed anyway + template<typename TReturnType, typename... TParameters> + static auto call(lua_State* state, PushedObject toCall, TParameters&&... input) + -> TReturnType + { + typedef typename Tupleizer<TReturnType>::type + RealReturnType; + + // we push the parameters on the stack + auto inArguments = Pusher<std::tuple<TParameters&&...>>::push(state, std::forward_as_tuple(std::forward<TParameters>(input)...)); + + // + const int outArgumentsCount = std::tuple_size<RealReturnType>::value; + auto outArguments = callRaw(state, std::move(toCall) + std::move(inArguments), outArgumentsCount); + + // pcall succeeded, we pop the returned values and return them + return readTopAndPop<TReturnType>(state, std::move(outArguments)); + } + + static int gettraceback(lua_State* L) { + lua_getglobal(L, "debug"); // stack: error, debug library + lua_getfield(L, -1, "traceback"); // stack: error, debug library, debug.traceback function + lua_remove(L, -2); // stack: error, debug.traceback function + lua_pushstring(L, ""); // stack: error, debug.traceback, "" + lua_pushinteger(L, 2); // stack: error, debug.traceback, "", 2 + lua_call(L, 2, 1); // stack: error, traceback + lua_createtable(L, 2, 0); // stack: error, traceback, {} + lua_insert(L, 1); // stack: {}, error, traceback + lua_rawseti(L, 1, 2); // stack: {[2]=traceback}, error + lua_rawseti(L, 1, 1); // stack: {[1]=error,[2]=traceback} + return 1; // return the table + } + + // this function just calls lua_pcall and checks for errors + static PushedObject callRaw(lua_State* state, PushedObject functionAndArguments, const int outArguments) + { + // provide traceback handler + int tbindex = lua_gettop(state) - (functionAndArguments.getNum() - 1); + lua_pushcfunction(state, gettraceback); + + // move it back up, before our function and arguments + lua_insert(state, tbindex); + + // calling pcall automatically pops the parameters and pushes output + const auto pcallReturnValue = lua_pcall(state, functionAndArguments.getNum() - 1, outArguments, tbindex); + functionAndArguments.release(); + + lua_remove(state, tbindex); // remove traceback function + + + // if pcall failed, analyzing the problem and throwing + if (pcallReturnValue != 0) { + + // stack top: {error, traceback} + lua_rawgeti(state, -1, 1); // stack top: {error, traceback}, error + lua_rawgeti(state, -2, 2); // stack top: {error, traceback}, error, traceback + lua_remove(state, -3); // stack top: error, traceback + + PushedObject traceBackRef{state, 1}; + const auto traceBack = readTopAndPop<std::string>(state, std::move(traceBackRef)); // stack top: error + PushedObject errorCode{state, 1}; + + // an error occurred during execution, either an error message or a std::exception_ptr was pushed on the stack + if (pcallReturnValue == LUA_ERRMEM) { + throw std::bad_alloc{}; + + } else if (pcallReturnValue == LUA_ERRRUN) { + if (lua_isstring(state, 1)) { + // the error is a string + const auto str = readTopAndPop<std::string>(state, std::move(errorCode)); + throw ExecutionErrorException{str+traceBack}; + + } else { + // an exception_ptr was pushed on the stack + // rethrowing it with an additional ExecutionErrorException + try { + if (const auto exp = readTopAndPop<std::exception_ptr>(state, std::move(errorCode))) { + std::rethrow_exception(exp); + } + } catch(const std::exception& e) { + std::throw_with_nested(ExecutionErrorException{std::string{"Exception thrown by a callback function: "} + e.what()}); + } catch(...) { + std::throw_with_nested(ExecutionErrorException{"Exception thrown by a callback function called by Lua. "+traceBack}); + } + throw ExecutionErrorException{"Unknown Lua error"}; + } + } + } + + return PushedObject{state, outArguments}; + } + + + /**************************************************/ + /* PUSH FUNCTIONS */ + /**************************************************/ + template<typename T> + static PushedObject push(lua_State* state, T&& value) + { + return Pusher<typename std::decay<T>::type>::push(state, std::forward<T>(value)); + } + + // the Pusher structures allow you to push a value on the stack + // - static const int minSize : minimum size on the stack that the value can have + // - static const int maxSize : maximum size on the stack that the value can have + // - static int push(const LuaContext&, ValueType) : pushes the value on the stack and returns the size on the stack + + // implementation for custom objects + template<typename TType, typename = void> + struct Pusher { + static const int minSize = 1; + static const int maxSize = 1; + + template<typename TType2> + static PushedObject push(lua_State* state, TType2&& value) noexcept { + // this function is called when lua's garbage collector wants to destroy our object + // we simply call its destructor + const auto garbageCallbackFunction = [](lua_State* lua) -> int { + assert(lua_gettop(lua) == 1); + TType* ptr = static_cast<TType*>(lua_touserdata(lua, 1)); + assert(ptr); + ptr->~TType(); + return 0; + }; + + // this function will be stored in __index in the metatable + const auto indexFunction = [](lua_State* lua) -> int { + try { + assert(lua_gettop(lua) == 2); + assert(lua_isuserdata(lua, 1)); + + // searching for a handler + lua_pushlightuserdata(lua, const_cast<std::type_info*>(&typeid(TType))); + lua_gettable(lua, LUA_REGISTRYINDEX); + assert(!lua_isnil(lua, -1)); + + // looking into getter functions + lua_pushinteger(lua, 0); + lua_gettable(lua, -2); + lua_pushvalue(lua, 2); + lua_gettable(lua, -2); + if (!lua_isnil(lua, -1)) + return 1; + lua_pop(lua, 2); + + // looking into getter members + lua_pushinteger(lua, 1); + lua_gettable(lua, -2); + lua_pushvalue(lua, 2); + lua_gettable(lua, -2); + if (!lua_isnil(lua, -1)) { + lua_pushvalue(lua, 1); + return callRaw(lua, PushedObject{lua, 2}, 1).release(); + } + lua_pop(lua, 2); + + // looking into default getter + lua_pushinteger(lua, 2); + lua_gettable(lua, -2); + if (lua_isnil(lua, -1)) + return 1; + lua_pushvalue(lua, 1); + lua_pushvalue(lua, 2); + return callRaw(lua, PushedObject{lua, 3}, 1).release(); + + } catch (...) { + Pusher<std::exception_ptr>::push(lua, std::current_exception()).release(); + luaError(lua); + } + }; + + // this function will be stored in __newindex in the metatable + const auto newIndexFunction = [](lua_State* lua) -> int { + try { + assert(lua_gettop(lua) == 3); + assert(lua_isuserdata(lua, 1)); + + // searching for a handler + lua_pushlightuserdata(lua, const_cast<std::type_info*>(&typeid(TType))); + lua_rawget(lua, LUA_REGISTRYINDEX); + assert(!lua_isnil(lua, -1)); + + // looking into setter members + lua_pushinteger(lua, 4); + lua_rawget(lua, -2); + lua_pushvalue(lua, 2); + lua_rawget(lua, -2); + if (!lua_isnil(lua, -1)) { + lua_pushvalue(lua, 1); + lua_pushvalue(lua, 3); + callRaw(lua, PushedObject{lua, 3}, 0); + lua_pop(lua, 2); + return 0; + } + lua_pop(lua, 2); + + // looking into default setter + lua_pushinteger(lua, 5); + lua_rawget(lua, -2); + if (lua_isnil(lua, -1)) + { + lua_pop(lua, 2); + lua_pushstring(lua, "No setter found"); + luaError(lua); + } + lua_pushvalue(lua, 1); + lua_pushvalue(lua, 2); + lua_pushvalue(lua, 3); + callRaw(lua, PushedObject{lua, 4}, 0); + lua_pop(lua, 1); + return 0; + + } catch (...) { + Pusher<std::exception_ptr>::push(lua, std::current_exception()).release(); + luaError(lua); + } + }; + + const auto toStringFunction = [](lua_State* lua) -> int { + try { + assert(lua_gettop(lua) == 1); + assert(lua_isuserdata(lua, 1)); + lua_pushstring(lua, "__tostring"); + lua_gettable(lua, 1); + if (lua_isnil(lua, -1)) + { + const void *ptr = lua_topointer(lua, -2); + lua_pop(lua, 1); + lua_pushstring(lua, (boost::format("userdata 0x%08x") % reinterpret_cast<intptr_t>(ptr)).str().c_str()); + return 1; + } + lua_pushvalue(lua, 1); + return callRaw(lua, PushedObject{lua, 2}, 1).release(); + } catch (...) { + Pusher<std::exception_ptr>::push(lua, std::current_exception()).release(); + luaError(lua); + } + }; + + + // writing structure for this type into the registry + checkTypeRegistration(state, &typeid(TType)); + try { + // creating the object + // lua_newuserdata allocates memory in the internals of the lua library and returns it so we can fill it + // and that's what we do with placement-new + const auto pointerLocation = static_cast<TType*>(lua_newuserdata(state, sizeof(TType))); + new (pointerLocation) TType(std::forward<TType2>(value)); + } + catch (...) { + Pusher<std::exception_ptr>::push(state, std::current_exception()).release(); + luaError(state); + } + + PushedObject obj{state, 1}; + + // creating the metatable (over the object on the stack) + // lua_settable pops the key and value we just pushed, so stack management is easy + // all that remains on the stack after these function calls is the metatable + lua_newtable(state); + PushedObject pushedTable{state, 1}; + + // using the garbage collecting function we created above + if (!boost::has_trivial_destructor<TType>::value) + { + lua_pushstring(state, "__gc"); + lua_pushcfunction(state, garbageCallbackFunction); + lua_settable(state, -3); + } + + // the _typeid index of the metatable will store the type_info* + lua_pushstring(state, "_typeid"); + lua_pushlightuserdata(state, const_cast<std::type_info*>(&typeid(TType))); + lua_settable(state, -3); + + // using the index function we created above + lua_pushstring(state, "__index"); + lua_pushcfunction(state, indexFunction); + lua_settable(state, -3); + + // using the newindex function we created above + lua_pushstring(state, "__newindex"); + lua_pushcfunction(state, newIndexFunction); + lua_settable(state, -3); + + lua_pushstring(state, "__tostring"); + lua_pushcfunction(state, toStringFunction); + lua_settable(state, -3); + + lua_pushstring(state, "__eq"); + lua_getglobal(state, LUACONTEXT_GLOBAL_EQ); + lua_settable(state, -3); + + + // at this point, the stack contains the object at offset -2 and the metatable at offset -1 + // lua_setmetatable will bind the two together and pop the metatable + // our custom type remains on the stack (and that's what we want since this is a push function) + lua_setmetatable(state, -2); + pushedTable.release(); + + return obj; + } + }; + + // this structure has a "size" int static member which is equal to the total of the push min size of all the types + template<typename... TTypes> + struct PusherTotalMinSize; + + // this structure has a "size" int static member which is equal to the total of the push max size of all the types + template<typename... TTypes> + struct PusherTotalMaxSize; + + // this structure has a "size" int static member which is equal to the maximum size of the push of all the types + template<typename... TTypes> + struct PusherMinSize; + + // this structure has a "size" int static member which is equal to the maximum size of the push of all the types + template<typename... TTypes> + struct PusherMaxSize; + + + /**************************************************/ + /* READ FUNCTIONS */ + /**************************************************/ + // the "Reader" structures allow to read data from the stack + // - the "ReturnType" type is what is returned by the reader, and can be different than the template parameter (especially with references and constness) + // - the "read" static function will check and read at the same time, returning an empty optional if it is the wrong type + + template<typename TType, typename = void> + struct Reader { + typedef typename std::conditional<std::is_pointer<TType>::value, TType, TType&>::type + ReturnType; + + static auto read(lua_State* state, int index) + -> boost::optional<ReturnType> + { + if (!test(state, index)) + return boost::none; + return boost::optional<ReturnType>(*static_cast<TType*>(lua_touserdata(state, index))); + } + + private: + static bool test(lua_State* state, int index) + { + if (!lua_isuserdata(state, index)) + return false; + if (!lua_getmetatable(state, index)) + return false; + + // now we have our metatable on the top of the stack + // retrieving its _typeid member + lua_pushstring(state, "_typeid"); + lua_gettable(state, -2); + const auto storedTypeID = static_cast<const std::type_info*>(lua_touserdata(state, -1)); + const auto typeIDToCompare = &typeid(TType); + + // if wrong typeid, returning false + lua_pop(state, 2); + if (storedTypeID != typeIDToCompare) + return false; + + return true; + } + }; + + /** + * This functions reads multiple values starting at "index" and passes them to the callback + */ + template<typename TRetValue, typename TCallback> + static auto readIntoFunction(lua_State* /*state*/, tag<TRetValue>, TCallback&& callback, int /*index*/) + -> TRetValue + { + return callback(); + } + template<typename TRetValue, typename TCallback, typename TFirstType, typename... TTypes> + static auto readIntoFunction(lua_State* state, tag<TRetValue> retValueTag, TCallback&& callback, int index, tag<TFirstType>, tag<TTypes>... othersTags) + -> typename std::enable_if<IsOptional<TFirstType>::value, TRetValue>::type + { + if (index >= 0) { + static const TFirstType empty{}; + Binder<TCallback, const TFirstType&> binder{ callback, empty }; + return readIntoFunction(state, retValueTag, binder, index + 1, othersTags...); + } + + const auto& firstElem = Reader<typename std::decay<TFirstType>::type>::read(state, index); + if (!firstElem) + throw WrongTypeException(lua_typename(state, lua_type(state, index)), typeid(TFirstType)); + + Binder<TCallback, const TFirstType&> binder{ callback, *firstElem }; + return readIntoFunction(state, retValueTag, binder, index + 1, othersTags...); + } + template<typename TRetValue, typename TCallback, typename TFirstType, typename... TTypes> + static auto readIntoFunction(lua_State* state, tag<TRetValue> retValueTag, TCallback&& callback, int index, tag<TFirstType>, tag<TTypes>... othersTags) + -> typename std::enable_if<!IsOptional<TFirstType>::value, TRetValue>::type + { + if (index >= 0) + throw std::logic_error("Wrong number of parameters"); + + const auto& firstElem = Reader<typename std::decay<TFirstType>::type>::read(state, index); + if (!firstElem) + throw WrongTypeException(lua_typename(state, lua_type(state, index)), typeid(TFirstType)); + + Binder<TCallback, const TFirstType&> binder{ callback, *firstElem }; + return readIntoFunction(state, retValueTag, binder, index + 1, othersTags...); + } + + + /**************************************************/ + /* UTILITIES */ + /**************************************************/ + // structure that will ensure that a certain value is stored somewhere in the registry + struct ValueInRegistry { + // this constructor will clone and hold the value at the specified index (or by default at the top of the stack) in the registry + ValueInRegistry(lua_State* lua_, int index=-1) : lua{lua_} + { + lua_pushlightuserdata(lua, this); + lua_pushvalue(lua, -1 + index); + lua_settable(lua, LUA_REGISTRYINDEX); + } + + // removing the function from the registry + ~ValueInRegistry() + { + lua_pushlightuserdata(lua, this); + lua_pushnil(lua); + lua_settable(lua, LUA_REGISTRYINDEX); + } + + // loads the value and puts it at the top of the stack + PushedObject pop() + { + lua_pushlightuserdata(lua, this); + lua_gettable(lua, LUA_REGISTRYINDEX); + return PushedObject{lua, 1}; + } + + ValueInRegistry(const ValueInRegistry&) = delete; + ValueInRegistry& operator=(const ValueInRegistry&) = delete; + + private: + lua_State* lua; + }; + + // binds the first parameter of a function object + template<typename TFunctionObject, typename TFirstParamType> + struct Binder { + TFunctionObject function; + TFirstParamType param; + + template<typename... TParams> + auto operator()(TParams&&... params) + -> decltype(function(param, std::forward<TParams>(params)...)) + { + return function(param, std::forward<TParams>(params)...); + } + }; + + // turns a type into a tuple + // void is turned into std::tuple<> + // existing tuples are untouched + template<typename T> + struct Tupleizer; + + // this structure takes a pointer to a member function type and returns the base function type + template<typename TType> + struct RemoveMemberPointerFunction { typedef void type; }; // required because of a compiler bug + + // this structure takes any object and detects its function type + template<typename TObjectType> + struct FunctionTypeDetector { typedef typename RemoveMemberPointerFunction<decltype(&std::decay<TObjectType>::type::operator())>::type type; }; + + // this structure takes a function arguments list and has the "min" and the "max" static const member variables, whose value equal to the min and max number of parameters for the function + // the only case where "min != max" is with boost::optional at the end of the list + template<typename... TArgumentsList> + struct FunctionArgumentsCounter {}; + + // true is the template parameter is a boost::optional + template<typename T> + struct IsOptional : public std::false_type {}; +}; + +/// @deprecated +static LuaContext::EmptyArray_t ATTR_UNUSED + LuaEmptyArray {}; +/// @deprecated +static LuaContext::Metatable_t ATTR_UNUSED + LuaMetatable {}; + +/**************************************************/ +/* PARTIAL IMPLEMENTATIONS */ +/**************************************************/ +template<> +inline auto LuaContext::readTopAndPop<void>(lua_State* /*state*/, PushedObject /*obj*/) + -> void +{ +} + +// this structure takes a template parameter T +// if T is a tuple, it returns T ; if T is not a tuple, it returns std::tuple<T> +// we have to use this structure because std::tuple<std::tuple<...>> triggers a bug in both MSVC++ and GCC +template<typename T> +struct LuaContext::Tupleizer { typedef std::tuple<T> type; }; +template<typename... Args> +struct LuaContext::Tupleizer<std::tuple<Args...>> { typedef std::tuple<Args...> type; }; +template<> +struct LuaContext::Tupleizer<void> { typedef std::tuple<> type; }; + +// this structure takes any object and detects its function type +template<typename TRetValue, typename... TParameters> +struct LuaContext::FunctionTypeDetector<TRetValue (TParameters...)> { typedef TRetValue type(TParameters...); }; +template<typename TObjectType> +struct LuaContext::FunctionTypeDetector<TObjectType*> { typedef typename FunctionTypeDetector<TObjectType>::type type; }; + +// this structure takes a pointer to a member function type and returns the base function type +template<typename TType, typename TRetValue, typename... TParameters> +struct LuaContext::RemoveMemberPointerFunction<TRetValue (TType::*)(TParameters...)> { typedef TRetValue type(TParameters...); }; +template<typename TType, typename TRetValue, typename... TParameters> +struct LuaContext::RemoveMemberPointerFunction<TRetValue (TType::*)(TParameters...) const> { typedef TRetValue type(TParameters...); }; +template<typename TType, typename TRetValue, typename... TParameters> +struct LuaContext::RemoveMemberPointerFunction<TRetValue (TType::*)(TParameters...) volatile> { typedef TRetValue type(TParameters...); }; +template<typename TType, typename TRetValue, typename... TParameters> +struct LuaContext::RemoveMemberPointerFunction<TRetValue (TType::*)(TParameters...) const volatile> { typedef TRetValue type(TParameters...); }; + +// implementation of PusherTotalMinSize +template<typename TFirst, typename... TTypes> +struct LuaContext::PusherTotalMinSize<TFirst, TTypes...> { static const int size = Pusher<typename std::decay<TFirst>::type>::minSize + PusherTotalMinSize<TTypes...>::size; }; +template<> +struct LuaContext::PusherTotalMinSize<> { static const int size = 0; }; + +// implementation of PusherTotalMaxSize +template<typename TFirst, typename... TTypes> +struct LuaContext::PusherTotalMaxSize<TFirst, TTypes...> { static const int size = Pusher<typename std::decay<TFirst>::type>::maxSize + PusherTotalMaxSize<TTypes...>::size; }; +template<> +struct LuaContext::PusherTotalMaxSize<> { static const int size = 0; }; + +// implementation of PusherMinSize +template<typename TFirst, typename TSecond, typename... TTypes> +struct LuaContext::PusherMinSize<TFirst, TSecond, TTypes...> +{ + static const int size = Pusher<typename std::decay<TFirst>::type>::minSize < Pusher<typename std::decay<TSecond>::type>::minSize + ? + PusherMinSize<typename std::decay<TFirst>::type, TTypes...>::size + : + PusherMinSize<typename std::decay<TSecond>::type, TTypes...>::size; +}; + +template<typename TFirst> +struct LuaContext::PusherMinSize<TFirst> { static const int size = Pusher<typename std::decay<TFirst>::type>::minSize; }; + +// implementation of PusherMaxSize +template<typename TFirst, typename... TTypes> +struct LuaContext::PusherMaxSize<TFirst, TTypes...> { static const int size = Pusher<typename std::decay<TFirst>::type>::maxSize > PusherTotalMaxSize<TTypes...>::size ? Pusher<typename std::decay<TFirst>::type>::maxSize : PusherMaxSize<TTypes...>::size; }; +template<> +struct LuaContext::PusherMaxSize<> { static const int size = 0; }; + +// implementation of FunctionArgumentsCounter +template<typename TFirst, typename... TParams> +struct LuaContext::FunctionArgumentsCounter<TFirst, TParams...> { + typedef FunctionArgumentsCounter<TParams...> + SubType; + static const int min = (IsOptional<TFirst>::value && SubType::min == 0) ? 0 : 1 + SubType::min; + static const int max = 1 + SubType::max; +}; +template<> +struct LuaContext::FunctionArgumentsCounter<> { + static const int min = 0; + static const int max = 0; +}; + +// implementation of IsOptional +template<typename T> +struct LuaContext::IsOptional<boost::optional<T>> : public std::true_type {}; + +// implementation of LuaFunctionCaller +template<typename TFunctionType> +class LuaContext::LuaFunctionCaller { static_assert(std::is_function<TFunctionType>::value, "Template parameter of LuaFunctionCaller must be a function type"); }; +template<typename TRetValue, typename... TParams> +class LuaContext::LuaFunctionCaller<TRetValue (TParams...)> +{ +public: + TRetValue operator()(TParams&&... params) const + { + auto obj = valueHolder->pop(); + return call<TRetValue>(state, std::move(obj), std::forward<TParams>(params)...); + } + +private: + std::shared_ptr<ValueInRegistry> valueHolder; + lua_State* state; + +private: + friend LuaContext; + explicit LuaFunctionCaller(lua_State* state_, int index) : + valueHolder(std::make_shared<ValueInRegistry>(state_, index)), + state(state_) + {} +}; + + +/**************************************************/ +/* PUSH FUNCTIONS */ +/**************************************************/ +// specializations of the Pusher structure + +// opaque Lua references +template<> +struct LuaContext::Pusher<LuaContext::LuaObject> { + static const int minSize = 1; + static const int maxSize = 1; + + static PushedObject push(lua_State* state, const LuaContext::LuaObject& value) noexcept { + if (value.objectInRegistry.get()) { + PushedObject obj = value.objectInRegistry->pop(); + return obj; + } else { + lua_pushnil(state); + return PushedObject{state, 1}; + } + } +}; + +// boolean +template<> +struct LuaContext::Pusher<bool> { + static const int minSize = 1; + static const int maxSize = 1; + + static PushedObject push(lua_State* state, bool value) noexcept { + lua_pushboolean(state, value); + return PushedObject{state, 1}; + } +}; + +// string +template<> +struct LuaContext::Pusher<std::string> { + static const int minSize = 1; + static const int maxSize = 1; + + static PushedObject push(lua_State* state, const std::string& value) noexcept { + lua_pushlstring(state, value.c_str(), value.length()); + return PushedObject{state, 1}; + } +}; + +// const char* +template<> +struct LuaContext::Pusher<const char*> { + static const int minSize = 1; + static const int maxSize = 1; + + static PushedObject push(lua_State* state, const char* value) noexcept { + lua_pushstring(state, value); + return PushedObject{state, 1}; + } +}; + +// const char[N] +template<int N> +struct LuaContext::Pusher<const char[N]> { + static const int minSize = 1; + static const int maxSize = 1; + + static PushedObject push(lua_State* state, const char* value) noexcept { + lua_pushstring(state, value); + return PushedObject{state, 1}; + } +}; + +// floating numbers +template<typename T> +struct LuaContext::Pusher<T, typename std::enable_if<std::is_floating_point<T>::value>::type> { + static const int minSize = 1; + static const int maxSize = 1; + + static PushedObject push(lua_State* state, T value) noexcept { + lua_pushnumber(state, value); + return PushedObject{state, 1}; + } +}; + +// integers +template<typename T> +struct LuaContext::Pusher<T, typename std::enable_if<std::is_integral<T>::value>::type> { + static const int minSize = 1; + static const int maxSize = 1; + + static PushedObject push(lua_State* state, T value) noexcept { + lua_pushinteger(state, value); + return PushedObject{state, 1}; + } +}; + +// nil +template<> +struct LuaContext::Pusher<std::nullptr_t> { + static const int minSize = 1; + static const int maxSize = 1; + + static PushedObject push(lua_State* state, std::nullptr_t) noexcept { + lua_pushnil(state); + return PushedObject{state, 1}; + } +}; + +// empty arrays +template<> +struct LuaContext::Pusher<LuaContext::EmptyArray_t> { + static const int minSize = 1; + static const int maxSize = 1; + + static PushedObject push(lua_State* state, EmptyArray_t) noexcept { + lua_newtable(state); + return PushedObject{state, 1}; + } +}; + +// std::type_info* is a lightuserdata +template<> +struct LuaContext::Pusher<const std::type_info*> { + static const int minSize = 1; + static const int maxSize = 1; + + static PushedObject push(lua_State* state, const std::type_info* ptr) noexcept { + lua_pushlightuserdata(state, const_cast<std::type_info*>(ptr)); + return PushedObject{state, 1}; + } +}; + +// thread +template<> +struct LuaContext::Pusher<LuaContext::ThreadID> { + static const int minSize = 1; + static const int maxSize = 1; + + static PushedObject push(lua_State* state, const LuaContext::ThreadID& value) noexcept { + lua_pushthread(value.state); + return PushedObject{state, 1}; + } +}; + +// maps +template<typename TKey, typename TValue> +struct LuaContext::Pusher<std::map<TKey,TValue>> { + static const int minSize = 1; + static const int maxSize = 1; + + static PushedObject push(lua_State* state, const std::map<TKey,TValue>& value) noexcept { + static_assert(Pusher<typename std::decay<TKey>::type>::minSize == 1 && Pusher<typename std::decay<TKey>::type>::maxSize == 1, "Can't push multiple elements for a table key"); + static_assert(Pusher<typename std::decay<TValue>::type>::minSize == 1 && Pusher<typename std::decay<TValue>::type>::maxSize == 1, "Can't push multiple elements for a table value"); + + auto obj = Pusher<EmptyArray_t>::push(state, EmptyArray); + + for (auto i = value.begin(), e = value.end(); i != e; ++i) + setTable<TValue>(state, obj, i->first, i->second); + + return obj; + } +}; + +// unordered_maps +template<typename TKey, typename TValue, typename THash, typename TKeyEqual> +struct LuaContext::Pusher<std::unordered_map<TKey,TValue,THash,TKeyEqual>> { + static const int minSize = 1; + static const int maxSize = 1; + + static PushedObject push(lua_State* state, const std::unordered_map<TKey,TValue,THash,TKeyEqual>& value) noexcept { + static_assert(Pusher<typename std::decay<TKey>::type>::minSize == 1 && Pusher<typename std::decay<TKey>::type>::maxSize == 1, "Can't push multiple elements for a table key"); + static_assert(Pusher<typename std::decay<TValue>::type>::minSize == 1 && Pusher<typename std::decay<TValue>::type>::maxSize == 1, "Can't push multiple elements for a table value"); + + auto obj = Pusher<EmptyArray_t>::push(state, EmptyArray); + + for (auto i = value.begin(), e = value.end(); i != e; ++i) + setTable<TValue>(state, obj, i->first, i->second); + + return obj; + } +}; + +// vectors of pairs +template<typename TType1, typename TType2> +struct LuaContext::Pusher<std::vector<std::pair<TType1,TType2>>> { + static const int minSize = 1; + static const int maxSize = 1; + + static PushedObject push(lua_State* state, const std::vector<std::pair<TType1,TType2>>& value) noexcept { + static_assert(Pusher<typename std::decay<TType1>::type>::minSize == 1 && Pusher<typename std::decay<TType1>::type>::maxSize == 1, "Can't push multiple elements for a table key"); + static_assert(Pusher<typename std::decay<TType2>::type>::minSize == 1 && Pusher<typename std::decay<TType2>::type>::maxSize == 1, "Can't push multiple elements for a table value"); + + auto obj = Pusher<EmptyArray_t>::push(state, EmptyArray); + + for (auto i = value.begin(), e = value.end(); i != e; ++i) + setTable<TType2>(state, obj, i->first, i->second); + + return obj; + } +}; + +// vectors +template<typename TType> +struct LuaContext::Pusher<std::vector<TType>> { + static const int minSize = 1; + static const int maxSize = 1; + + static PushedObject push(lua_State* state, const std::vector<TType>& value) noexcept { + static_assert(Pusher<typename std::decay<TType>::type>::minSize == 1 && Pusher<typename std::decay<TType>::type>::maxSize == 1, "Can't push multiple elements for a table value"); + + auto obj = Pusher<EmptyArray_t>::push(state, EmptyArray); + + for (unsigned int i = 0; i < value.size(); ++i) + setTable<TType>(state, obj, i + 1, value[i]); + + return obj; + } +}; + +// unique_ptr +template<typename TType> +struct LuaContext::Pusher<std::unique_ptr<TType>> { + static const int minSize = Pusher<std::shared_ptr<TType>>::minSize; + static const int maxSize = Pusher<std::shared_ptr<TType>>::maxSize; + + static PushedObject push(lua_State* state, std::unique_ptr<TType> value) noexcept { + return Pusher<std::shared_ptr<TType>>::push(state, std::move(value)); + } +}; + +// enum +template<typename TEnum> +struct LuaContext::Pusher<TEnum, typename std::enable_if<std::is_enum<TEnum>::value>::type> { + #if !defined(__clang__) || __clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ > 3) + typedef typename std::underlying_type<TEnum>::type + RealType; + #else + // implementation when std::underlying_type is not supported + typedef unsigned long + RealType; + #endif + + static const int minSize = Pusher<RealType>::minSize; + static const int maxSize = Pusher<RealType>::maxSize; + + static PushedObject push(lua_State* state, TEnum value) noexcept { + return Pusher<RealType>::push(state, static_cast<RealType>(value)); + } +}; + +// any function +// this specialization is not directly called, but is called by other specializations +template<typename TReturnType, typename... TParameters> +struct LuaContext::Pusher<TReturnType (TParameters...)> +{ + static const int minSize = 1; + static const int maxSize = 1; + + // counts the number of arguments + typedef FunctionArgumentsCounter<TParameters...> + LocalFunctionArgumentsCounter; + + // this is the version of "push" for non-trivially destructible function objects + template<typename TFunctionObject> + static auto push(lua_State* state, TFunctionObject fn) noexcept + -> typename std::enable_if<!boost::has_trivial_destructor<TFunctionObject>::value, PushedObject>::type + { + // TODO: is_move_constructible not supported by some compilers + //static_assert(std::is_move_constructible<TFunctionObject>::value, "The function object must be move-constructible"); + + // when the lua script calls the thing we will push on the stack, we want "fn" to be executed + // if we used lua's cfunctions system, we could not detect when the function is no longer in use, which could cause problems + // so we use userdata instead + + // this function is called when the lua script tries to call our custom data type + // we transfer execution to the "callback" function below + const auto callCallback = [](lua_State* lua) -> int { + assert(lua_gettop(lua) >= 1); + assert(lua_isuserdata(lua, 1)); + auto function = static_cast<TFunctionObject*>(lua_touserdata(lua, 1)); + assert(function); + + return callback(lua, function, lua_gettop(lua) - 1).release(); + }; + + // this one is called when lua's garbage collector no longer needs our custom data type + // we call the function object's destructor + const auto garbageCallback = [](lua_State* lua) -> int { + assert(lua_gettop(lua) == 1); + auto function = static_cast<TFunctionObject*>(lua_touserdata(lua, 1)); + assert(function); + function->~TFunctionObject(); + return 0; + }; + + // creating the object + // lua_newuserdata allocates memory in the internals of the lua library and returns it so we can fill it + // and that's what we do with placement-new + const auto functionLocation = static_cast<TFunctionObject*>(lua_newuserdata(state, sizeof(TFunctionObject))); + new (functionLocation) TFunctionObject(std::move(fn)); + + // creating the metatable (over the object on the stack) + // lua_settable pops the key and value we just pushed, so stack management is easy + // all that remains on the stack after these function calls is the metatable + lua_newtable(state); + lua_pushstring(state, "__call"); + lua_pushcfunction(state, callCallback); + lua_settable(state, -3); + + lua_pushstring(state, "__gc"); + lua_pushcfunction(state, garbageCallback); + lua_settable(state, -3); + + // at this point, the stack contains the object at offset -2 and the metatable at offset -1 + // lua_setmetatable will bind the two together and pop the metatable + // our custom function remains on the stack (and that's what we want) + lua_setmetatable(state, -2); + + return PushedObject{state, 1}; + } + + // this is the version of "push" for trivially destructible objects + template<typename TFunctionObject> + static auto push(lua_State* state, TFunctionObject fn) noexcept + -> typename std::enable_if<boost::has_trivial_destructor<TFunctionObject>::value, PushedObject>::type + { + // TODO: is_move_constructible not supported by some compilers + //static_assert(std::is_move_constructible<TFunctionObject>::value, "The function object must be move-constructible"); + + // when the lua script calls the thing we will push on the stack, we want "fn" to be executed + // since "fn" doesn't need to be destroyed, we simply push it on the stack + + // this is the cfunction that is the callback + const auto function = [](lua_State* state_) -> int + { + // the function object is an upvalue + const auto toCall = static_cast<TFunctionObject*>(lua_touserdata(state_, lua_upvalueindex(1))); + return callback(state_, toCall, lua_gettop(state_)).release(); + }; + + // we copy the function object onto the stack + const auto functionObjectLocation = static_cast<TFunctionObject*>(lua_newuserdata(state, sizeof(TFunctionObject))); + new (functionObjectLocation) TFunctionObject(std::move(fn)); + + // pushing the function with the function object as upvalue + lua_pushcclosure(state, function, 1); + return PushedObject{state, 1}; + } + + // this is the version of "push" for pointer to functions + static auto push(lua_State* state, TReturnType (*fn)(TParameters...)) noexcept + -> PushedObject + { + // when the lua script calls the thing we will push on the stack, we want "fn" to be executed + // since "fn" doesn't need to be destroyed, we simply push it on the stack + + // this is the cfunction that is the callback + const auto function = [](lua_State* state_) -> int + { + // the function object is an upvalue + const auto toCall = reinterpret_cast<TReturnType (*)(TParameters...)>(lua_touserdata(state_, lua_upvalueindex(1))); + return callback(state_, toCall, lua_gettop(state_)).release(); + }; + + // we copy the function object onto the stack + lua_pushlightuserdata(state, reinterpret_cast<void*>(fn)); + + // pushing the function with the function object as upvalue + lua_pushcclosure(state, function, 1); + return PushedObject{state, 1}; + } + + // this is the version of "push" for references to functions + static auto push(lua_State* state, TReturnType (&fn)(TParameters...)) noexcept + -> PushedObject + { + return push(state, &fn); + } + +private: + // callback that calls the function object + // this function is used by the callbacks and handles loading arguments from the stack and pushing the return value back + template<typename TFunctionObject> + static auto callback(lua_State* state, TFunctionObject* toCall, int argumentsCount) + -> PushedObject + { + // checking if number of parameters is correct + if (argumentsCount < LocalFunctionArgumentsCounter::min) { + // if not, using lua_error to return an error + luaL_where(state, 1); + lua_pushstring(state, "This function requires at least "); + lua_pushnumber(state, LocalFunctionArgumentsCounter::min); + lua_pushstring(state, " parameter(s)"); + lua_concat(state, 4); + luaError(state); + + } else if (argumentsCount > LocalFunctionArgumentsCounter::max) { + // if not, using lua_error to return an error + luaL_where(state, 1); + lua_pushstring(state, "This function requires at most "); + lua_pushnumber(state, LocalFunctionArgumentsCounter::max); + lua_pushstring(state, " parameter(s)"); + lua_concat(state, 4); + luaError(state); + } + + // calling the function + try { + return callback2(state, *toCall, argumentsCount); + + } catch (const WrongTypeException& ex) { + // wrong parameter type, using lua_error to return an error + luaL_where(state, 1); + lua_pushstring(state, "Unable to convert parameter from "); + lua_pushstring(state, ex.luaType.c_str()); + lua_pushstring(state, " to "); + lua_pushstring(state, ex.destination.name()); + lua_concat(state, 5); + luaError(state); + + } catch (const std::exception& e) { + luaL_where(state, 1); + lua_pushstring(state, "Caught exception: "); + lua_pushstring(state, e.what()); + lua_concat(state, 3); + luaError(state); + } catch (...) { + Pusher<std::exception_ptr>::push(state, std::current_exception()).release(); + luaError(state); + } + } + + template<typename TFunctionObject> + static auto callback2(lua_State* state, TFunctionObject&& toCall, int argumentsCount) + -> typename std::enable_if<!std::is_void<TReturnType>::value && !std::is_void<TFunctionObject>::value, PushedObject>::type + { + // pushing the result on the stack and returning number of pushed elements + typedef Pusher<typename std::decay<TReturnType>::type> + P; + return P::push(state, readIntoFunction(state, tag<TReturnType>{}, toCall, -argumentsCount, tag<TParameters>{}...)); + } + + template<typename TFunctionObject> + static auto callback2(lua_State* state, TFunctionObject&& toCall, int argumentsCount) + -> typename std::enable_if<std::is_void<TReturnType>::value && !std::is_void<TFunctionObject>::value, PushedObject>::type + { + readIntoFunction(state, tag<TReturnType>{}, toCall, -argumentsCount, tag<TParameters>{}...); + return PushedObject{state, 0}; + } +}; + +// C function pointers +template<typename TReturnType, typename... TParameters> +struct LuaContext::Pusher<TReturnType (*)(TParameters...)> +{ + // using the function-pushing implementation + typedef Pusher<TReturnType (TParameters...)> + SubPusher; + static const int minSize = SubPusher::minSize; + static const int maxSize = SubPusher::maxSize; + + template<typename TType> + static PushedObject push(lua_State* state, TType value) noexcept { + return SubPusher::push(state, value); + } +}; + +// C function references +template<typename TReturnType, typename... TParameters> +struct LuaContext::Pusher<TReturnType (&)(TParameters...)> +{ + // using the function-pushing implementation + typedef Pusher<TReturnType(TParameters...)> + SubPusher; + static const int minSize = SubPusher::minSize; + static const int maxSize = SubPusher::maxSize; + + template<typename TType> + static PushedObject push(lua_State* state, TType value) noexcept { + return SubPusher::push(state, value); + } +}; + +// std::function +template<typename TReturnType, typename... TParameters> +struct LuaContext::Pusher<std::function<TReturnType (TParameters...)>> +{ + // using the function-pushing implementation + typedef Pusher<TReturnType (TParameters...)> + SubPusher; + static const int minSize = SubPusher::minSize; + static const int maxSize = SubPusher::maxSize; + + static PushedObject push(lua_State* state, const std::function<TReturnType (TParameters...)>& value) noexcept { + return SubPusher::push(state, value); + } +}; + +// boost::variant +template<typename... TTypes> +struct LuaContext::Pusher<boost::variant<TTypes...>> +{ + static const int minSize = PusherMinSize<TTypes...>::size; + static const int maxSize = PusherMaxSize<TTypes...>::size; + + static PushedObject push(lua_State* state, const boost::variant<TTypes...>& value) noexcept { + PushedObject obj{state, 0}; + VariantWriter writer{state, obj}; + value.apply_visitor(writer); + return obj; + } + +private: + struct VariantWriter : public boost::static_visitor<> { + template<typename TType> + void operator()(TType value) noexcept + { + obj = Pusher<typename std::decay<TType>::type>::push(state, std::move(value)); + } + + VariantWriter(lua_State* state_, PushedObject& obj_) : state(state_), obj(obj_) {} + lua_State* state; + PushedObject& obj; + }; +}; + +// boost::optional +template<typename TType> +struct LuaContext::Pusher<boost::optional<TType>> { + typedef Pusher<typename std::decay<TType>::type> + UnderlyingPusher; + + static const int minSize = UnderlyingPusher::minSize < 1 ? UnderlyingPusher::minSize : 1; + static const int maxSize = UnderlyingPusher::maxSize > 1 ? UnderlyingPusher::maxSize : 1; + + static PushedObject push(lua_State* state, const boost::optional<TType>& value) noexcept { + if (value) { + return UnderlyingPusher::push(state, value.get()); + } else { + lua_pushnil(state); + return PushedObject{state, 1}; + } + } +}; + +// tuple +template<typename... TTypes> +struct LuaContext::Pusher<std::tuple<TTypes...>> { + // TODO: NOT EXCEPTION SAFE /!\ // + static const int minSize = PusherTotalMinSize<TTypes...>::size; + static const int maxSize = PusherTotalMaxSize<TTypes...>::size; + + static PushedObject push(lua_State* state, const std::tuple<TTypes...>& value) noexcept { + return PushedObject{state, push2(state, value, std::integral_constant<int,0>{})}; + } + + static PushedObject push(lua_State* state, std::tuple<TTypes...>&& value) noexcept { + return PushedObject{state, push2(state, std::move(value), std::integral_constant<int,0>{})}; + } + +private: + template<int N> + static int push2(lua_State* state, const std::tuple<TTypes...>& value, std::integral_constant<int,N>) noexcept { + typedef typename std::tuple_element<N,std::tuple<TTypes...>>::type ElemType; + + return Pusher<typename std::decay<ElemType>::type>::push(state, std::get<N>(value)).release() + + push2(state, value, std::integral_constant<int,N+1>{}); + } + + template<int N> + static int push2(lua_State* state, std::tuple<TTypes...>&& value, std::integral_constant<int,N>) noexcept { + typedef typename std::tuple_element<N,std::tuple<TTypes...>>::type ElemType; + + return Pusher<typename std::decay<ElemType>::type>::push(state, std::move(std::get<N>(value))).release() + + push2(state, std::move(value), std::integral_constant<int,N+1>{}); + } + + static int push2(lua_State* /*state*/, const std::tuple<TTypes...>&, std::integral_constant<int,sizeof...(TTypes)>) noexcept { + return 0; + } + + static int push2(lua_State* /*state*/, std::tuple<TTypes...>&&, std::integral_constant<int,sizeof...(TTypes)>) noexcept { + return 0; + } +}; + +/**************************************************/ +/* READ FUNCTIONS */ +/**************************************************/ +// specializations of the Reader structures + +// opaque Lua references +template<> +struct LuaContext::Reader<LuaContext::LuaObject> +{ + static auto read(lua_State* state, int index) + -> boost::optional<LuaContext::LuaObject> + { + LuaContext::LuaObject obj(state, index); + return obj; + } +}; + +// reading null +template<> +struct LuaContext::Reader<std::nullptr_t> +{ + static auto read(lua_State* state, int index) + -> boost::optional<std::nullptr_t> + { + if (!lua_isnil(state, index)) + return boost::none; + return nullptr; + } +}; + +// integrals +template<typename TType> +struct LuaContext::Reader< + TType, + typename std::enable_if<std::is_integral<TType>::value>::type + > +{ + static auto read(lua_State* state, int index) + -> boost::optional<TType> + { +# if LUA_VERSION_NUM >= 502 + + int success; + auto value = lua_tointegerx(state, index, &success); + if (success == 0) + return boost::none; + return static_cast<TType>(value); + +# else + + if (!lua_isnumber(state, index)) + return boost::none; + return static_cast<TType>(lua_tointeger(state, index)); + +# endif + } +}; + +// floating points +template<typename TType> +struct LuaContext::Reader< + TType, + typename std::enable_if<std::is_floating_point<TType>::value>::type + > +{ + static auto read(lua_State* state, int index) + -> boost::optional<TType> + { +# if LUA_VERSION_NUM >= 502 + + int success; + auto value = lua_tonumberx(state, index, &success); + if (success == 0) + return boost::none; + return static_cast<TType>(value); + +# else + + if (!lua_isnumber(state, index)) + return boost::none; + return static_cast<TType>(lua_tonumber(state, index)); + +# endif + } +}; + +// boolean +template<> +struct LuaContext::Reader<bool> +{ + static auto read(lua_State* state, int index) + -> boost::optional<bool> + { + if (!lua_isboolean(state, index)) + return boost::none; + return lua_toboolean(state, index) != 0; + } +}; + +// string +// lua_tostring returns a temporary pointer, but that's not a problem since we copy +// the data into a std::string +template<> +struct LuaContext::Reader<std::string> +{ + static auto read(lua_State* state, int index) + -> boost::optional<std::string> + { + std::string result; + + // lua_tolstring might convert the variable that would confuse lua_next, so we + // make a copy of the variable. + lua_pushvalue(state, index); + + size_t len; + const auto val = lua_tolstring(state, -1, &len); + + if (val != nullptr) + result.assign(val, len); + + lua_pop(state, 1); + + return val != nullptr ? boost::optional<std::string>{ std::move(result) } : boost::none; + } +}; + +// enums +template<typename TType> +struct LuaContext::Reader< + TType, + typename std::enable_if<std::is_enum<TType>::value>::type + > +{ + static auto read(lua_State* state, int index) + -> boost::optional<TType> + { + if (!lua_isnumber(state, index) || fmod(lua_tonumber(state, index), 1.) != 0) + return boost::none; + return static_cast<TType>(lua_tointeger(state, index)); + } +}; + +// LuaFunctionCaller +template<typename TRetValue, typename... TParameters> +struct LuaContext::Reader<LuaContext::LuaFunctionCaller<TRetValue (TParameters...)>> +{ + typedef LuaFunctionCaller<TRetValue (TParameters...)> + ReturnType; + + static auto read(lua_State* state, int index) + -> boost::optional<ReturnType> + { + if (lua_isfunction(state, index) == 0 && lua_isuserdata(state, index) == 0) + return boost::none; + return ReturnType(state, index); + } +}; + +// function +template<typename TRetValue, typename... TParameters> +struct LuaContext::Reader<std::function<TRetValue (TParameters...)>> +{ + static auto read(lua_State* state, int index) + -> boost::optional<std::function<TRetValue (TParameters...)>> + { + if (auto val = Reader<LuaContext::LuaFunctionCaller<TRetValue (TParameters...)>>::read(state, index)) + { + std::function<TRetValue (TParameters...)> f{*val}; + return boost::optional<std::function<TRetValue (TParameters...)>>{std::move(f)}; + } + + return boost::none; + } +}; + +// vector of pairs +template<typename TType1, typename TType2> +struct LuaContext::Reader<std::vector<std::pair<TType1,TType2>>> +{ + static auto read(lua_State* state, int index) + -> boost::optional<std::vector<std::pair<TType1, TType2>>> + { + if (!lua_istable(state, index)) + return boost::none; + + std::vector<std::pair<TType1, TType2>> result; + + // we traverse the table at the top of the stack + lua_pushnil(state); // first key + while (lua_next(state, (index > 0) ? index : (index - 1)) != 0) { + // now a key and its value are pushed on the stack + try { + auto val1 = Reader<TType1>::read(state, -2); + auto val2 = Reader<TType2>::read(state, -1); + + if (!val1.is_initialized() || !val2.is_initialized()) { + lua_pop(state, 2); // we remove the value and the key + return {}; + } + + result.push_back({ val1.get(), val2.get() }); + lua_pop(state, 1); // we remove the value but keep the key for the next iteration + + } catch(...) { + lua_pop(state, 2); // we remove the value and the key + return {}; + } + } + + return { std::move(result) }; + } +}; + +// map +template<typename TKey, typename TValue> +struct LuaContext::Reader<std::map<TKey,TValue>> +{ + static auto read(lua_State* state, int index) + -> boost::optional<std::map<TKey,TValue>> + { + if (!lua_istable(state, index)) + return boost::none; + + std::map<TKey,TValue> result; + + // we traverse the table at the top of the stack + lua_pushnil(state); // first key + while (lua_next(state, (index > 0) ? index : (index - 1)) != 0) { + // now a key and its value are pushed on the stack + try { + auto key = Reader<TKey>::read(state, -2); + auto value = Reader<TValue>::read(state, -1); + + if (!key.is_initialized() || !value.is_initialized()) { + lua_pop(state, 2); // we remove the value and the key + return {}; + } + + result.insert({ key.get(), value.get() }); + lua_pop(state, 1); // we remove the value but keep the key for the next iteration + + } catch(...) { + lua_pop(state, 2); // we remove the value and the key + return {}; + } + } + + return { std::move(result) }; + } +}; + +// unordered_map +template<typename TKey, typename TValue, typename THash, typename TKeyEqual> +struct LuaContext::Reader<std::unordered_map<TKey,TValue,THash,TKeyEqual>> +{ + static auto read(lua_State* state, int index) + -> boost::optional<std::unordered_map<TKey,TValue,THash,TKeyEqual>> + { + if (!lua_istable(state, index)) + return boost::none; + + std::unordered_map<TKey,TValue,THash,TKeyEqual> result; + + // we traverse the table at the top of the stack + lua_pushnil(state); // first key + while (lua_next(state, (index > 0) ? index : (index - 1)) != 0) { + // now a key and its value are pushed on the stack + try { + auto key = Reader<TKey>::read(state, -2); + auto value = Reader<TValue>::read(state, -1); + + if (!key.is_initialized() || !value.is_initialized()) { + lua_pop(state, 2); // we remove the value and the key + return {}; + } + + result.insert({ key.get(), value.get() }); + lua_pop(state, 1); // we remove the value but keep the key for the next iteration + + } catch(...) { + lua_pop(state, 2); // we remove the value and the key + return {}; + } + } + + return { std::move(result) }; + } +}; + +// optional +// IMPORTANT: optional means "either nil or the value of the right type" +// * if the value is nil, then an optional containing an empty optional is returned +// * if the value is of the right type, then an optional containing an optional containing the value is returned +// * if the value is of the wrong type, then an empty optional is returned +template<typename TType> +struct LuaContext::Reader<boost::optional<TType>> +{ + static auto read(lua_State* state, int index) + -> boost::optional<boost::optional<TType>> + { + if (lua_isnil(state, index)) + return boost::optional<TType>{boost::none}; + if (auto&& other = Reader<TType>::read(state, index)) + return std::move(other); + return boost::none; + } +}; + +// variant +template<typename... TTypes> +struct LuaContext::Reader<boost::variant<TTypes...>> +{ + typedef boost::variant<TTypes...> + ReturnType; + +private: + // class doing operations for a range of types from TIterBegin to TIterEnd + template<typename TIterBegin, typename TIterEnd, typename = void> + struct VariantReader + { + using SubReader = Reader<typename std::decay<typename boost::mpl::deref<TIterBegin>::type>::type>; + + static auto read(lua_State* state, int index) + -> boost::optional<ReturnType> + { + // note: using SubReader::read triggers a compilation error when used with a reference + if (const auto val = SubReader::read(state, index)) + return boost::variant<TTypes...>{*val}; + return VariantReader<typename boost::mpl::next<TIterBegin>::type, TIterEnd>::read(state, index); + } + }; + + // specialization of class above being called when list of remaining types is empty + template<typename TIterBegin, typename TIterEnd> + struct VariantReader<TIterBegin, TIterEnd, typename std::enable_if<boost::mpl::distance<TIterBegin, TIterEnd>::type::value == 0>::type> + { + static auto read(lua_State* /*state*/, int /*index*/) + -> boost::optional<ReturnType> + { + return boost::none; + } + }; + + // this is the main type + typedef VariantReader<typename boost::mpl::begin<typename ReturnType::types>::type, typename boost::mpl::end<typename ReturnType::types>::type> + MainVariantReader; + +public: + static auto read(lua_State* state, int index) + -> boost::optional<ReturnType> + { + return MainVariantReader::read(state, index); + } +}; + +// reading a tuple +// tuple have an additional argument for their functions, that is the maximum size to read +// if maxSize is smaller than the tuple size, then the remaining parameters will be left to default value +template<> +struct LuaContext::Reader<std::tuple<>> +{ + static auto read(lua_State* /*state*/, int /*index*/, int /*maxSize*/ = 0) + -> boost::optional<std::tuple<>> + { + return std::tuple<>{}; + } +}; + +template<typename TFirst, typename... TOthers> +struct LuaContext::Reader<std::tuple<TFirst, TOthers...>, + typename std::enable_if<!LuaContext::IsOptional<TFirst>::value>::type // TODO: replace by std::is_default_constructible when it works on every compiler + > +{ + // this is the "TFirst is NOT default constructible" version + + typedef std::tuple<TFirst, TOthers...> + ReturnType; + + static auto read(lua_State* state, int index, int maxSize = std::tuple_size<ReturnType>::value) + -> boost::optional<ReturnType> + { + if (maxSize <= 0) + return boost::none; + + auto firstVal = Reader<TFirst>::read(state, index); + auto othersVal = Reader<std::tuple<TOthers...>>::read(state, index + 1, maxSize - 1); + + if (!firstVal || !othersVal) + return boost::none; + + return std::tuple_cat(std::tuple<TFirst>(*firstVal), std::move(*othersVal)); + } +}; + +template<typename TFirst, typename... TOthers> +struct LuaContext::Reader<std::tuple<TFirst, TOthers...>, + typename std::enable_if<LuaContext::IsOptional<TFirst>::value>::type // TODO: replace by std::is_default_constructible when it works on every compiler + > +{ + // this is the "TFirst is default-constructible" version + + typedef std::tuple<TFirst, TOthers...> + ReturnType; + + static auto read(lua_State* state, int index, int maxSize = std::tuple_size<ReturnType>::value) + -> boost::optional<ReturnType> + { + auto othersVal = Reader<std::tuple<TOthers...>>::read(state, index + 1, maxSize - 1); + if (!othersVal) + return boost::none; + + if (maxSize <= 0) + return std::tuple_cat(std::tuple<TFirst>(), std::move(*othersVal)); + + auto firstVal = Reader<TFirst>::read(state, index); + if (!firstVal) + return boost::none; + + return std::tuple_cat(std::tuple<TFirst>(*firstVal), std::move(*othersVal)); + } +}; + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif diff --git a/ext/protozero/include/protozero/basic_pbf_builder.hpp b/ext/protozero/include/protozero/basic_pbf_builder.hpp new file mode 100644 index 0000000..0ede726 --- /dev/null +++ b/ext/protozero/include/protozero/basic_pbf_builder.hpp @@ -0,0 +1,266 @@ +#ifndef PROTOZERO_BASIC_PBF_BUILDER_HPP +#define PROTOZERO_BASIC_PBF_BUILDER_HPP + +/***************************************************************************** + +protozero - Minimalistic protocol buffer decoder and encoder in C++. + +This file is from https://github.com/mapbox/protozero where you can find more +documentation. + +*****************************************************************************/ + +/** + * @file basic_pbf_builder.hpp + * + * @brief Contains the basic_pbf_builder template class. + */ + +#include "basic_pbf_writer.hpp" +#include "types.hpp" + +#include <type_traits> + +namespace protozero { + +/** + * The basic_pbf_builder is used to write PBF formatted messages into a buffer. + * It is based on the basic_pbf_writer class and has all the same methods. The + * difference is that while the pbf_writer class takes an integer tag, + * this template class takes a tag of the template type T. The idea is that + * T will be an enumeration value and this helps reduce the possibility of + * programming errors. + * + * Almost all methods in this class can throw an std::bad_alloc exception if + * the underlying buffer class wants to resize. + * + * Read the tutorial to understand how this class is used. In most cases you + * want to use the pbf_builder class which uses a std::string as buffer type. + */ +template <typename TBuffer, typename T> +class basic_pbf_builder : public basic_pbf_writer<TBuffer> { + + static_assert(std::is_same<pbf_tag_type, typename std::underlying_type<T>::type>::value, + "T must be enum with underlying type protozero::pbf_tag_type"); + +public: + + /// The type of messages this class will build. + using enum_type = T; + + basic_pbf_builder() = default; + + /** + * Create a builder using the given string as a data store. The object + * stores a reference to that string and adds all data to it. The string + * doesn't have to be empty. The pbf_message object will just append data. + */ + explicit basic_pbf_builder(TBuffer& data) noexcept : + basic_pbf_writer<TBuffer>{data} { + } + + /** + * Construct a pbf_builder for a submessage from the pbf_message or + * pbf_writer of the parent message. + * + * @param parent_writer The parent pbf_message or pbf_writer + * @param tag Tag of the field that will be written + */ + template <typename P> + basic_pbf_builder(basic_pbf_writer<TBuffer>& parent_writer, P tag) noexcept : + basic_pbf_writer<TBuffer>{parent_writer, pbf_tag_type(tag)} { + } + +/// @cond INTERNAL +#define PROTOZERO_WRITER_WRAP_ADD_SCALAR(name, type) \ + void add_##name(T tag, type value) { \ + basic_pbf_writer<TBuffer>::add_##name(pbf_tag_type(tag), value); \ + } + + PROTOZERO_WRITER_WRAP_ADD_SCALAR(bool, bool) + PROTOZERO_WRITER_WRAP_ADD_SCALAR(enum, int32_t) + PROTOZERO_WRITER_WRAP_ADD_SCALAR(int32, int32_t) + PROTOZERO_WRITER_WRAP_ADD_SCALAR(sint32, int32_t) + PROTOZERO_WRITER_WRAP_ADD_SCALAR(uint32, uint32_t) + PROTOZERO_WRITER_WRAP_ADD_SCALAR(int64, int64_t) + PROTOZERO_WRITER_WRAP_ADD_SCALAR(sint64, int64_t) + PROTOZERO_WRITER_WRAP_ADD_SCALAR(uint64, uint64_t) + PROTOZERO_WRITER_WRAP_ADD_SCALAR(fixed32, uint32_t) + PROTOZERO_WRITER_WRAP_ADD_SCALAR(sfixed32, int32_t) + PROTOZERO_WRITER_WRAP_ADD_SCALAR(fixed64, uint64_t) + PROTOZERO_WRITER_WRAP_ADD_SCALAR(sfixed64, int64_t) + PROTOZERO_WRITER_WRAP_ADD_SCALAR(float, float) + PROTOZERO_WRITER_WRAP_ADD_SCALAR(double, double) + +#undef PROTOZERO_WRITER_WRAP_ADD_SCALAR +/// @endcond + + /** + * Add "bytes" field to data. + * + * @param tag Tag of the field + * @param value Pointer to value to be written + * @param size Number of bytes to be written + */ + void add_bytes(T tag, const char* value, std::size_t size) { + basic_pbf_writer<TBuffer>::add_bytes(pbf_tag_type(tag), value, size); + } + + /** + * Add "bytes" field to data. + * + * @param tag Tag of the field + * @param value Value to be written + */ + void add_bytes(T tag, const data_view& value) { + basic_pbf_writer<TBuffer>::add_bytes(pbf_tag_type(tag), value); + } + + /** + * Add "bytes" field to data. + * + * @param tag Tag of the field + * @param value Value to be written + */ + void add_bytes(T tag, const std::string& value) { + basic_pbf_writer<TBuffer>::add_bytes(pbf_tag_type(tag), value); + } + + /** + * Add "bytes" field to data. Bytes from the value are written until + * a null byte is encountered. The null byte is not added. + * + * @param tag Tag of the field + * @param value Pointer to zero-delimited value to be written + */ + void add_bytes(T tag, const char* value) { + basic_pbf_writer<TBuffer>::add_bytes(pbf_tag_type(tag), value); + } + + /** + * Add "bytes" field to data using vectored input. All the data in the + * 2nd and further arguments is "concatenated" with only a single copy + * into the final buffer. + * + * This will work with objects of any type supporting the data() and + * size() methods like std::string or protozero::data_view. + * + * Example: + * @code + * std::string data1 = "abc"; + * std::string data2 = "xyz"; + * builder.add_bytes_vectored(1, data1, data2); + * @endcode + * + * @tparam Ts List of types supporting data() and size() methods. + * @param tag Tag of the field + * @param values List of objects of types Ts with data to be appended. + */ + template <typename... Ts> + void add_bytes_vectored(T tag, Ts&&... values) { + basic_pbf_writer<TBuffer>::add_bytes_vectored(pbf_tag_type(tag), std::forward<Ts>(values)...); + } + + /** + * Add "string" field to data. + * + * @param tag Tag of the field + * @param value Pointer to value to be written + * @param size Number of bytes to be written + */ + void add_string(T tag, const char* value, std::size_t size) { + basic_pbf_writer<TBuffer>::add_string(pbf_tag_type(tag), value, size); + } + + /** + * Add "string" field to data. + * + * @param tag Tag of the field + * @param value Value to be written + */ + void add_string(T tag, const data_view& value) { + basic_pbf_writer<TBuffer>::add_string(pbf_tag_type(tag), value); + } + + /** + * Add "string" field to data. + * + * @param tag Tag of the field + * @param value Value to be written + */ + void add_string(T tag, const std::string& value) { + basic_pbf_writer<TBuffer>::add_string(pbf_tag_type(tag), value); + } + + /** + * Add "string" field to data. Bytes from the value are written until + * a null byte is encountered. The null byte is not added. + * + * @param tag Tag of the field + * @param value Pointer to value to be written + */ + void add_string(T tag, const char* value) { + basic_pbf_writer<TBuffer>::add_string(pbf_tag_type(tag), value); + } + + /** + * Add "message" field to data. + * + * @param tag Tag of the field + * @param value Pointer to message to be written + * @param size Length of the message + */ + void add_message(T tag, const char* value, std::size_t size) { + basic_pbf_writer<TBuffer>::add_message(pbf_tag_type(tag), value, size); + } + + /** + * Add "message" field to data. + * + * @param tag Tag of the field + * @param value Value to be written. The value must be a complete message. + */ + void add_message(T tag, const data_view& value) { + basic_pbf_writer<TBuffer>::add_message(pbf_tag_type(tag), value); + } + + /** + * Add "message" field to data. + * + * @param tag Tag of the field + * @param value Value to be written. The value must be a complete message. + */ + void add_message(T tag, const std::string& value) { + basic_pbf_writer<TBuffer>::add_message(pbf_tag_type(tag), value); + } + +/// @cond INTERNAL +#define PROTOZERO_WRITER_WRAP_ADD_PACKED(name) \ + template <typename InputIterator> \ + void add_packed_##name(T tag, InputIterator first, InputIterator last) { \ + basic_pbf_writer<TBuffer>::add_packed_##name(pbf_tag_type(tag), first, last); \ + } + + PROTOZERO_WRITER_WRAP_ADD_PACKED(bool) + PROTOZERO_WRITER_WRAP_ADD_PACKED(enum) + PROTOZERO_WRITER_WRAP_ADD_PACKED(int32) + PROTOZERO_WRITER_WRAP_ADD_PACKED(sint32) + PROTOZERO_WRITER_WRAP_ADD_PACKED(uint32) + PROTOZERO_WRITER_WRAP_ADD_PACKED(int64) + PROTOZERO_WRITER_WRAP_ADD_PACKED(sint64) + PROTOZERO_WRITER_WRAP_ADD_PACKED(uint64) + PROTOZERO_WRITER_WRAP_ADD_PACKED(fixed32) + PROTOZERO_WRITER_WRAP_ADD_PACKED(sfixed32) + PROTOZERO_WRITER_WRAP_ADD_PACKED(fixed64) + PROTOZERO_WRITER_WRAP_ADD_PACKED(sfixed64) + PROTOZERO_WRITER_WRAP_ADD_PACKED(float) + PROTOZERO_WRITER_WRAP_ADD_PACKED(double) + +#undef PROTOZERO_WRITER_WRAP_ADD_PACKED +/// @endcond + +}; // class basic_pbf_builder + +} // end namespace protozero + +#endif // PROTOZERO_BASIC_PBF_BUILDER_HPP diff --git a/ext/protozero/include/protozero/basic_pbf_writer.hpp b/ext/protozero/include/protozero/basic_pbf_writer.hpp new file mode 100644 index 0000000..f167c4d --- /dev/null +++ b/ext/protozero/include/protozero/basic_pbf_writer.hpp @@ -0,0 +1,1054 @@ +#ifndef PROTOZERO_BASIC_PBF_WRITER_HPP +#define PROTOZERO_BASIC_PBF_WRITER_HPP + +/***************************************************************************** + +protozero - Minimalistic protocol buffer decoder and encoder in C++. + +This file is from https://github.com/mapbox/protozero where you can find more +documentation. + +*****************************************************************************/ + +/** + * @file basic_pbf_writer.hpp + * + * @brief Contains the basic_pbf_writer template class. + */ + +#include "buffer_tmpl.hpp" +#include "config.hpp" +#include "data_view.hpp" +#include "types.hpp" +#include "varint.hpp" + +#if PROTOZERO_BYTE_ORDER != PROTOZERO_LITTLE_ENDIAN +# include <protozero/byteswap.hpp> +#endif + +#include <cstddef> +#include <cstdint> +#include <cstring> +#include <initializer_list> +#include <iterator> +#include <limits> +#include <string> +#include <utility> + +namespace protozero { + +namespace detail { + + template <typename B, typename T> class packed_field_varint; + template <typename B, typename T> class packed_field_svarint; + template <typename B, typename T> class packed_field_fixed; + +} // end namespace detail + +/** + * The basic_pbf_writer is used to write PBF formatted messages into a buffer. + * + * This uses TBuffer as the type for the underlaying buffer. In typical uses + * this is std::string, but you can use a different type that must support + * the right interface. Please see the documentation for details. + * + * Almost all methods in this class can throw an std::bad_alloc exception if + * the underlying buffer class wants to resize. + */ +template <typename TBuffer> +class basic_pbf_writer { + + // A pointer to a buffer holding the data already written to the PBF + // message. For default constructed writers or writers that have been + // rolled back, this is a nullptr. + TBuffer* m_data = nullptr; + + // A pointer to a parent writer object if this is a submessage. If this + // is a top-level writer, it is a nullptr. + basic_pbf_writer* m_parent_writer = nullptr; + + // This is usually 0. If there is an open submessage, this is set in the + // parent to the rollback position, ie. the last position before the + // submessage was started. This is the position where the header of the + // submessage starts. + std::size_t m_rollback_pos = 0; + + // This is usually 0. If there is an open submessage, this is set in the + // parent to the position where the data of the submessage is written to. + std::size_t m_pos = 0; + + void add_varint(uint64_t value) { + protozero_assert(m_pos == 0 && "you can't add fields to a parent basic_pbf_writer if there is an existing basic_pbf_writer for a submessage"); + protozero_assert(m_data); + add_varint_to_buffer(m_data, value); + } + + void add_field(pbf_tag_type tag, pbf_wire_type type) { + protozero_assert(((tag > 0 && tag < 19000) || (tag > 19999 && tag <= ((1U << 29U) - 1))) && "tag out of range"); + const uint32_t b = (tag << 3U) | uint32_t(type); + add_varint(b); + } + + void add_tagged_varint(pbf_tag_type tag, uint64_t value) { + add_field(tag, pbf_wire_type::varint); + add_varint(value); + } + + template <typename T> + void add_fixed(T value) { + protozero_assert(m_pos == 0 && "you can't add fields to a parent basic_pbf_writer if there is an existing basic_pbf_writer for a submessage"); + protozero_assert(m_data); +#if PROTOZERO_BYTE_ORDER != PROTOZERO_LITTLE_ENDIAN + byteswap_inplace(&value); +#endif + buffer_customization<TBuffer>::append(m_data, reinterpret_cast<const char*>(&value), sizeof(T)); + } + + template <typename T, typename It> + void add_packed_fixed(pbf_tag_type tag, It first, It last, std::input_iterator_tag /*unused*/) { + if (first == last) { + return; + } + + basic_pbf_writer sw{*this, tag}; + + while (first != last) { + sw.add_fixed<T>(*first++); + } + } + + template <typename T, typename It> + void add_packed_fixed(pbf_tag_type tag, It first, It last, std::forward_iterator_tag /*unused*/) { + if (first == last) { + return; + } + + const auto length = std::distance(first, last); + add_length_varint(tag, sizeof(T) * pbf_length_type(length)); + reserve(sizeof(T) * std::size_t(length)); + + while (first != last) { + add_fixed<T>(*first++); + } + } + + template <typename It> + void add_packed_varint(pbf_tag_type tag, It first, It last) { + if (first == last) { + return; + } + + basic_pbf_writer sw{*this, tag}; + + while (first != last) { + sw.add_varint(uint64_t(*first++)); + } + } + + template <typename It> + void add_packed_svarint(pbf_tag_type tag, It first, It last) { + if (first == last) { + return; + } + + basic_pbf_writer sw{*this, tag}; + + while (first != last) { + sw.add_varint(encode_zigzag64(*first++)); + } + } + + // The number of bytes to reserve for the varint holding the length of + // a length-delimited field. The length has to fit into pbf_length_type, + // and a varint needs 8 bit for every 7 bit. + enum : int { + reserve_bytes = sizeof(pbf_length_type) * 8 / 7 + 1 + }; + + // If m_rollpack_pos is set to this special value, it means that when + // the submessage is closed, nothing needs to be done, because the length + // of the submessage has already been written correctly. + enum : std::size_t { + size_is_known = std::numeric_limits<std::size_t>::max() + }; + + void open_submessage(pbf_tag_type tag, std::size_t size) { + protozero_assert(m_pos == 0); + protozero_assert(m_data); + if (size == 0) { + m_rollback_pos = buffer_customization<TBuffer>::size(m_data); + add_field(tag, pbf_wire_type::length_delimited); + buffer_customization<TBuffer>::append_zeros(m_data, std::size_t(reserve_bytes)); + } else { + m_rollback_pos = size_is_known; + add_length_varint(tag, pbf_length_type(size)); + reserve(size); + } + m_pos = buffer_customization<TBuffer>::size(m_data); + } + + void rollback_submessage() { + protozero_assert(m_pos != 0); + protozero_assert(m_rollback_pos != size_is_known); + protozero_assert(m_data); + buffer_customization<TBuffer>::resize(m_data, m_rollback_pos); + m_pos = 0; + } + + void commit_submessage() { + protozero_assert(m_pos != 0); + protozero_assert(m_rollback_pos != size_is_known); + protozero_assert(m_data); + const auto length = pbf_length_type(buffer_customization<TBuffer>::size(m_data) - m_pos); + + protozero_assert(buffer_customization<TBuffer>::size(m_data) >= m_pos - reserve_bytes); + const auto n = add_varint_to_buffer(buffer_customization<TBuffer>::at_pos(m_data, m_pos - reserve_bytes), length); + + buffer_customization<TBuffer>::erase_range(m_data, m_pos - reserve_bytes + n, m_pos); + m_pos = 0; + } + + void close_submessage() { + protozero_assert(m_data); + if (m_pos == 0 || m_rollback_pos == size_is_known) { + return; + } + if (buffer_customization<TBuffer>::size(m_data) - m_pos == 0) { + rollback_submessage(); + } else { + commit_submessage(); + } + } + + void add_length_varint(pbf_tag_type tag, pbf_length_type length) { + add_field(tag, pbf_wire_type::length_delimited); + add_varint(length); + } + +public: + + /** + * Create a writer using the specified buffer as a data store. The + * basic_pbf_writer stores a pointer to that buffer and adds all data to + * it. The buffer doesn't have to be empty. The basic_pbf_writer will just + * append data. + */ + explicit basic_pbf_writer(TBuffer& buffer) noexcept : + m_data{&buffer} { + } + + /** + * Create a writer without a data store. In this form the writer can not + * be used! + */ + basic_pbf_writer() noexcept = default; + + /** + * Construct a basic_pbf_writer for a submessage from the basic_pbf_writer + * of the parent message. + * + * @param parent_writer The basic_pbf_writer + * @param tag Tag (field number) of the field that will be written + * @param size Optional size of the submessage in bytes (use 0 for unknown). + * Setting this allows some optimizations but is only possible in + * a few very specific cases. + */ + basic_pbf_writer(basic_pbf_writer& parent_writer, pbf_tag_type tag, std::size_t size = 0) : + m_data{parent_writer.m_data}, + m_parent_writer{&parent_writer} { + m_parent_writer->open_submessage(tag, size); + } + + /// A basic_pbf_writer object can not be copied + basic_pbf_writer(const basic_pbf_writer&) = delete; + + /// A basic_pbf_writer object can not be copied + basic_pbf_writer& operator=(const basic_pbf_writer&) = delete; + + /** + * A basic_pbf_writer object can be moved. After this the other + * basic_pbf_writer will be invalid. + */ + basic_pbf_writer(basic_pbf_writer&& other) noexcept : + m_data{other.m_data}, + m_parent_writer{other.m_parent_writer}, + m_rollback_pos{other.m_rollback_pos}, + m_pos{other.m_pos} { + other.m_data = nullptr; + other.m_parent_writer = nullptr; + other.m_rollback_pos = 0; + other.m_pos = 0; + } + + /** + * A basic_pbf_writer object can be moved. After this the other + * basic_pbf_writer will be invalid. + */ + basic_pbf_writer& operator=(basic_pbf_writer&& other) noexcept { + m_data = other.m_data; + m_parent_writer = other.m_parent_writer; + m_rollback_pos = other.m_rollback_pos; + m_pos = other.m_pos; + other.m_data = nullptr; + other.m_parent_writer = nullptr; + other.m_rollback_pos = 0; + other.m_pos = 0; + return *this; + } + + ~basic_pbf_writer() noexcept { + try { + if (m_parent_writer != nullptr) { + m_parent_writer->close_submessage(); + } + } catch (...) { + // This try/catch is used to make the destructor formally noexcept. + // close_submessage() is not noexcept, but will not throw the way + // it is called here, so we are good. But to be paranoid, call... + std::terminate(); + } + } + + /** + * Check if this writer is valid. A writer is invalid if it was default + * constructed, moved from, or if commit() has been called on it. + * Otherwise it is valid. + */ + bool valid() const noexcept { + return m_data != nullptr; + } + + /** + * Swap the contents of this object with the other. + * + * @param other Other object to swap data with. + */ + void swap(basic_pbf_writer& other) noexcept { + using std::swap; + swap(m_data, other.m_data); + swap(m_parent_writer, other.m_parent_writer); + swap(m_rollback_pos, other.m_rollback_pos); + swap(m_pos, other.m_pos); + } + + /** + * Reserve size bytes in the underlying message store in addition to + * whatever the message store already holds. So unlike + * the `std::string::reserve()` method this is not an absolute size, + * but additional memory that should be reserved. + * + * @param size Number of bytes to reserve in underlying message store. + */ + void reserve(std::size_t size) { + protozero_assert(m_data); + buffer_customization<TBuffer>::reserve_additional(m_data, size); + } + + /** + * Commit this submessage. This does the same as when the basic_pbf_writer + * goes out of scope and is destructed. + * + * @pre Must be a basic_pbf_writer of a submessage, ie one opened with the + * basic_pbf_writer constructor taking a parent message. + * @post The basic_pbf_writer is invalid and can't be used any more. + */ + void commit() { + protozero_assert(m_parent_writer && "you can't call commit() on a basic_pbf_writer without a parent"); + protozero_assert(m_pos == 0 && "you can't call commit() on a basic_pbf_writer that has an open nested submessage"); + m_parent_writer->close_submessage(); + m_parent_writer = nullptr; + m_data = nullptr; + } + + /** + * Cancel writing of this submessage. The complete submessage will be + * removed as if it was never created and no fields were added. + * + * @pre Must be a basic_pbf_writer of a submessage, ie one opened with the + * basic_pbf_writer constructor taking a parent message. + * @post The basic_pbf_writer is invalid and can't be used any more. + */ + void rollback() { + protozero_assert(m_parent_writer && "you can't call rollback() on a basic_pbf_writer without a parent"); + protozero_assert(m_pos == 0 && "you can't call rollback() on a basic_pbf_writer that has an open nested submessage"); + m_parent_writer->rollback_submessage(); + m_parent_writer = nullptr; + m_data = nullptr; + } + + ///@{ + /** + * @name Scalar field writer functions + */ + + /** + * Add "bool" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_bool(pbf_tag_type tag, bool value) { + add_field(tag, pbf_wire_type::varint); + protozero_assert(m_pos == 0 && "you can't add fields to a parent basic_pbf_writer if there is an existing basic_pbf_writer for a submessage"); + protozero_assert(m_data); + m_data->push_back(char(value)); + } + + /** + * Add "enum" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_enum(pbf_tag_type tag, int32_t value) { + add_tagged_varint(tag, uint64_t(value)); + } + + /** + * Add "int32" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_int32(pbf_tag_type tag, int32_t value) { + add_tagged_varint(tag, uint64_t(value)); + } + + /** + * Add "sint32" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_sint32(pbf_tag_type tag, int32_t value) { + add_tagged_varint(tag, encode_zigzag32(value)); + } + + /** + * Add "uint32" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_uint32(pbf_tag_type tag, uint32_t value) { + add_tagged_varint(tag, value); + } + + /** + * Add "int64" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_int64(pbf_tag_type tag, int64_t value) { + add_tagged_varint(tag, uint64_t(value)); + } + + /** + * Add "sint64" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_sint64(pbf_tag_type tag, int64_t value) { + add_tagged_varint(tag, encode_zigzag64(value)); + } + + /** + * Add "uint64" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_uint64(pbf_tag_type tag, uint64_t value) { + add_tagged_varint(tag, value); + } + + /** + * Add "fixed32" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_fixed32(pbf_tag_type tag, uint32_t value) { + add_field(tag, pbf_wire_type::fixed32); + add_fixed<uint32_t>(value); + } + + /** + * Add "sfixed32" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_sfixed32(pbf_tag_type tag, int32_t value) { + add_field(tag, pbf_wire_type::fixed32); + add_fixed<int32_t>(value); + } + + /** + * Add "fixed64" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_fixed64(pbf_tag_type tag, uint64_t value) { + add_field(tag, pbf_wire_type::fixed64); + add_fixed<uint64_t>(value); + } + + /** + * Add "sfixed64" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_sfixed64(pbf_tag_type tag, int64_t value) { + add_field(tag, pbf_wire_type::fixed64); + add_fixed<int64_t>(value); + } + + /** + * Add "float" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_float(pbf_tag_type tag, float value) { + add_field(tag, pbf_wire_type::fixed32); + add_fixed<float>(value); + } + + /** + * Add "double" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_double(pbf_tag_type tag, double value) { + add_field(tag, pbf_wire_type::fixed64); + add_fixed<double>(value); + } + + /** + * Add "bytes" field to data. + * + * @param tag Tag (field number) of the field + * @param value Pointer to value to be written + * @param size Number of bytes to be written + */ + void add_bytes(pbf_tag_type tag, const char* value, std::size_t size) { + protozero_assert(m_pos == 0 && "you can't add fields to a parent basic_pbf_writer if there is an existing basic_pbf_writer for a submessage"); + protozero_assert(m_data); + protozero_assert(size <= std::numeric_limits<pbf_length_type>::max()); + add_length_varint(tag, pbf_length_type(size)); + buffer_customization<TBuffer>::append(m_data, value, size); + } + + /** + * Add "bytes" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_bytes(pbf_tag_type tag, const data_view& value) { + add_bytes(tag, value.data(), value.size()); + } + + /** + * Add "bytes" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_bytes(pbf_tag_type tag, const std::string& value) { + add_bytes(tag, value.data(), value.size()); + } + + /** + * Add "bytes" field to data. Bytes from the value are written until + * a null byte is encountered. The null byte is not added. + * + * @param tag Tag (field number) of the field + * @param value Pointer to zero-delimited value to be written + */ + void add_bytes(pbf_tag_type tag, const char* value) { + add_bytes(tag, value, std::strlen(value)); + } + + /** + * Add "bytes" field to data using vectored input. All the data in the + * 2nd and further arguments is "concatenated" with only a single copy + * into the final buffer. + * + * This will work with objects of any type supporting the data() and + * size() methods like std::string or protozero::data_view. + * + * Example: + * @code + * std::string data1 = "abc"; + * std::string data2 = "xyz"; + * writer.add_bytes_vectored(1, data1, data2); + * @endcode + * + * @tparam Ts List of types supporting data() and size() methods. + * @param tag Tag (field number) of the field + * @param values List of objects of types Ts with data to be appended. + */ + template <typename... Ts> + void add_bytes_vectored(pbf_tag_type tag, Ts&&... values) { + protozero_assert(m_pos == 0 && "you can't add fields to a parent basic_pbf_writer if there is an existing basic_pbf_writer for a submessage"); + protozero_assert(m_data); + size_t sum_size = 0; + (void)std::initializer_list<size_t>{sum_size += values.size()...}; + protozero_assert(sum_size <= std::numeric_limits<pbf_length_type>::max()); + add_length_varint(tag, pbf_length_type(sum_size)); + buffer_customization<TBuffer>::reserve_additional(m_data, sum_size); + (void)std::initializer_list<int>{(buffer_customization<TBuffer>::append(m_data, values.data(), values.size()), 0)...}; + } + + /** + * Add "string" field to data. + * + * @param tag Tag (field number) of the field + * @param value Pointer to value to be written + * @param size Number of bytes to be written + */ + void add_string(pbf_tag_type tag, const char* value, std::size_t size) { + add_bytes(tag, value, size); + } + + /** + * Add "string" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_string(pbf_tag_type tag, const data_view& value) { + add_bytes(tag, value.data(), value.size()); + } + + /** + * Add "string" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_string(pbf_tag_type tag, const std::string& value) { + add_bytes(tag, value.data(), value.size()); + } + + /** + * Add "string" field to data. Bytes from the value are written until + * a null byte is encountered. The null byte is not added. + * + * @param tag Tag (field number) of the field + * @param value Pointer to value to be written + */ + void add_string(pbf_tag_type tag, const char* value) { + add_bytes(tag, value, std::strlen(value)); + } + + /** + * Add "message" field to data. + * + * @param tag Tag (field number) of the field + * @param value Pointer to message to be written + * @param size Length of the message + */ + void add_message(pbf_tag_type tag, const char* value, std::size_t size) { + add_bytes(tag, value, size); + } + + /** + * Add "message" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written. The value must be a complete message. + */ + void add_message(pbf_tag_type tag, const data_view& value) { + add_bytes(tag, value.data(), value.size()); + } + + /** + * Add "message" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written. The value must be a complete message. + */ + void add_message(pbf_tag_type tag, const std::string& value) { + add_bytes(tag, value.data(), value.size()); + } + + ///@} + + ///@{ + /** + * @name Repeated packed field writer functions + */ + + /** + * Add "repeated packed bool" field to data. + * + * @tparam InputIterator A type satisfying the InputIterator concept. + * Dereferencing the iterator must yield a type assignable to bool. + * @param tag Tag (field number) of the field + * @param first Iterator pointing to the beginning of the data + * @param last Iterator pointing one past the end of data + */ + template <typename InputIterator> + void add_packed_bool(pbf_tag_type tag, InputIterator first, InputIterator last) { + add_packed_varint(tag, first, last); + } + + /** + * Add "repeated packed enum" field to data. + * + * @tparam InputIterator A type satisfying the InputIterator concept. + * Dereferencing the iterator must yield a type assignable to int32_t. + * @param tag Tag (field number) of the field + * @param first Iterator pointing to the beginning of the data + * @param last Iterator pointing one past the end of data + */ + template <typename InputIterator> + void add_packed_enum(pbf_tag_type tag, InputIterator first, InputIterator last) { + add_packed_varint(tag, first, last); + } + + /** + * Add "repeated packed int32" field to data. + * + * @tparam InputIterator A type satisfying the InputIterator concept. + * Dereferencing the iterator must yield a type assignable to int32_t. + * @param tag Tag (field number) of the field + * @param first Iterator pointing to the beginning of the data + * @param last Iterator pointing one past the end of data + */ + template <typename InputIterator> + void add_packed_int32(pbf_tag_type tag, InputIterator first, InputIterator last) { + add_packed_varint(tag, first, last); + } + + /** + * Add "repeated packed sint32" field to data. + * + * @tparam InputIterator A type satisfying the InputIterator concept. + * Dereferencing the iterator must yield a type assignable to int32_t. + * @param tag Tag (field number) of the field + * @param first Iterator pointing to the beginning of the data + * @param last Iterator pointing one past the end of data + */ + template <typename InputIterator> + void add_packed_sint32(pbf_tag_type tag, InputIterator first, InputIterator last) { + add_packed_svarint(tag, first, last); + } + + /** + * Add "repeated packed uint32" field to data. + * + * @tparam InputIterator A type satisfying the InputIterator concept. + * Dereferencing the iterator must yield a type assignable to uint32_t. + * @param tag Tag (field number) of the field + * @param first Iterator pointing to the beginning of the data + * @param last Iterator pointing one past the end of data + */ + template <typename InputIterator> + void add_packed_uint32(pbf_tag_type tag, InputIterator first, InputIterator last) { + add_packed_varint(tag, first, last); + } + + /** + * Add "repeated packed int64" field to data. + * + * @tparam InputIterator A type satisfying the InputIterator concept. + * Dereferencing the iterator must yield a type assignable to int64_t. + * @param tag Tag (field number) of the field + * @param first Iterator pointing to the beginning of the data + * @param last Iterator pointing one past the end of data + */ + template <typename InputIterator> + void add_packed_int64(pbf_tag_type tag, InputIterator first, InputIterator last) { + add_packed_varint(tag, first, last); + } + + /** + * Add "repeated packed sint64" field to data. + * + * @tparam InputIterator A type satisfying the InputIterator concept. + * Dereferencing the iterator must yield a type assignable to int64_t. + * @param tag Tag (field number) of the field + * @param first Iterator pointing to the beginning of the data + * @param last Iterator pointing one past the end of data + */ + template <typename InputIterator> + void add_packed_sint64(pbf_tag_type tag, InputIterator first, InputIterator last) { + add_packed_svarint(tag, first, last); + } + + /** + * Add "repeated packed uint64" field to data. + * + * @tparam InputIterator A type satisfying the InputIterator concept. + * Dereferencing the iterator must yield a type assignable to uint64_t. + * @param tag Tag (field number) of the field + * @param first Iterator pointing to the beginning of the data + * @param last Iterator pointing one past the end of data + */ + template <typename InputIterator> + void add_packed_uint64(pbf_tag_type tag, InputIterator first, InputIterator last) { + add_packed_varint(tag, first, last); + } + + /** + * Add a "repeated packed" fixed-size field to data. The following + * fixed-size fields are available: + * + * uint32_t -> repeated packed fixed32 + * int32_t -> repeated packed sfixed32 + * uint64_t -> repeated packed fixed64 + * int64_t -> repeated packed sfixed64 + * double -> repeated packed double + * float -> repeated packed float + * + * @tparam ValueType One of the following types: (u)int32/64_t, double, float. + * @tparam InputIterator A type satisfying the InputIterator concept. + * @param tag Tag (field number) of the field + * @param first Iterator pointing to the beginning of the data + * @param last Iterator pointing one past the end of data + */ + template <typename ValueType, typename InputIterator> + void add_packed_fixed(pbf_tag_type tag, InputIterator first, InputIterator last) { + static_assert(std::is_same<ValueType, uint32_t>::value || + std::is_same<ValueType, int32_t>::value || + std::is_same<ValueType, int64_t>::value || + std::is_same<ValueType, uint64_t>::value || + std::is_same<ValueType, double>::value || + std::is_same<ValueType, float>::value, "Only some types are allowed"); + add_packed_fixed<ValueType, InputIterator>(tag, first, last, + typename std::iterator_traits<InputIterator>::iterator_category{}); + } + + /** + * Add "repeated packed fixed32" field to data. + * + * @tparam InputIterator A type satisfying the InputIterator concept. + * Dereferencing the iterator must yield a type assignable to uint32_t. + * @param tag Tag (field number) of the field + * @param first Iterator pointing to the beginning of the data + * @param last Iterator pointing one past the end of data + */ + template <typename InputIterator> + void add_packed_fixed32(pbf_tag_type tag, InputIterator first, InputIterator last) { + add_packed_fixed<uint32_t, InputIterator>(tag, first, last, + typename std::iterator_traits<InputIterator>::iterator_category{}); + } + + /** + * Add "repeated packed sfixed32" field to data. + * + * @tparam InputIterator A type satisfying the InputIterator concept. + * Dereferencing the iterator must yield a type assignable to int32_t. + * @param tag Tag (field number) of the field + * @param first Iterator pointing to the beginning of the data + * @param last Iterator pointing one past the end of data + */ + template <typename InputIterator> + void add_packed_sfixed32(pbf_tag_type tag, InputIterator first, InputIterator last) { + add_packed_fixed<int32_t, InputIterator>(tag, first, last, + typename std::iterator_traits<InputIterator>::iterator_category{}); + } + + /** + * Add "repeated packed fixed64" field to data. + * + * @tparam InputIterator A type satisfying the InputIterator concept. + * Dereferencing the iterator must yield a type assignable to uint64_t. + * @param tag Tag (field number) of the field + * @param first Iterator pointing to the beginning of the data + * @param last Iterator pointing one past the end of data + */ + template <typename InputIterator> + void add_packed_fixed64(pbf_tag_type tag, InputIterator first, InputIterator last) { + add_packed_fixed<uint64_t, InputIterator>(tag, first, last, + typename std::iterator_traits<InputIterator>::iterator_category{}); + } + + /** + * Add "repeated packed sfixed64" field to data. + * + * @tparam InputIterator A type satisfying the InputIterator concept. + * Dereferencing the iterator must yield a type assignable to int64_t. + * @param tag Tag (field number) of the field + * @param first Iterator pointing to the beginning of the data + * @param last Iterator pointing one past the end of data + */ + template <typename InputIterator> + void add_packed_sfixed64(pbf_tag_type tag, InputIterator first, InputIterator last) { + add_packed_fixed<int64_t, InputIterator>(tag, first, last, + typename std::iterator_traits<InputIterator>::iterator_category{}); + } + + /** + * Add "repeated packed float" field to data. + * + * @tparam InputIterator A type satisfying the InputIterator concept. + * Dereferencing the iterator must yield a type assignable to float. + * @param tag Tag (field number) of the field + * @param first Iterator pointing to the beginning of the data + * @param last Iterator pointing one past the end of data + */ + template <typename InputIterator> + void add_packed_float(pbf_tag_type tag, InputIterator first, InputIterator last) { + add_packed_fixed<float, InputIterator>(tag, first, last, + typename std::iterator_traits<InputIterator>::iterator_category{}); + } + + /** + * Add "repeated packed double" field to data. + * + * @tparam InputIterator A type satisfying the InputIterator concept. + * Dereferencing the iterator must yield a type assignable to double. + * @param tag Tag (field number) of the field + * @param first Iterator pointing to the beginning of the data + * @param last Iterator pointing one past the end of data + */ + template <typename InputIterator> + void add_packed_double(pbf_tag_type tag, InputIterator first, InputIterator last) { + add_packed_fixed<double, InputIterator>(tag, first, last, + typename std::iterator_traits<InputIterator>::iterator_category{}); + } + + ///@} + + template <typename B, typename T> friend class detail::packed_field_varint; + template <typename B, typename T> friend class detail::packed_field_svarint; + template <typename B, typename T> friend class detail::packed_field_fixed; + +}; // class basic_pbf_writer + +/** + * Swap two basic_pbf_writer objects. + * + * @param lhs First object. + * @param rhs Second object. + */ +template <typename TBuffer> +inline void swap(basic_pbf_writer<TBuffer>& lhs, basic_pbf_writer<TBuffer>& rhs) noexcept { + lhs.swap(rhs); +} + +namespace detail { + + template <typename TBuffer> + class packed_field { + + basic_pbf_writer<TBuffer> m_writer{}; + + public: + + packed_field(const packed_field&) = delete; + packed_field& operator=(const packed_field&) = delete; + + packed_field(packed_field&&) noexcept = default; + packed_field& operator=(packed_field&&) noexcept = default; + + packed_field() = default; + + packed_field(basic_pbf_writer<TBuffer>& parent_writer, pbf_tag_type tag) : + m_writer{parent_writer, tag} { + } + + packed_field(basic_pbf_writer<TBuffer>& parent_writer, pbf_tag_type tag, std::size_t size) : + m_writer{parent_writer, tag, size} { + } + + ~packed_field() noexcept = default; + + bool valid() const noexcept { + return m_writer.valid(); + } + + void commit() { + m_writer.commit(); + } + + void rollback() { + m_writer.rollback(); + } + + basic_pbf_writer<TBuffer>& writer() noexcept { + return m_writer; + } + + }; // class packed_field + + template <typename TBuffer, typename T> + class packed_field_fixed : public packed_field<TBuffer> { + + public: + + packed_field_fixed() : + packed_field<TBuffer>{} { + } + + template <typename P> + packed_field_fixed(basic_pbf_writer<TBuffer>& parent_writer, P tag) : + packed_field<TBuffer>{parent_writer, static_cast<pbf_tag_type>(tag)} { + } + + template <typename P> + packed_field_fixed(basic_pbf_writer<TBuffer>& parent_writer, P tag, std::size_t size) : + packed_field<TBuffer>{parent_writer, static_cast<pbf_tag_type>(tag), size * sizeof(T)} { + } + + void add_element(T value) { + this->writer().template add_fixed<T>(value); + } + + }; // class packed_field_fixed + + template <typename TBuffer, typename T> + class packed_field_varint : public packed_field<TBuffer> { + + public: + + packed_field_varint() : + packed_field<TBuffer>{} { + } + + template <typename P> + packed_field_varint(basic_pbf_writer<TBuffer>& parent_writer, P tag) : + packed_field<TBuffer>{parent_writer, static_cast<pbf_tag_type>(tag)} { + } + + void add_element(T value) { + this->writer().add_varint(uint64_t(value)); + } + + }; // class packed_field_varint + + template <typename TBuffer, typename T> + class packed_field_svarint : public packed_field<TBuffer> { + + public: + + packed_field_svarint() : + packed_field<TBuffer>{} { + } + + template <typename P> + packed_field_svarint(basic_pbf_writer<TBuffer>& parent_writer, P tag) : + packed_field<TBuffer>{parent_writer, static_cast<pbf_tag_type>(tag)} { + } + + void add_element(T value) { + this->writer().add_varint(encode_zigzag64(value)); + } + + }; // class packed_field_svarint + +} // end namespace detail + +} // end namespace protozero + +#endif // PROTOZERO_BASIC_PBF_WRITER_HPP diff --git a/ext/protozero/include/protozero/buffer_fixed.hpp b/ext/protozero/include/protozero/buffer_fixed.hpp new file mode 100644 index 0000000..b2e6d1d --- /dev/null +++ b/ext/protozero/include/protozero/buffer_fixed.hpp @@ -0,0 +1,222 @@ +#ifndef PROTOZERO_BUFFER_FIXED_HPP +#define PROTOZERO_BUFFER_FIXED_HPP + +/***************************************************************************** + +protozero - Minimalistic protocol buffer decoder and encoder in C++. + +This file is from https://github.com/mapbox/protozero where you can find more +documentation. + +*****************************************************************************/ + +/** + * @file buffer_fixed.hpp + * + * @brief Contains the fixed_size_buffer_adaptor class. + */ + +#include "buffer_tmpl.hpp" +#include "config.hpp" + +#include <algorithm> +#include <cstddef> +#include <iterator> +#include <stdexcept> + +namespace protozero { + +/** + * This class can be used instead of std::string if you want to create a + * vector tile in a fixed-size buffer. Any operation that needs more space + * than is available will fail with a std::length_error exception. + */ +class fixed_size_buffer_adaptor { + + char* m_data; + std::size_t m_capacity; + std::size_t m_size = 0; + +public: + + /// @cond usual container typedefs not documented + + using size_type = std::size_t; + + using value_type = char; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; + + using iterator = pointer; + using const_iterator = const_pointer; + + /// @endcond + + /** + * Constructor. + * + * @param data Pointer to some memory allocated for the buffer. + * @param capacity Number of bytes available. + */ + fixed_size_buffer_adaptor(char* data, std::size_t capacity) noexcept : + m_data(data), + m_capacity(capacity) { + } + + /** + * Constructor. + * + * @param container Some container class supporting the member functions + * data() and size(). + */ + template <typename T> + explicit fixed_size_buffer_adaptor(T& container) : + m_data(container.data()), + m_capacity(container.size()) { + } + + /// Returns a pointer to the data in the buffer. + const char* data() const noexcept { + return m_data; + } + + /// Returns a pointer to the data in the buffer. + char* data() noexcept { + return m_data; + } + + /// The capacity this buffer was created with. + std::size_t capacity() const noexcept { + return m_capacity; + } + + /// The number of bytes used in the buffer. Always <= capacity(). + std::size_t size() const noexcept { + return m_size; + } + + /// Return iterator to beginning of data. + char* begin() noexcept { + return m_data; + } + + /// Return iterator to beginning of data. + const char* begin() const noexcept { + return m_data; + } + + /// Return iterator to beginning of data. + const char* cbegin() const noexcept { + return m_data; + } + + /// Return iterator to end of data. + char* end() noexcept { + return m_data + m_size; + } + + /// Return iterator to end of data. + const char* end() const noexcept { + return m_data + m_size; + } + + /// Return iterator to end of data. + const char* cend() const noexcept { + return m_data + m_size; + } + +/// @cond INTERNAL + + // Do not rely on anything beyond this point + + void append(const char* data, std::size_t count) { + if (m_size + count > m_capacity) { + throw std::length_error{"fixed size data store exhausted"}; + } + std::copy_n(data, count, m_data + m_size); + m_size += count; + } + + void append_zeros(std::size_t count) { + if (m_size + count > m_capacity) { + throw std::length_error{"fixed size data store exhausted"}; + } + std::fill_n(m_data + m_size, count, '\0'); + m_size += count; + } + + void resize(std::size_t size) { + protozero_assert(size < m_size); + if (size > m_capacity) { + throw std::length_error{"fixed size data store exhausted"}; + } + m_size = size; + } + + void erase_range(std::size_t from, std::size_t to) { + protozero_assert(from <= m_size); + protozero_assert(to <= m_size); + protozero_assert(from < to); + std::copy(m_data + to, m_data + m_size, m_data + from); + m_size -= (to - from); + } + + char* at_pos(std::size_t pos) { + protozero_assert(pos <= m_size); + return m_data + pos; + } + + void push_back(char ch) { + if (m_size >= m_capacity) { + throw std::length_error{"fixed size data store exhausted"}; + } + m_data[m_size++] = ch; + } +/// @endcond + +}; // class fixed_size_buffer_adaptor + +/// @cond INTERNAL +template <> +struct buffer_customization<fixed_size_buffer_adaptor> { + + static std::size_t size(const fixed_size_buffer_adaptor* buffer) noexcept { + return buffer->size(); + } + + static void append(fixed_size_buffer_adaptor* buffer, const char* data, std::size_t count) { + buffer->append(data, count); + } + + static void append_zeros(fixed_size_buffer_adaptor* buffer, std::size_t count) { + buffer->append_zeros(count); + } + + static void resize(fixed_size_buffer_adaptor* buffer, std::size_t size) { + buffer->resize(size); + } + + static void reserve_additional(fixed_size_buffer_adaptor* /*buffer*/, std::size_t /*size*/) { + /* nothing to be done for fixed-size buffers */ + } + + static void erase_range(fixed_size_buffer_adaptor* buffer, std::size_t from, std::size_t to) { + buffer->erase_range(from, to); + } + + static char* at_pos(fixed_size_buffer_adaptor* buffer, std::size_t pos) { + return buffer->at_pos(pos); + } + + static void push_back(fixed_size_buffer_adaptor* buffer, char ch) { + buffer->push_back(ch); + } + +}; +/// @endcond + +} // namespace protozero + +#endif // PROTOZERO_BUFFER_FIXED_HPP diff --git a/ext/protozero/include/protozero/buffer_string.hpp b/ext/protozero/include/protozero/buffer_string.hpp new file mode 100644 index 0000000..02e8ad2 --- /dev/null +++ b/ext/protozero/include/protozero/buffer_string.hpp @@ -0,0 +1,78 @@ +#ifndef PROTOZERO_BUFFER_STRING_HPP +#define PROTOZERO_BUFFER_STRING_HPP + +/***************************************************************************** + +protozero - Minimalistic protocol buffer decoder and encoder in C++. + +This file is from https://github.com/mapbox/protozero where you can find more +documentation. + +*****************************************************************************/ + +/** + * @file buffer_string.hpp + * + * @brief Contains the customization points for buffer implementation based + * on std::string + */ + +#include "buffer_tmpl.hpp" +#include "config.hpp" + +#include <cstddef> +#include <iterator> +#include <string> + +namespace protozero { + +// Implementation of buffer customizations points for std::string + +/// @cond INTERNAL +template <> +struct buffer_customization<std::string> { + + static std::size_t size(const std::string* buffer) noexcept { + return buffer->size(); + } + + static void append(std::string* buffer, const char* data, std::size_t count) { + buffer->append(data, count); + } + + static void append_zeros(std::string* buffer, std::size_t count) { + buffer->append(count, '\0'); + } + + static void resize(std::string* buffer, std::size_t size) { + protozero_assert(size < buffer->size()); + buffer->resize(size); + } + + static void reserve_additional(std::string* buffer, std::size_t size) { + buffer->reserve(buffer->size() + size); + } + + static void erase_range(std::string* buffer, std::size_t from, std::size_t to) { + protozero_assert(from <= buffer->size()); + protozero_assert(to <= buffer->size()); + protozero_assert(from <= to); + buffer->erase(std::next(buffer->begin(), static_cast<std::string::iterator::difference_type>(from)), + std::next(buffer->begin(), static_cast<std::string::iterator::difference_type>(to))); + } + + static char* at_pos(std::string* buffer, std::size_t pos) { + protozero_assert(pos <= buffer->size()); + return (&*buffer->begin()) + pos; + } + + static void push_back(std::string* buffer, char ch) { + buffer->push_back(ch); + } + +}; +/// @endcond + +} // namespace protozero + +#endif // PROTOZERO_BUFFER_STRING_HPP diff --git a/ext/protozero/include/protozero/buffer_tmpl.hpp b/ext/protozero/include/protozero/buffer_tmpl.hpp new file mode 100644 index 0000000..ac22399 --- /dev/null +++ b/ext/protozero/include/protozero/buffer_tmpl.hpp @@ -0,0 +1,113 @@ +#ifndef PROTOZERO_BUFFER_TMPL_HPP +#define PROTOZERO_BUFFER_TMPL_HPP + +/***************************************************************************** + +protozero - Minimalistic protocol buffer decoder and encoder in C++. + +This file is from https://github.com/mapbox/protozero where you can find more +documentation. + +*****************************************************************************/ + +/** + * @file buffer_tmpl.hpp + * + * @brief Contains the customization points for buffer implementations. + */ + +#include <cstddef> +#include <iterator> +#include <string> + +namespace protozero { + +// Implementation of buffer customizations points for std::string + +/// @cond INTERNAL +template <typename T> +struct buffer_customization { + + /** + * Get the number of bytes currently used in the buffer. + * + * @param buffer Pointer to the buffer. + * @returns number of bytes used in the buffer. + */ + static std::size_t size(const std::string* buffer); + + /** + * Append count bytes from data to the buffer. + * + * @param buffer Pointer to the buffer. + * @param data Pointer to the data. + * @param count Number of bytes to be added to the buffer. + */ + static void append(std::string* buffer, const char* data, std::size_t count); + + /** + * Append count zero bytes to the buffer. + * + * @param buffer Pointer to the buffer. + * @param count Number of bytes to be added to the buffer. + */ + static void append_zeros(std::string* buffer, std::size_t count); + + /** + * Shrink the buffer to the specified size. The new size will always be + * smaller than the current size. + * + * @param buffer Pointer to the buffer. + * @param size New size of the buffer. + * + * @pre size < current size of buffer + */ + static void resize(std::string* buffer, std::size_t size); + + /** + * Reserve an additional size bytes for use in the buffer. This is used for + * variable-sized buffers to tell the buffer implementation that soon more + * memory will be used. The implementation can ignore this. + * + * @param buffer Pointer to the buffer. + * @param size Number of bytes to reserve. + */ + static void reserve_additional(std::string* buffer, std::size_t size); + + /** + * Delete data from the buffer. This must move back the data after the + * part being deleted and resize the buffer accordingly. + * + * @param buffer Pointer to the buffer. + * @param from Offset into the buffer where we want to erase from. + * @param to Offset into the buffer one past the last byte we want to erase. + * + * @pre from, to <= size of the buffer, from < to + */ + static void erase_range(std::string* buffer, std::size_t from, std::size_t to); + + /** + * Return a pointer to the memory at the specified position in the buffer. + * + * @param buffer Pointer to the buffer. + * @param pos The position in the buffer. + * @returns pointer to the memory in the buffer at the specified position. + * + * @pre pos <= size of the buffer + */ + static char* at_pos(std::string* buffer, std::size_t pos); + + /** + * Add a char to the buffer incrementing the number of chars in the buffer. + * + * @param buffer Pointer to the buffer. + * @param ch The character to add. + */ + static void push_back(std::string* buffer, char ch); + +}; +/// @endcond + +} // namespace protozero + +#endif // PROTOZERO_BUFFER_TMPL_HPP diff --git a/ext/protozero/include/protozero/buffer_vector.hpp b/ext/protozero/include/protozero/buffer_vector.hpp new file mode 100644 index 0000000..c163300 --- /dev/null +++ b/ext/protozero/include/protozero/buffer_vector.hpp @@ -0,0 +1,78 @@ +#ifndef PROTOZERO_BUFFER_VECTOR_HPP +#define PROTOZERO_BUFFER_VECTOR_HPP + +/***************************************************************************** + +protozero - Minimalistic protocol buffer decoder and encoder in C++. + +This file is from https://github.com/mapbox/protozero where you can find more +documentation. + +*****************************************************************************/ + +/** + * @file buffer_vector.hpp + * + * @brief Contains the customization points for buffer implementation based + * on std::vector<char> + */ + +#include "buffer_tmpl.hpp" +#include "config.hpp" + +#include <cstddef> +#include <iterator> +#include <vector> + +namespace protozero { + +// Implementation of buffer customizations points for std::vector<char> + +/// @cond INTERNAL +template <> +struct buffer_customization<std::vector<char>> { + + static std::size_t size(const std::vector<char>* buffer) noexcept { + return buffer->size(); + } + + static void append(std::vector<char>* buffer, const char* data, std::size_t count) { + buffer->insert(buffer->end(), data, data + count); + } + + static void append_zeros(std::vector<char>* buffer, std::size_t count) { + buffer->insert(buffer->end(), count, '\0'); + } + + static void resize(std::vector<char>* buffer, std::size_t size) { + protozero_assert(size < buffer->size()); + buffer->resize(size); + } + + static void reserve_additional(std::vector<char>* buffer, std::size_t size) { + buffer->reserve(buffer->size() + size); + } + + static void erase_range(std::vector<char>* buffer, std::size_t from, std::size_t to) { + protozero_assert(from <= buffer->size()); + protozero_assert(to <= buffer->size()); + protozero_assert(from <= to); + buffer->erase(std::next(buffer->begin(), static_cast<std::string::iterator::difference_type>(from)), + std::next(buffer->begin(), static_cast<std::string::iterator::difference_type>(to))); + } + + static char* at_pos(std::vector<char>* buffer, std::size_t pos) { + protozero_assert(pos <= buffer->size()); + return (&*buffer->begin()) + pos; + } + + static void push_back(std::vector<char>* buffer, char ch) { + buffer->push_back(ch); + } + +}; +/// @endcond + +} // namespace protozero + +#endif // PROTOZERO_BUFFER_VECTOR_HPP diff --git a/ext/protozero/include/protozero/byteswap.hpp b/ext/protozero/include/protozero/byteswap.hpp new file mode 100644 index 0000000..75cae69 --- /dev/null +++ b/ext/protozero/include/protozero/byteswap.hpp @@ -0,0 +1,108 @@ +#ifndef PROTOZERO_BYTESWAP_HPP +#define PROTOZERO_BYTESWAP_HPP + +/***************************************************************************** + +protozero - Minimalistic protocol buffer decoder and encoder in C++. + +This file is from https://github.com/mapbox/protozero where you can find more +documentation. + +*****************************************************************************/ + +/** + * @file byteswap.hpp + * + * @brief Contains functions to swap bytes in values (for different endianness). + */ + +#include "config.hpp" + +#include <cstdint> +#include <cstring> + +namespace protozero { +namespace detail { + +inline uint32_t byteswap_impl(uint32_t value) noexcept { +#ifdef PROTOZERO_USE_BUILTIN_BSWAP + return __builtin_bswap32(value); +#else + return ((value & 0xff000000U) >> 24U) | + ((value & 0x00ff0000U) >> 8U) | + ((value & 0x0000ff00U) << 8U) | + ((value & 0x000000ffU) << 24U); +#endif +} + +inline uint64_t byteswap_impl(uint64_t value) noexcept { +#ifdef PROTOZERO_USE_BUILTIN_BSWAP + return __builtin_bswap64(value); +#else + return ((value & 0xff00000000000000ULL) >> 56U) | + ((value & 0x00ff000000000000ULL) >> 40U) | + ((value & 0x0000ff0000000000ULL) >> 24U) | + ((value & 0x000000ff00000000ULL) >> 8U) | + ((value & 0x00000000ff000000ULL) << 8U) | + ((value & 0x0000000000ff0000ULL) << 24U) | + ((value & 0x000000000000ff00ULL) << 40U) | + ((value & 0x00000000000000ffULL) << 56U); +#endif +} + +} // end namespace detail + +/// byteswap the data pointed to by ptr in-place. +inline void byteswap_inplace(uint32_t* ptr) noexcept { + *ptr = detail::byteswap_impl(*ptr); +} + +/// byteswap the data pointed to by ptr in-place. +inline void byteswap_inplace(uint64_t* ptr) noexcept { + *ptr = detail::byteswap_impl(*ptr); +} + +/// byteswap the data pointed to by ptr in-place. +inline void byteswap_inplace(int32_t* ptr) noexcept { + auto* bptr = reinterpret_cast<uint32_t*>(ptr); + *bptr = detail::byteswap_impl(*bptr); +} + +/// byteswap the data pointed to by ptr in-place. +inline void byteswap_inplace(int64_t* ptr) noexcept { + auto* bptr = reinterpret_cast<uint64_t*>(ptr); + *bptr = detail::byteswap_impl(*bptr); +} + +/// byteswap the data pointed to by ptr in-place. +inline void byteswap_inplace(float* ptr) noexcept { + static_assert(sizeof(float) == 4, "Expecting four byte float"); + + uint32_t tmp = 0; + std::memcpy(&tmp, ptr, 4); + tmp = detail::byteswap_impl(tmp); // uint32 overload + std::memcpy(ptr, &tmp, 4); +} + +/// byteswap the data pointed to by ptr in-place. +inline void byteswap_inplace(double* ptr) noexcept { + static_assert(sizeof(double) == 8, "Expecting eight byte double"); + + uint64_t tmp = 0; + std::memcpy(&tmp, ptr, 8); + tmp = detail::byteswap_impl(tmp); // uint64 overload + std::memcpy(ptr, &tmp, 8); +} + +namespace detail { + + // Added for backwards compatibility with any code that might use this + // function (even if it shouldn't have). Will be removed in a later + // version of protozero. + using ::protozero::byteswap_inplace; + +} // end namespace detail + +} // end namespace protozero + +#endif // PROTOZERO_BYTESWAP_HPP diff --git a/ext/protozero/include/protozero/config.hpp b/ext/protozero/include/protozero/config.hpp new file mode 100644 index 0000000..6fc7749 --- /dev/null +++ b/ext/protozero/include/protozero/config.hpp @@ -0,0 +1,48 @@ +#ifndef PROTOZERO_CONFIG_HPP +#define PROTOZERO_CONFIG_HPP + +/***************************************************************************** + +protozero - Minimalistic protocol buffer decoder and encoder in C++. + +This file is from https://github.com/mapbox/protozero where you can find more +documentation. + +*****************************************************************************/ + +#include <cassert> + +/** + * @file config.hpp + * + * @brief Contains macro checks for different configurations. + */ + +#define PROTOZERO_LITTLE_ENDIAN 1234 +#define PROTOZERO_BIG_ENDIAN 4321 + +// Find out which byte order the machine has. +#if defined(__BYTE_ORDER) +# if (__BYTE_ORDER == __LITTLE_ENDIAN) +# define PROTOZERO_BYTE_ORDER PROTOZERO_LITTLE_ENDIAN +# endif +# if (__BYTE_ORDER == __BIG_ENDIAN) +# define PROTOZERO_BYTE_ORDER PROTOZERO_BIG_ENDIAN +# endif +#else +// This probably isn't a very good default, but might do until we figure +// out something better. +# define PROTOZERO_BYTE_ORDER PROTOZERO_LITTLE_ENDIAN +#endif + +// Check whether __builtin_bswap is available +#if defined(__GNUC__) || defined(__clang__) +# define PROTOZERO_USE_BUILTIN_BSWAP +#endif + +// Wrapper for assert() used for testing +#ifndef protozero_assert +# define protozero_assert(x) assert(x) +#endif + +#endif // PROTOZERO_CONFIG_HPP diff --git a/ext/protozero/include/protozero/data_view.hpp b/ext/protozero/include/protozero/data_view.hpp new file mode 100644 index 0000000..3ec87af --- /dev/null +++ b/ext/protozero/include/protozero/data_view.hpp @@ -0,0 +1,236 @@ +#ifndef PROTOZERO_DATA_VIEW_HPP +#define PROTOZERO_DATA_VIEW_HPP + +/***************************************************************************** + +protozero - Minimalistic protocol buffer decoder and encoder in C++. + +This file is from https://github.com/mapbox/protozero where you can find more +documentation. + +*****************************************************************************/ + +/** + * @file data_view.hpp + * + * @brief Contains the implementation of the data_view class. + */ + +#include "config.hpp" + +#include <algorithm> +#include <cstddef> +#include <cstring> +#include <string> +#include <utility> + +namespace protozero { + +#ifdef PROTOZERO_USE_VIEW +using data_view = PROTOZERO_USE_VIEW; +#else + +/** + * Holds a pointer to some data and a length. + * + * This class is supposed to be compatible with the std::string_view + * that will be available in C++17. + */ +class data_view { + + const char* m_data = nullptr; + std::size_t m_size = 0; + +public: + + /** + * Default constructor. Construct an empty data_view. + */ + constexpr data_view() noexcept = default; + + /** + * Create data_view from pointer and size. + * + * @param ptr Pointer to the data. + * @param length Length of the data. + */ + constexpr data_view(const char* ptr, std::size_t length) noexcept + : m_data{ptr}, + m_size{length} { + } + + /** + * Create data_view from string. + * + * @param str String with the data. + */ + data_view(const std::string& str) noexcept // NOLINT(google-explicit-constructor, hicpp-explicit-conversions) + : m_data{str.data()}, + m_size{str.size()} { + } + + /** + * Create data_view from zero-terminated string. + * + * @param ptr Pointer to the data. + */ + data_view(const char* ptr) noexcept // NOLINT(google-explicit-constructor, hicpp-explicit-conversions) + : m_data{ptr}, + m_size{std::strlen(ptr)} { + } + + /** + * Swap the contents of this object with the other. + * + * @param other Other object to swap data with. + */ + void swap(data_view& other) noexcept { + using std::swap; + swap(m_data, other.m_data); + swap(m_size, other.m_size); + } + + /// Return pointer to data. + constexpr const char* data() const noexcept { + return m_data; + } + + /// Return length of data in bytes. + constexpr std::size_t size() const noexcept { + return m_size; + } + + /// Returns true if size is 0. + constexpr bool empty() const noexcept { + return m_size == 0; + } + +#ifndef PROTOZERO_STRICT_API + /** + * Convert data view to string. + * + * @pre Must not be default constructed data_view. + * + * @deprecated to_string() is not available in C++17 string_view so it + * should not be used to make conversion to that class easier + * in the future. + */ + std::string to_string() const { + protozero_assert(m_data); + return {m_data, m_size}; + } +#endif + + /** + * Convert data view to string. + * + * @pre Must not be default constructed data_view. + */ + explicit operator std::string() const { + protozero_assert(m_data); + return {m_data, m_size}; + } + + /** + * Compares the contents of this object with the given other object. + * + * @returns 0 if they are the same, <0 if this object is smaller than + * the other or >0 if it is larger. If both objects have the + * same size returns <0 if this object is lexicographically + * before the other, >0 otherwise. + * + * @pre Must not be default constructed data_view. + */ + int compare(data_view other) const noexcept { + assert(m_data && other.m_data); + const int cmp = std::memcmp(data(), other.data(), + std::min(size(), other.size())); + if (cmp == 0) { + if (size() == other.size()) { + return 0; + } + return size() < other.size() ? -1 : 1; + } + return cmp; + } + +}; // class data_view + +/** + * Swap two data_view objects. + * + * @param lhs First object. + * @param rhs Second object. + */ +inline void swap(data_view& lhs, data_view& rhs) noexcept { + lhs.swap(rhs); +} + +/** + * Two data_view instances are equal if they have the same size and the + * same content. + * + * @param lhs First object. + * @param rhs Second object. + */ +inline constexpr bool operator==(const data_view lhs, const data_view rhs) noexcept { + return lhs.size() == rhs.size() && + std::equal(lhs.data(), lhs.data() + lhs.size(), rhs.data()); +} + +/** + * Two data_view instances are not equal if they have different sizes or the + * content differs. + * + * @param lhs First object. + * @param rhs Second object. + */ +inline constexpr bool operator!=(const data_view lhs, const data_view rhs) noexcept { + return !(lhs == rhs); +} + +/** + * Returns true if lhs.compare(rhs) < 0. + * + * @param lhs First object. + * @param rhs Second object. + */ +inline bool operator<(const data_view lhs, const data_view rhs) noexcept { + return lhs.compare(rhs) < 0; +} + +/** + * Returns true if lhs.compare(rhs) <= 0. + * + * @param lhs First object. + * @param rhs Second object. + */ +inline bool operator<=(const data_view lhs, const data_view rhs) noexcept { + return lhs.compare(rhs) <= 0; +} + +/** + * Returns true if lhs.compare(rhs) > 0. + * + * @param lhs First object. + * @param rhs Second object. + */ +inline bool operator>(const data_view lhs, const data_view rhs) noexcept { + return lhs.compare(rhs) > 0; +} + +/** + * Returns true if lhs.compare(rhs) >= 0. + * + * @param lhs First object. + * @param rhs Second object. + */ +inline bool operator>=(const data_view lhs, const data_view rhs) noexcept { + return lhs.compare(rhs) >= 0; +} + +#endif + +} // end namespace protozero + +#endif // PROTOZERO_DATA_VIEW_HPP diff --git a/ext/protozero/include/protozero/exception.hpp b/ext/protozero/include/protozero/exception.hpp new file mode 100644 index 0000000..a3cd0f1 --- /dev/null +++ b/ext/protozero/include/protozero/exception.hpp @@ -0,0 +1,101 @@ +#ifndef PROTOZERO_EXCEPTION_HPP +#define PROTOZERO_EXCEPTION_HPP + +/***************************************************************************** + +protozero - Minimalistic protocol buffer decoder and encoder in C++. + +This file is from https://github.com/mapbox/protozero where you can find more +documentation. + +*****************************************************************************/ + +/** + * @file exception.hpp + * + * @brief Contains the exceptions used in the protozero library. + */ + +#include <exception> + +/** + * @brief All parts of the protozero header-only library are in this namespace. + */ +namespace protozero { + +/** + * All exceptions explicitly thrown by the functions of the protozero library + * derive from this exception. + */ +struct exception : std::exception { + /// Returns the explanatory string. + const char* what() const noexcept override { + return "pbf exception"; + } +}; + +/** + * This exception is thrown when parsing a varint thats larger than allowed. + * This should never happen unless the data is corrupted. + */ +struct varint_too_long_exception : exception { + /// Returns the explanatory string. + const char* what() const noexcept override { + return "varint too long exception"; + } +}; + +/** + * This exception is thrown when the wire type of a pdf field is unknown. + * This should never happen unless the data is corrupted. + */ +struct unknown_pbf_wire_type_exception : exception { + /// Returns the explanatory string. + const char* what() const noexcept override { + return "unknown pbf field type exception"; + } +}; + +/** + * This exception is thrown when we are trying to read a field and there + * are not enough bytes left in the buffer to read it. Almost all functions + * of the pbf_reader class can throw this exception. + * + * This should never happen unless the data is corrupted or you have + * initialized the pbf_reader object with incomplete data. + */ +struct end_of_buffer_exception : exception { + /// Returns the explanatory string. + const char* what() const noexcept override { + return "end of buffer exception"; + } +}; + +/** + * This exception is thrown when a tag has an invalid value. Tags must be + * unsigned integers between 1 and 2^29-1. Tags between 19000 and 19999 are + * not allowed. See + * https://developers.google.com/protocol-buffers/docs/proto#assigning-tags + */ +struct invalid_tag_exception : exception { + /// Returns the explanatory string. + const char* what() const noexcept override { + return "invalid tag exception"; + } +}; + +/** + * This exception is thrown when a length field of a packed repeated field is + * invalid. For fixed size types the length must be a multiple of the size of + * the type. + */ +struct invalid_length_exception : exception { + /// Returns the explanatory string. + const char* what() const noexcept override { + return "invalid length exception"; + } +}; + +} // end namespace protozero + +#endif // PROTOZERO_EXCEPTION_HPP diff --git a/ext/protozero/include/protozero/iterators.hpp b/ext/protozero/include/protozero/iterators.hpp new file mode 100644 index 0000000..ee8ef8e --- /dev/null +++ b/ext/protozero/include/protozero/iterators.hpp @@ -0,0 +1,481 @@ +#ifndef PROTOZERO_ITERATORS_HPP +#define PROTOZERO_ITERATORS_HPP + +/***************************************************************************** + +protozero - Minimalistic protocol buffer decoder and encoder in C++. + +This file is from https://github.com/mapbox/protozero where you can find more +documentation. + +*****************************************************************************/ + +/** + * @file iterators.hpp + * + * @brief Contains the iterators for access to packed repeated fields. + */ + +#include "config.hpp" +#include "varint.hpp" + +#if PROTOZERO_BYTE_ORDER != PROTOZERO_LITTLE_ENDIAN +# include <protozero/byteswap.hpp> +#endif + +#include <algorithm> +#include <cstring> +#include <iterator> +#include <utility> + +namespace protozero { + +/** + * A range of iterators based on std::pair. Created from beginning and + * end iterators. Used as a return type from some pbf_reader methods + * that is easy to use with range-based for loops. + */ +template <typename T, typename P = std::pair<T, T>> +class iterator_range : +#ifdef PROTOZERO_STRICT_API + protected +#else + public +#endif + P { + +public: + + /// The type of the iterators in this range. + using iterator = T; + + /// The value type of the underlying iterator. + using value_type = typename std::iterator_traits<T>::value_type; + + /** + * Default constructor. Create empty iterator_range. + */ + constexpr iterator_range() : + P{iterator{}, iterator{}} { + } + + /** + * Create iterator range from two iterators. + * + * @param first_iterator Iterator to beginning of range. + * @param last_iterator Iterator to end of range. + */ + constexpr iterator_range(iterator&& first_iterator, iterator&& last_iterator) : + P{std::forward<iterator>(first_iterator), + std::forward<iterator>(last_iterator)} { + } + + /// Return iterator to beginning of range. + constexpr iterator begin() const noexcept { + return this->first; + } + + /// Return iterator to end of range. + constexpr iterator end() const noexcept { + return this->second; + } + + /// Return iterator to beginning of range. + constexpr iterator cbegin() const noexcept { + return this->first; + } + + /// Return iterator to end of range. + constexpr iterator cend() const noexcept { + return this->second; + } + + /** + * Return true if this range is empty. + * + * Complexity: Constant. + */ + constexpr bool empty() const noexcept { + return begin() == end(); + } + + /** + * Get the size of the range, ie the number of elements it contains. + * + * Complexity: Constant or linear depending on the underlaying iterator. + */ + std::size_t size() const noexcept { + return static_cast<size_t>(std::distance(begin(), end())); + } + + /** + * Get element at the beginning of the range. + * + * @pre Range must not be empty. + */ + value_type front() const { + protozero_assert(!empty()); + return *(this->first); + } + + /** + * Advance beginning of range by one. + * + * @pre Range must not be empty. + */ + void drop_front() { + protozero_assert(!empty()); + ++this->first; + } + + /** + * Swap the contents of this range with the other. + * + * @param other Other range to swap data with. + */ + void swap(iterator_range& other) noexcept { + using std::swap; + swap(this->first, other.first); + swap(this->second, other.second); + } + +}; // struct iterator_range + +/** + * Swap two iterator_ranges. + * + * @param lhs First range. + * @param rhs Second range. + */ +template <typename T> +inline void swap(iterator_range<T>& lhs, iterator_range<T>& rhs) noexcept { + lhs.swap(rhs); +} + +/** + * A forward iterator used for accessing packed repeated fields of fixed + * length (fixed32, sfixed32, float, double). + */ +template <typename T> +class const_fixed_iterator { + + /// Pointer to current iterator position + const char* m_data = nullptr; + +public: + + /// @cond usual iterator functions not documented + + using iterator_category = std::random_access_iterator_tag; + using value_type = T; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + const_fixed_iterator() noexcept = default; + + explicit const_fixed_iterator(const char* data) noexcept : + m_data{data} { + } + + const_fixed_iterator(const const_fixed_iterator&) noexcept = default; + const_fixed_iterator(const_fixed_iterator&&) noexcept = default; + + const_fixed_iterator& operator=(const const_fixed_iterator&) noexcept = default; + const_fixed_iterator& operator=(const_fixed_iterator&&) noexcept = default; + + ~const_fixed_iterator() noexcept = default; + + value_type operator*() const noexcept { + value_type result; + std::memcpy(&result, m_data, sizeof(value_type)); +#if PROTOZERO_BYTE_ORDER != PROTOZERO_LITTLE_ENDIAN + byteswap_inplace(&result); +#endif + return result; + } + + const_fixed_iterator& operator++() noexcept { + m_data += sizeof(value_type); + return *this; + } + + const_fixed_iterator operator++(int) noexcept { + const const_fixed_iterator tmp{*this}; + ++(*this); + return tmp; + } + + const_fixed_iterator& operator--() noexcept { + m_data -= sizeof(value_type); + return *this; + } + + const_fixed_iterator operator--(int) noexcept { + const const_fixed_iterator tmp{*this}; + --(*this); + return tmp; + } + + friend bool operator==(const_fixed_iterator lhs, const_fixed_iterator rhs) noexcept { + return lhs.m_data == rhs.m_data; + } + + friend bool operator!=(const_fixed_iterator lhs, const_fixed_iterator rhs) noexcept { + return !(lhs == rhs); + } + + friend bool operator<(const_fixed_iterator lhs, const_fixed_iterator rhs) noexcept { + return lhs.m_data < rhs.m_data; + } + + friend bool operator>(const_fixed_iterator lhs, const_fixed_iterator rhs) noexcept { + return rhs < lhs; + } + + friend bool operator<=(const_fixed_iterator lhs, const_fixed_iterator rhs) noexcept { + return !(lhs > rhs); + } + + friend bool operator>=(const_fixed_iterator lhs, const_fixed_iterator rhs) noexcept { + return !(lhs < rhs); + } + + const_fixed_iterator& operator+=(difference_type val) noexcept { + m_data += (sizeof(value_type) * val); + return *this; + } + + friend const_fixed_iterator operator+(const_fixed_iterator lhs, difference_type rhs) noexcept { + const_fixed_iterator tmp{lhs}; + tmp.m_data += (sizeof(value_type) * rhs); + return tmp; + } + + friend const_fixed_iterator operator+(difference_type lhs, const_fixed_iterator rhs) noexcept { + const_fixed_iterator tmp{rhs}; + tmp.m_data += (sizeof(value_type) * lhs); + return tmp; + } + + const_fixed_iterator& operator-=(difference_type val) noexcept { + m_data -= (sizeof(value_type) * val); + return *this; + } + + friend const_fixed_iterator operator-(const_fixed_iterator lhs, difference_type rhs) noexcept { + const_fixed_iterator tmp{lhs}; + tmp.m_data -= (sizeof(value_type) * rhs); + return tmp; + } + + friend difference_type operator-(const_fixed_iterator lhs, const_fixed_iterator rhs) noexcept { + return static_cast<difference_type>(lhs.m_data - rhs.m_data) / static_cast<difference_type>(sizeof(T)); + } + + value_type operator[](difference_type n) const noexcept { + return *(*this + n); + } + + /// @endcond + +}; // class const_fixed_iterator + +/** + * A forward iterator used for accessing packed repeated varint fields + * (int32, uint32, int64, uint64, bool, enum). + */ +template <typename T> +class const_varint_iterator { + +protected: + + /// Pointer to current iterator position + const char* m_data = nullptr; // NOLINT(misc-non-private-member-variables-in-classes, cppcoreguidelines-non-private-member-variables-in-classes,-warnings-as-errors) + + /// Pointer to end iterator position + const char* m_end = nullptr; // NOLINT(misc-non-private-member-variables-in-classes, cppcoreguidelines-non-private-member-variables-in-classes,-warnings-as-errors) + +public: + + /// @cond usual iterator functions not documented + + using iterator_category = std::forward_iterator_tag; + using value_type = T; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + static difference_type distance(const_varint_iterator begin, const_varint_iterator end) noexcept { + // The "distance" between default initialized const_varint_iterator's + // is always 0. + if (!begin.m_data) { + return 0; + } + // We know that each varint contains exactly one byte with the most + // significant bit not set. We can use this to quickly figure out + // how many varints there are without actually decoding the varints. + return std::count_if(begin.m_data, end.m_data, [](char c) noexcept { + return (static_cast<unsigned char>(c) & 0x80U) == 0; + }); + } + + const_varint_iterator() noexcept = default; + + const_varint_iterator(const char* data, const char* end) noexcept : + m_data{data}, + m_end{end} { + } + + const_varint_iterator(const const_varint_iterator&) noexcept = default; + const_varint_iterator(const_varint_iterator&&) noexcept = default; + + const_varint_iterator& operator=(const const_varint_iterator&) noexcept = default; + const_varint_iterator& operator=(const_varint_iterator&&) noexcept = default; + + ~const_varint_iterator() noexcept = default; + + value_type operator*() const { + protozero_assert(m_data); + const char* d = m_data; // will be thrown away + return static_cast<value_type>(decode_varint(&d, m_end)); + } + + const_varint_iterator& operator++() { + protozero_assert(m_data); + skip_varint(&m_data, m_end); + return *this; + } + + const_varint_iterator operator++(int) { + protozero_assert(m_data); + const const_varint_iterator tmp{*this}; + ++(*this); + return tmp; + } + + bool operator==(const const_varint_iterator& rhs) const noexcept { + return m_data == rhs.m_data && m_end == rhs.m_end; + } + + bool operator!=(const const_varint_iterator& rhs) const noexcept { + return !(*this == rhs); + } + + /// @endcond + +}; // class const_varint_iterator + +/** + * A forward iterator used for accessing packed repeated svarint fields + * (sint32, sint64). + */ +template <typename T> +class const_svarint_iterator : public const_varint_iterator<T> { + +public: + + /// @cond usual iterator functions not documented + + using iterator_category = std::forward_iterator_tag; + using value_type = T; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + const_svarint_iterator() noexcept : + const_varint_iterator<T>{} { + } + + const_svarint_iterator(const char* data, const char* end) noexcept : + const_varint_iterator<T>{data, end} { + } + + const_svarint_iterator(const const_svarint_iterator&) = default; + const_svarint_iterator(const_svarint_iterator&&) noexcept = default; + + const_svarint_iterator& operator=(const const_svarint_iterator&) = default; + const_svarint_iterator& operator=(const_svarint_iterator&&) noexcept = default; + + ~const_svarint_iterator() = default; + + value_type operator*() const { + protozero_assert(this->m_data); + const char* d = this->m_data; // will be thrown away + return static_cast<value_type>(decode_zigzag64(decode_varint(&d, this->m_end))); + } + + const_svarint_iterator& operator++() { + protozero_assert(this->m_data); + skip_varint(&this->m_data, this->m_end); + return *this; + } + + const_svarint_iterator operator++(int) { + protozero_assert(this->m_data); + const const_svarint_iterator tmp{*this}; + ++(*this); + return tmp; + } + + /// @endcond + +}; // class const_svarint_iterator + +} // end namespace protozero + +namespace std { + + // Specialize std::distance for all the protozero iterators. Because + // functions can't be partially specialized, we have to do this for + // every value_type we are using. + + /// @cond individual overloads do not need to be documented + + template <> + inline typename protozero::const_varint_iterator<int32_t>::difference_type + distance<protozero::const_varint_iterator<int32_t>>(protozero::const_varint_iterator<int32_t> first, // NOLINT(readability-inconsistent-declaration-parameter-name) + protozero::const_varint_iterator<int32_t> last) { + return protozero::const_varint_iterator<int32_t>::distance(first, last); + } + + template <> + inline typename protozero::const_varint_iterator<int64_t>::difference_type + distance<protozero::const_varint_iterator<int64_t>>(protozero::const_varint_iterator<int64_t> first, // NOLINT(readability-inconsistent-declaration-parameter-name) + protozero::const_varint_iterator<int64_t> last) { + return protozero::const_varint_iterator<int64_t>::distance(first, last); + } + + template <> + inline typename protozero::const_varint_iterator<uint32_t>::difference_type + distance<protozero::const_varint_iterator<uint32_t>>(protozero::const_varint_iterator<uint32_t> first, // NOLINT(readability-inconsistent-declaration-parameter-name) + protozero::const_varint_iterator<uint32_t> last) { + return protozero::const_varint_iterator<uint32_t>::distance(first, last); + } + + template <> + inline typename protozero::const_varint_iterator<uint64_t>::difference_type + distance<protozero::const_varint_iterator<uint64_t>>(protozero::const_varint_iterator<uint64_t> first, // NOLINT(readability-inconsistent-declaration-parameter-name) + protozero::const_varint_iterator<uint64_t> last) { + return protozero::const_varint_iterator<uint64_t>::distance(first, last); + } + + template <> + inline typename protozero::const_svarint_iterator<int32_t>::difference_type + distance<protozero::const_svarint_iterator<int32_t>>(protozero::const_svarint_iterator<int32_t> first, // NOLINT(readability-inconsistent-declaration-parameter-name) + protozero::const_svarint_iterator<int32_t> last) { + return protozero::const_svarint_iterator<int32_t>::distance(first, last); + } + + template <> + inline typename protozero::const_svarint_iterator<int64_t>::difference_type + distance<protozero::const_svarint_iterator<int64_t>>(protozero::const_svarint_iterator<int64_t> first, // NOLINT(readability-inconsistent-declaration-parameter-name) + protozero::const_svarint_iterator<int64_t> last) { + return protozero::const_svarint_iterator<int64_t>::distance(first, last); + } + + /// @endcond + +} // end namespace std + +#endif // PROTOZERO_ITERATORS_HPP diff --git a/ext/protozero/include/protozero/pbf_builder.hpp b/ext/protozero/include/protozero/pbf_builder.hpp new file mode 100644 index 0000000..71a2dec --- /dev/null +++ b/ext/protozero/include/protozero/pbf_builder.hpp @@ -0,0 +1,32 @@ +#ifndef PROTOZERO_PBF_BUILDER_HPP +#define PROTOZERO_PBF_BUILDER_HPP + +/***************************************************************************** + +protozero - Minimalistic protocol buffer decoder and encoder in C++. + +This file is from https://github.com/mapbox/protozero where you can find more +documentation. + +*****************************************************************************/ + +/** + * @file pbf_builder.hpp + * + * @brief Contains the pbf_builder template class. + */ + +#include "basic_pbf_builder.hpp" +#include "pbf_writer.hpp" + +#include <string> + +namespace protozero { + +/// Specialization of basic_pbf_builder using std::string as buffer type. +template <typename T> +using pbf_builder = basic_pbf_builder<std::string, T>; + +} // end namespace protozero + +#endif // PROTOZERO_PBF_BUILDER_HPP diff --git a/ext/protozero/include/protozero/pbf_message.hpp b/ext/protozero/include/protozero/pbf_message.hpp new file mode 100644 index 0000000..d7fd8b5 --- /dev/null +++ b/ext/protozero/include/protozero/pbf_message.hpp @@ -0,0 +1,184 @@ +#ifndef PROTOZERO_PBF_MESSAGE_HPP +#define PROTOZERO_PBF_MESSAGE_HPP + +/***************************************************************************** + +protozero - Minimalistic protocol buffer decoder and encoder in C++. + +This file is from https://github.com/mapbox/protozero where you can find more +documentation. + +*****************************************************************************/ + +/** + * @file pbf_message.hpp + * + * @brief Contains the pbf_message template class. + */ + +#include "pbf_reader.hpp" +#include "types.hpp" + +#include <type_traits> + +namespace protozero { + +/** + * This class represents a protobuf message. Either a top-level message or + * a nested sub-message. Top-level messages can be created from any buffer + * with a pointer and length: + * + * @code + * enum class Message : protozero::pbf_tag_type { + * ... + * }; + * + * std::string buffer; + * // fill buffer... + * pbf_message<Message> message{buffer.data(), buffer.size()}; + * @endcode + * + * Sub-messages are created using get_message(): + * + * @code + * enum class SubMessage : protozero::pbf_tag_type { + * ... + * }; + * + * pbf_message<Message> message{...}; + * message.next(); + * pbf_message<SubMessage> submessage = message.get_message(); + * @endcode + * + * All methods of the pbf_message class except get_bytes() and get_string() + * provide the strong exception guarantee, ie they either succeed or do not + * change the pbf_message object they are called on. Use the get_data() method + * instead of get_bytes() or get_string(), if you need this guarantee. + * + * This template class is based on the pbf_reader class and has all the same + * methods. The difference is that whereever the pbf_reader class takes an + * integer tag, this template class takes a tag of the template type T. + * + * Read the tutorial to understand how this class is used. + */ +template <typename T> +class pbf_message : public pbf_reader { + + static_assert(std::is_same<pbf_tag_type, typename std::underlying_type<T>::type>::value, + "T must be enum with underlying type protozero::pbf_tag_type"); + +public: + + /// The type of messages this class will read. + using enum_type = T; + + /** + * Construct a pbf_message. All arguments are forwarded to the pbf_reader + * parent class. + */ + template <typename... Args> + pbf_message(Args&&... args) noexcept : // NOLINT(google-explicit-constructor, hicpp-explicit-conversions) + pbf_reader{std::forward<Args>(args)...} { + } + + /** + * Set next field in the message as the current field. This is usually + * called in a while loop: + * + * @code + * pbf_message<...> message(...); + * while (message.next()) { + * // handle field + * } + * @endcode + * + * @returns `true` if there is a next field, `false` if not. + * @pre There must be no current field. + * @post If it returns `true` there is a current field now. + */ + bool next() { + return pbf_reader::next(); + } + + /** + * Set next field with given tag in the message as the current field. + * Fields with other tags are skipped. This is usually called in a while + * loop for repeated fields: + * + * @code + * pbf_message<Example1> message{...}; + * while (message.next(Example1::repeated_fixed64_r)) { + * // handle field + * } + * @endcode + * + * or you can call it just once to get the one field with this tag: + * + * @code + * pbf_message<Example1> message{...}; + * if (message.next(Example1::required_uint32_x)) { + * // handle field + * } + * @endcode + * + * Note that this will not check the wire type. The two-argument version + * of this function will also check the wire type. + * + * @returns `true` if there is a next field with this tag. + * @pre There must be no current field. + * @post If it returns `true` there is a current field now with the given tag. + */ + bool next(T next_tag) { + return pbf_reader::next(pbf_tag_type(next_tag)); + } + + /** + * Set next field with given tag and wire type in the message as the + * current field. Fields with other tags are skipped. This is usually + * called in a while loop for repeated fields: + * + * @code + * pbf_message<Example1> message{...}; + * while (message.next(Example1::repeated_fixed64_r, pbf_wire_type::varint)) { + * // handle field + * } + * @endcode + * + * or you can call it just once to get the one field with this tag: + * + * @code + * pbf_message<Example1> message{...}; + * if (message.next(Example1::required_uint32_x, pbf_wire_type::varint)) { + * // handle field + * } + * @endcode + * + * Note that this will also check the wire type. The one-argument version + * of this function will not check the wire type. + * + * @returns `true` if there is a next field with this tag. + * @pre There must be no current field. + * @post If it returns `true` there is a current field now with the given tag. + */ + bool next(T next_tag, pbf_wire_type type) { + return pbf_reader::next(pbf_tag_type(next_tag), type); + } + + /** + * The tag of the current field. The tag is the enum value for the field + * number from the description in the .proto file. + * + * Call next() before calling this function to set the current field. + * + * @returns tag of the current field. + * @pre There must be a current field (ie. next() must have returned `true`). + */ + T tag() const noexcept { + return T(pbf_reader::tag()); + } + +}; // class pbf_message + +} // end namespace protozero + +#endif // PROTOZERO_PBF_MESSAGE_HPP diff --git a/ext/protozero/include/protozero/pbf_reader.hpp b/ext/protozero/include/protozero/pbf_reader.hpp new file mode 100644 index 0000000..92bfdee --- /dev/null +++ b/ext/protozero/include/protozero/pbf_reader.hpp @@ -0,0 +1,977 @@ +#ifndef PROTOZERO_PBF_READER_HPP +#define PROTOZERO_PBF_READER_HPP + +/***************************************************************************** + +protozero - Minimalistic protocol buffer decoder and encoder in C++. + +This file is from https://github.com/mapbox/protozero where you can find more +documentation. + +*****************************************************************************/ + +/** + * @file pbf_reader.hpp + * + * @brief Contains the pbf_reader class. + */ + +#include "config.hpp" +#include "data_view.hpp" +#include "exception.hpp" +#include "iterators.hpp" +#include "types.hpp" +#include "varint.hpp" + +#if PROTOZERO_BYTE_ORDER != PROTOZERO_LITTLE_ENDIAN +# include <protozero/byteswap.hpp> +#endif + +#include <cstddef> +#include <cstdint> +#include <cstring> +#include <string> +#include <utility> + +namespace protozero { + +/** + * This class represents a protobuf message. Either a top-level message or + * a nested sub-message. Top-level messages can be created from any buffer + * with a pointer and length: + * + * @code + * std::string buffer; + * // fill buffer... + * pbf_reader message{buffer.data(), buffer.size()}; + * @endcode + * + * Sub-messages are created using get_message(): + * + * @code + * pbf_reader message{...}; + * message.next(); + * pbf_reader submessage = message.get_message(); + * @endcode + * + * All methods of the pbf_reader class except get_bytes() and get_string() + * provide the strong exception guarantee, ie they either succeed or do not + * change the pbf_reader object they are called on. Use the get_view() method + * instead of get_bytes() or get_string(), if you need this guarantee. + */ +class pbf_reader { + + // A pointer to the next unread data. + const char* m_data = nullptr; + + // A pointer to one past the end of data. + const char* m_end = nullptr; + + // The wire type of the current field. + pbf_wire_type m_wire_type = pbf_wire_type::unknown; + + // The tag of the current field. + pbf_tag_type m_tag = 0; + + template <typename T> + T get_fixed() { + T result; + const char* data = m_data; + skip_bytes(sizeof(T)); + std::memcpy(&result, data, sizeof(T)); +#if PROTOZERO_BYTE_ORDER != PROTOZERO_LITTLE_ENDIAN + byteswap_inplace(&result); +#endif + return result; + } + + template <typename T> + iterator_range<const_fixed_iterator<T>> packed_fixed() { + protozero_assert(tag() != 0 && "call next() before accessing field value"); + const auto len = get_len_and_skip(); + if (len % sizeof(T) != 0) { + throw invalid_length_exception{}; + } + return {const_fixed_iterator<T>(m_data - len), + const_fixed_iterator<T>(m_data)}; + } + + template <typename T> + T get_varint() { + const auto val = static_cast<T>(decode_varint(&m_data, m_end)); + return val; + } + + template <typename T> + T get_svarint() { + protozero_assert((has_wire_type(pbf_wire_type::varint) || has_wire_type(pbf_wire_type::length_delimited)) && "not a varint"); + return static_cast<T>(decode_zigzag64(decode_varint(&m_data, m_end))); + } + + pbf_length_type get_length() { + return get_varint<pbf_length_type>(); + } + + void skip_bytes(pbf_length_type len) { + if (m_end - m_data < static_cast<ptrdiff_t>(len)) { + throw end_of_buffer_exception{}; + } + m_data += len; + +#ifndef NDEBUG + // In debug builds reset the tag to zero so that we can detect (some) + // wrong code. + m_tag = 0; +#endif + } + + pbf_length_type get_len_and_skip() { + const auto len = get_length(); + skip_bytes(len); + return len; + } + + template <typename T> + iterator_range<T> get_packed() { + protozero_assert(tag() != 0 && "call next() before accessing field value"); + const auto len = get_len_and_skip(); + return {T{m_data - len, m_data}, + T{m_data, m_data}}; + } + +public: + + /** + * Construct a pbf_reader message from a data_view. The pointer from the + * data_view will be stored inside the pbf_reader object, no data is + * copied. So you must make sure the view stays valid as long as the + * pbf_reader object is used. + * + * The buffer must contain a complete protobuf message. + * + * @post There is no current field. + */ + explicit pbf_reader(const data_view& view) noexcept + : m_data{view.data()}, + m_end{view.data() + view.size()} { + } + + /** + * Construct a pbf_reader message from a data pointer and a length. The + * pointer will be stored inside the pbf_reader object, no data is copied. + * So you must make sure the buffer stays valid as long as the pbf_reader + * object is used. + * + * The buffer must contain a complete protobuf message. + * + * @post There is no current field. + */ + pbf_reader(const char* data, std::size_t size) noexcept + : m_data{data}, + m_end{data + size} { + } + +#ifndef PROTOZERO_STRICT_API + /** + * Construct a pbf_reader message from a data pointer and a length. The + * pointer will be stored inside the pbf_reader object, no data is copied. + * So you must make sure the buffer stays valid as long as the pbf_reader + * object is used. + * + * The buffer must contain a complete protobuf message. + * + * @post There is no current field. + * @deprecated Use one of the other constructors. + */ + explicit pbf_reader(const std::pair<const char*, std::size_t>& data) noexcept + : m_data{data.first}, + m_end{data.first + data.second} { + } +#endif + + /** + * Construct a pbf_reader message from a std::string. A pointer to the + * string internals will be stored inside the pbf_reader object, no data + * is copied. So you must make sure the string is unchanged as long as the + * pbf_reader object is used. + * + * The string must contain a complete protobuf message. + * + * @post There is no current field. + */ + explicit pbf_reader(const std::string& data) noexcept + : m_data{data.data()}, + m_end{data.data() + data.size()} { + } + + /** + * pbf_reader can be default constructed and behaves like it has an empty + * buffer. + */ + pbf_reader() noexcept = default; + + /// pbf_reader messages can be copied trivially. + pbf_reader(const pbf_reader&) noexcept = default; + + /// pbf_reader messages can be moved trivially. + pbf_reader(pbf_reader&&) noexcept = default; + + /// pbf_reader messages can be copied trivially. + pbf_reader& operator=(const pbf_reader& other) noexcept = default; + + /// pbf_reader messages can be moved trivially. + pbf_reader& operator=(pbf_reader&& other) noexcept = default; + + ~pbf_reader() = default; + + /** + * Swap the contents of this object with the other. + * + * @param other Other object to swap data with. + */ + void swap(pbf_reader& other) noexcept { + using std::swap; + swap(m_data, other.m_data); + swap(m_end, other.m_end); + swap(m_wire_type, other.m_wire_type); + swap(m_tag, other.m_tag); + } + + /** + * In a boolean context the pbf_reader class evaluates to `true` if there + * are still fields available and to `false` if the last field has been + * read. + */ + operator bool() const noexcept { // NOLINT(google-explicit-constructor, hicpp-explicit-conversions) + return m_data != m_end; + } + + /** + * Get a view of the not yet read data. + */ + data_view data() const noexcept { + return {m_data, static_cast<std::size_t>(m_end - m_data)}; + } + + /** + * Return the length in bytes of the current message. If you have + * already called next() and/or any of the get_*() functions, this will + * return the remaining length. + * + * This can, for instance, be used to estimate the space needed for a + * buffer. Of course you have to know reasonably well what data to expect + * and how it is encoded for this number to have any meaning. + */ + std::size_t length() const noexcept { + return std::size_t(m_end - m_data); + } + + /** + * Set next field in the message as the current field. This is usually + * called in a while loop: + * + * @code + * pbf_reader message(...); + * while (message.next()) { + * // handle field + * } + * @endcode + * + * @returns `true` if there is a next field, `false` if not. + * @pre There must be no current field. + * @post If it returns `true` there is a current field now. + */ + bool next() { + if (m_data == m_end) { + return false; + } + + const auto value = get_varint<uint32_t>(); + m_tag = pbf_tag_type(value >> 3U); + + // tags 0 and 19000 to 19999 are not allowed as per + // https://developers.google.com/protocol-buffers/docs/proto#assigning-tags + if (m_tag == 0 || (m_tag >= 19000 && m_tag <= 19999)) { + throw invalid_tag_exception{}; + } + + m_wire_type = pbf_wire_type(value & 0x07U); + switch (m_wire_type) { + case pbf_wire_type::varint: + case pbf_wire_type::fixed64: + case pbf_wire_type::length_delimited: + case pbf_wire_type::fixed32: + break; + default: + throw unknown_pbf_wire_type_exception{}; + } + + return true; + } + + /** + * Set next field with given tag in the message as the current field. + * Fields with other tags are skipped. This is usually called in a while + * loop for repeated fields: + * + * @code + * pbf_reader message{...}; + * while (message.next(17)) { + * // handle field + * } + * @endcode + * + * or you can call it just once to get the one field with this tag: + * + * @code + * pbf_reader message{...}; + * if (message.next(17)) { + * // handle field + * } + * @endcode + * + * Note that this will not check the wire type. The two-argument version + * of this function will also check the wire type. + * + * @returns `true` if there is a next field with this tag. + * @pre There must be no current field. + * @post If it returns `true` there is a current field now with the given tag. + */ + bool next(pbf_tag_type next_tag) { + while (next()) { + if (m_tag == next_tag) { + return true; + } + skip(); + } + return false; + } + + /** + * Set next field with given tag and wire type in the message as the + * current field. Fields with other tags are skipped. This is usually + * called in a while loop for repeated fields: + * + * @code + * pbf_reader message{...}; + * while (message.next(17, pbf_wire_type::varint)) { + * // handle field + * } + * @endcode + * + * or you can call it just once to get the one field with this tag: + * + * @code + * pbf_reader message{...}; + * if (message.next(17, pbf_wire_type::varint)) { + * // handle field + * } + * @endcode + * + * Note that this will also check the wire type. The one-argument version + * of this function will not check the wire type. + * + * @returns `true` if there is a next field with this tag. + * @pre There must be no current field. + * @post If it returns `true` there is a current field now with the given tag. + */ + bool next(pbf_tag_type next_tag, pbf_wire_type type) { + while (next()) { + if (m_tag == next_tag && m_wire_type == type) { + return true; + } + skip(); + } + return false; + } + + /** + * The tag of the current field. The tag is the field number from the + * description in the .proto file. + * + * Call next() before calling this function to set the current field. + * + * @returns tag of the current field. + * @pre There must be a current field (ie. next() must have returned `true`). + */ + pbf_tag_type tag() const noexcept { + return m_tag; + } + + /** + * Get the wire type of the current field. The wire types are: + * + * * 0 - varint + * * 1 - 64 bit + * * 2 - length-delimited + * * 5 - 32 bit + * + * All other types are illegal. + * + * Call next() before calling this function to set the current field. + * + * @returns wire type of the current field. + * @pre There must be a current field (ie. next() must have returned `true`). + */ + pbf_wire_type wire_type() const noexcept { + return m_wire_type; + } + + /** + * Get the tag and wire type of the current field in one integer suitable + * for comparison with a switch statement. + * + * Use it like this: + * + * @code + * pbf_reader message{...}; + * while (message.next()) { + * switch (message.tag_and_type()) { + * case tag_and_type(17, pbf_wire_type::length_delimited): + * .... + * break; + * case tag_and_type(21, pbf_wire_type::varint): + * .... + * break; + * default: + * message.skip(); + * } + * } + * @endcode + */ + uint32_t tag_and_type() const noexcept { + return protozero::tag_and_type(tag(), wire_type()); + } + + /** + * Check the wire type of the current field. + * + * @returns `true` if the current field has the given wire type. + * @pre There must be a current field (ie. next() must have returned `true`). + */ + bool has_wire_type(pbf_wire_type type) const noexcept { + return wire_type() == type; + } + + /** + * Consume the current field. + * + * @pre There must be a current field (ie. next() must have returned `true`). + * @post The current field was consumed and there is no current field now. + */ + void skip() { + protozero_assert(tag() != 0 && "call next() before calling skip()"); + switch (wire_type()) { + case pbf_wire_type::varint: + skip_varint(&m_data, m_end); + break; + case pbf_wire_type::fixed64: + skip_bytes(8); + break; + case pbf_wire_type::length_delimited: + skip_bytes(get_length()); + break; + case pbf_wire_type::fixed32: + skip_bytes(4); + break; + default: + break; + } + } + + ///@{ + /** + * @name Scalar field accessor functions + */ + + /** + * Consume and return value of current "bool" field. + * + * @pre There must be a current field (ie. next() must have returned `true`). + * @pre The current field must be of type "bool". + * @post The current field was consumed and there is no current field now. + */ + bool get_bool() { + protozero_assert(tag() != 0 && "call next() before accessing field value"); + protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint"); + const bool result = m_data[0] != 0; + skip_varint(&m_data, m_end); + return result; + } + + /** + * Consume and return value of current "enum" field. + * + * @pre There must be a current field (ie. next() must have returned `true`). + * @pre The current field must be of type "enum". + * @post The current field was consumed and there is no current field now. + */ + int32_t get_enum() { + protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint"); + return get_varint<int32_t>(); + } + + /** + * Consume and return value of current "int32" varint field. + * + * @pre There must be a current field (ie. next() must have returned `true`). + * @pre The current field must be of type "int32". + * @post The current field was consumed and there is no current field now. + */ + int32_t get_int32() { + protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint"); + return get_varint<int32_t>(); + } + + /** + * Consume and return value of current "sint32" varint field. + * + * @pre There must be a current field (ie. next() must have returned `true`). + * @pre The current field must be of type "sint32". + * @post The current field was consumed and there is no current field now. + */ + int32_t get_sint32() { + protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint"); + return get_svarint<int32_t>(); + } + + /** + * Consume and return value of current "uint32" varint field. + * + * @pre There must be a current field (ie. next() must have returned `true`). + * @pre The current field must be of type "uint32". + * @post The current field was consumed and there is no current field now. + */ + uint32_t get_uint32() { + protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint"); + return get_varint<uint32_t>(); + } + + /** + * Consume and return value of current "int64" varint field. + * + * @pre There must be a current field (ie. next() must have returned `true`). + * @pre The current field must be of type "int64". + * @post The current field was consumed and there is no current field now. + */ + int64_t get_int64() { + protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint"); + return get_varint<int64_t>(); + } + + /** + * Consume and return value of current "sint64" varint field. + * + * @pre There must be a current field (ie. next() must have returned `true`). + * @pre The current field must be of type "sint64". + * @post The current field was consumed and there is no current field now. + */ + int64_t get_sint64() { + protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint"); + return get_svarint<int64_t>(); + } + + /** + * Consume and return value of current "uint64" varint field. + * + * @pre There must be a current field (ie. next() must have returned `true`). + * @pre The current field must be of type "uint64". + * @post The current field was consumed and there is no current field now. + */ + uint64_t get_uint64() { + protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint"); + return get_varint<uint64_t>(); + } + + /** + * Consume and return value of current "fixed32" field. + * + * @pre There must be a current field (ie. next() must have returned `true`). + * @pre The current field must be of type "fixed32". + * @post The current field was consumed and there is no current field now. + */ + uint32_t get_fixed32() { + protozero_assert(tag() != 0 && "call next() before accessing field value"); + protozero_assert(has_wire_type(pbf_wire_type::fixed32) && "not a 32-bit fixed"); + return get_fixed<uint32_t>(); + } + + /** + * Consume and return value of current "sfixed32" field. + * + * @pre There must be a current field (ie. next() must have returned `true`). + * @pre The current field must be of type "sfixed32". + * @post The current field was consumed and there is no current field now. + */ + int32_t get_sfixed32() { + protozero_assert(tag() != 0 && "call next() before accessing field value"); + protozero_assert(has_wire_type(pbf_wire_type::fixed32) && "not a 32-bit fixed"); + return get_fixed<int32_t>(); + } + + /** + * Consume and return value of current "fixed64" field. + * + * @pre There must be a current field (ie. next() must have returned `true`). + * @pre The current field must be of type "fixed64". + * @post The current field was consumed and there is no current field now. + */ + uint64_t get_fixed64() { + protozero_assert(tag() != 0 && "call next() before accessing field value"); + protozero_assert(has_wire_type(pbf_wire_type::fixed64) && "not a 64-bit fixed"); + return get_fixed<uint64_t>(); + } + + /** + * Consume and return value of current "sfixed64" field. + * + * @pre There must be a current field (ie. next() must have returned `true`). + * @pre The current field must be of type "sfixed64". + * @post The current field was consumed and there is no current field now. + */ + int64_t get_sfixed64() { + protozero_assert(tag() != 0 && "call next() before accessing field value"); + protozero_assert(has_wire_type(pbf_wire_type::fixed64) && "not a 64-bit fixed"); + return get_fixed<int64_t>(); + } + + /** + * Consume and return value of current "float" field. + * + * @pre There must be a current field (ie. next() must have returned `true`). + * @pre The current field must be of type "float". + * @post The current field was consumed and there is no current field now. + */ + float get_float() { + protozero_assert(tag() != 0 && "call next() before accessing field value"); + protozero_assert(has_wire_type(pbf_wire_type::fixed32) && "not a 32-bit fixed"); + return get_fixed<float>(); + } + + /** + * Consume and return value of current "double" field. + * + * @pre There must be a current field (ie. next() must have returned `true`). + * @pre The current field must be of type "double". + * @post The current field was consumed and there is no current field now. + */ + double get_double() { + protozero_assert(tag() != 0 && "call next() before accessing field value"); + protozero_assert(has_wire_type(pbf_wire_type::fixed64) && "not a 64-bit fixed"); + return get_fixed<double>(); + } + + /** + * Consume and return value of current "bytes", "string", or "message" + * field. + * + * @returns A data_view object. + * @pre There must be a current field (ie. next() must have returned `true`). + * @pre The current field must be of type "bytes", "string", or "message". + * @post The current field was consumed and there is no current field now. + */ + data_view get_view() { + protozero_assert(tag() != 0 && "call next() before accessing field value"); + protozero_assert(has_wire_type(pbf_wire_type::length_delimited) && "not of type string, bytes or message"); + const auto len = get_len_and_skip(); + return {m_data - len, len}; + } + +#ifndef PROTOZERO_STRICT_API + /** + * Consume and return value of current "bytes" or "string" field. + * + * @returns A pair with a pointer to the data and the length of the data. + * @pre There must be a current field (ie. next() must have returned `true`). + * @pre The current field must be of type "bytes" or "string". + * @post The current field was consumed and there is no current field now. + */ + std::pair<const char*, pbf_length_type> get_data() { + protozero_assert(tag() != 0 && "call next() before accessing field value"); + protozero_assert(has_wire_type(pbf_wire_type::length_delimited) && "not of type string, bytes or message"); + const auto len = get_len_and_skip(); + return {m_data - len, len}; + } +#endif + + /** + * Consume and return value of current "bytes" field. + * + * @pre There must be a current field (ie. next() must have returned `true`). + * @pre The current field must be of type "bytes". + * @post The current field was consumed and there is no current field now. + */ + std::string get_bytes() { + return std::string(get_view()); + } + + /** + * Consume and return value of current "string" field. + * + * @pre There must be a current field (ie. next() must have returned `true`). + * @pre The current field must be of type "string". + * @post The current field was consumed and there is no current field now. + */ + std::string get_string() { + return std::string(get_view()); + } + + /** + * Consume and return value of current "message" field. + * + * @pre There must be a current field (ie. next() must have returned `true`). + * @pre The current field must be of type "message". + * @post The current field was consumed and there is no current field now. + */ + pbf_reader get_message() { + return pbf_reader{get_view()}; + } + + ///@} + + /// Forward iterator for iterating over bool (int32 varint) values. + using const_bool_iterator = const_varint_iterator< int32_t>; + + /// Forward iterator for iterating over enum (int32 varint) values. + using const_enum_iterator = const_varint_iterator< int32_t>; + + /// Forward iterator for iterating over int32 (varint) values. + using const_int32_iterator = const_varint_iterator< int32_t>; + + /// Forward iterator for iterating over sint32 (varint) values. + using const_sint32_iterator = const_svarint_iterator<int32_t>; + + /// Forward iterator for iterating over uint32 (varint) values. + using const_uint32_iterator = const_varint_iterator<uint32_t>; + + /// Forward iterator for iterating over int64 (varint) values. + using const_int64_iterator = const_varint_iterator< int64_t>; + + /// Forward iterator for iterating over sint64 (varint) values. + using const_sint64_iterator = const_svarint_iterator<int64_t>; + + /// Forward iterator for iterating over uint64 (varint) values. + using const_uint64_iterator = const_varint_iterator<uint64_t>; + + /// Forward iterator for iterating over fixed32 values. + using const_fixed32_iterator = const_fixed_iterator<uint32_t>; + + /// Forward iterator for iterating over sfixed32 values. + using const_sfixed32_iterator = const_fixed_iterator<int32_t>; + + /// Forward iterator for iterating over fixed64 values. + using const_fixed64_iterator = const_fixed_iterator<uint64_t>; + + /// Forward iterator for iterating over sfixed64 values. + using const_sfixed64_iterator = const_fixed_iterator<int64_t>; + + /// Forward iterator for iterating over float values. + using const_float_iterator = const_fixed_iterator<float>; + + /// Forward iterator for iterating over double values. + using const_double_iterator = const_fixed_iterator<double>; + + ///@{ + /** + * @name Repeated packed field accessor functions + */ + + /** + * Consume current "repeated packed bool" field. + * + * @returns a pair of iterators to the beginning and one past the end of + * the data. + * @pre There must be a current field (ie. next() must have returned `true`). + * @pre The current field must be of type "repeated packed bool". + * @post The current field was consumed and there is no current field now. + */ + iterator_range<pbf_reader::const_bool_iterator> get_packed_bool() { + return get_packed<pbf_reader::const_bool_iterator>(); + } + + /** + * Consume current "repeated packed enum" field. + * + * @returns a pair of iterators to the beginning and one past the end of + * the data. + * @pre There must be a current field (ie. next() must have returned `true`). + * @pre The current field must be of type "repeated packed enum". + * @post The current field was consumed and there is no current field now. + */ + iterator_range<pbf_reader::const_enum_iterator> get_packed_enum() { + return get_packed<pbf_reader::const_enum_iterator>(); + } + + /** + * Consume current "repeated packed int32" field. + * + * @returns a pair of iterators to the beginning and one past the end of + * the data. + * @pre There must be a current field (ie. next() must have returned `true`). + * @pre The current field must be of type "repeated packed int32". + * @post The current field was consumed and there is no current field now. + */ + iterator_range<pbf_reader::const_int32_iterator> get_packed_int32() { + return get_packed<pbf_reader::const_int32_iterator>(); + } + + /** + * Consume current "repeated packed sint32" field. + * + * @returns a pair of iterators to the beginning and one past the end of + * the data. + * @pre There must be a current field (ie. next() must have returned `true`). + * @pre The current field must be of type "repeated packed sint32". + * @post The current field was consumed and there is no current field now. + */ + iterator_range<pbf_reader::const_sint32_iterator> get_packed_sint32() { + return get_packed<pbf_reader::const_sint32_iterator>(); + } + + /** + * Consume current "repeated packed uint32" field. + * + * @returns a pair of iterators to the beginning and one past the end of + * the data. + * @pre There must be a current field (ie. next() must have returned `true`). + * @pre The current field must be of type "repeated packed uint32". + * @post The current field was consumed and there is no current field now. + */ + iterator_range<pbf_reader::const_uint32_iterator> get_packed_uint32() { + return get_packed<pbf_reader::const_uint32_iterator>(); + } + + /** + * Consume current "repeated packed int64" field. + * + * @returns a pair of iterators to the beginning and one past the end of + * the data. + * @pre There must be a current field (ie. next() must have returned `true`). + * @pre The current field must be of type "repeated packed int64". + * @post The current field was consumed and there is no current field now. + */ + iterator_range<pbf_reader::const_int64_iterator> get_packed_int64() { + return get_packed<pbf_reader::const_int64_iterator>(); + } + + /** + * Consume current "repeated packed sint64" field. + * + * @returns a pair of iterators to the beginning and one past the end of + * the data. + * @pre There must be a current field (ie. next() must have returned `true`). + * @pre The current field must be of type "repeated packed sint64". + * @post The current field was consumed and there is no current field now. + */ + iterator_range<pbf_reader::const_sint64_iterator> get_packed_sint64() { + return get_packed<pbf_reader::const_sint64_iterator>(); + } + + /** + * Consume current "repeated packed uint64" field. + * + * @returns a pair of iterators to the beginning and one past the end of + * the data. + * @pre There must be a current field (ie. next() must have returned `true`). + * @pre The current field must be of type "repeated packed uint64". + * @post The current field was consumed and there is no current field now. + */ + iterator_range<pbf_reader::const_uint64_iterator> get_packed_uint64() { + return get_packed<pbf_reader::const_uint64_iterator>(); + } + + /** + * Consume current "repeated packed fixed32" field. + * + * @returns a pair of iterators to the beginning and one past the end of + * the data. + * @pre There must be a current field (ie. next() must have returned `true`). + * @pre The current field must be of type "repeated packed fixed32". + * @post The current field was consumed and there is no current field now. + */ + iterator_range<pbf_reader::const_fixed32_iterator> get_packed_fixed32() { + return packed_fixed<uint32_t>(); + } + + /** + * Consume current "repeated packed sfixed32" field. + * + * @returns a pair of iterators to the beginning and one past the end of + * the data. + * @pre There must be a current field (ie. next() must have returned `true`). + * @pre The current field must be of type "repeated packed sfixed32". + * @post The current field was consumed and there is no current field now. + */ + iterator_range<pbf_reader::const_sfixed32_iterator> get_packed_sfixed32() { + return packed_fixed<int32_t>(); + } + + /** + * Consume current "repeated packed fixed64" field. + * + * @returns a pair of iterators to the beginning and one past the end of + * the data. + * @pre There must be a current field (ie. next() must have returned `true`). + * @pre The current field must be of type "repeated packed fixed64". + * @post The current field was consumed and there is no current field now. + */ + iterator_range<pbf_reader::const_fixed64_iterator> get_packed_fixed64() { + return packed_fixed<uint64_t>(); + } + + /** + * Consume current "repeated packed sfixed64" field. + * + * @returns a pair of iterators to the beginning and one past the end of + * the data. + * @pre There must be a current field (ie. next() must have returned `true`). + * @pre The current field must be of type "repeated packed sfixed64". + * @post The current field was consumed and there is no current field now. + */ + iterator_range<pbf_reader::const_sfixed64_iterator> get_packed_sfixed64() { + return packed_fixed<int64_t>(); + } + + /** + * Consume current "repeated packed float" field. + * + * @returns a pair of iterators to the beginning and one past the end of + * the data. + * @pre There must be a current field (ie. next() must have returned `true`). + * @pre The current field must be of type "repeated packed float". + * @post The current field was consumed and there is no current field now. + */ + iterator_range<pbf_reader::const_float_iterator> get_packed_float() { + return packed_fixed<float>(); + } + + /** + * Consume current "repeated packed double" field. + * + * @returns a pair of iterators to the beginning and one past the end of + * the data. + * @pre There must be a current field (ie. next() must have returned `true`). + * @pre The current field must be of type "repeated packed double". + * @post The current field was consumed and there is no current field now. + */ + iterator_range<pbf_reader::const_double_iterator> get_packed_double() { + return packed_fixed<double>(); + } + + ///@} + +}; // class pbf_reader + +/** + * Swap two pbf_reader objects. + * + * @param lhs First object. + * @param rhs Second object. + */ +inline void swap(pbf_reader& lhs, pbf_reader& rhs) noexcept { + lhs.swap(rhs); +} + +} // end namespace protozero + +#endif // PROTOZERO_PBF_READER_HPP diff --git a/ext/protozero/include/protozero/pbf_writer.hpp b/ext/protozero/include/protozero/pbf_writer.hpp new file mode 100644 index 0000000..9a07bd5 --- /dev/null +++ b/ext/protozero/include/protozero/pbf_writer.hpp @@ -0,0 +1,76 @@ +#ifndef PROTOZERO_PBF_WRITER_HPP +#define PROTOZERO_PBF_WRITER_HPP + +/***************************************************************************** + +protozero - Minimalistic protocol buffer decoder and encoder in C++. + +This file is from https://github.com/mapbox/protozero where you can find more +documentation. + +*****************************************************************************/ + +/** + * @file pbf_writer.hpp + * + * @brief Contains the pbf_writer class. + */ + +#include "basic_pbf_writer.hpp" +#include "buffer_string.hpp" + +#include <cstdint> +#include <string> + +namespace protozero { + +/** + * Specialization of basic_pbf_writer using std::string as buffer type. + */ +using pbf_writer = basic_pbf_writer<std::string>; + +/// Class for generating packed repeated bool fields. +using packed_field_bool = detail::packed_field_varint<std::string, bool>; + +/// Class for generating packed repeated enum fields. +using packed_field_enum = detail::packed_field_varint<std::string, int32_t>; + +/// Class for generating packed repeated int32 fields. +using packed_field_int32 = detail::packed_field_varint<std::string, int32_t>; + +/// Class for generating packed repeated sint32 fields. +using packed_field_sint32 = detail::packed_field_svarint<std::string, int32_t>; + +/// Class for generating packed repeated uint32 fields. +using packed_field_uint32 = detail::packed_field_varint<std::string, uint32_t>; + +/// Class for generating packed repeated int64 fields. +using packed_field_int64 = detail::packed_field_varint<std::string, int64_t>; + +/// Class for generating packed repeated sint64 fields. +using packed_field_sint64 = detail::packed_field_svarint<std::string, int64_t>; + +/// Class for generating packed repeated uint64 fields. +using packed_field_uint64 = detail::packed_field_varint<std::string, uint64_t>; + +/// Class for generating packed repeated fixed32 fields. +using packed_field_fixed32 = detail::packed_field_fixed<std::string, uint32_t>; + +/// Class for generating packed repeated sfixed32 fields. +using packed_field_sfixed32 = detail::packed_field_fixed<std::string, int32_t>; + +/// Class for generating packed repeated fixed64 fields. +using packed_field_fixed64 = detail::packed_field_fixed<std::string, uint64_t>; + +/// Class for generating packed repeated sfixed64 fields. +using packed_field_sfixed64 = detail::packed_field_fixed<std::string, int64_t>; + +/// Class for generating packed repeated float fields. +using packed_field_float = detail::packed_field_fixed<std::string, float>; + +/// Class for generating packed repeated double fields. +using packed_field_double = detail::packed_field_fixed<std::string, double>; + +} // end namespace protozero + +#endif // PROTOZERO_PBF_WRITER_HPP diff --git a/ext/protozero/include/protozero/types.hpp b/ext/protozero/include/protozero/types.hpp new file mode 100644 index 0000000..3aefddf --- /dev/null +++ b/ext/protozero/include/protozero/types.hpp @@ -0,0 +1,66 @@ +#ifndef PROTOZERO_TYPES_HPP +#define PROTOZERO_TYPES_HPP + +/***************************************************************************** + +protozero - Minimalistic protocol buffer decoder and encoder in C++. + +This file is from https://github.com/mapbox/protozero where you can find more +documentation. + +*****************************************************************************/ + +/** + * @file types.hpp + * + * @brief Contains the declaration of low-level types used in the pbf format. + */ + +#include "config.hpp" + +#include <algorithm> +#include <cstddef> +#include <cstdint> +#include <cstring> +#include <string> +#include <utility> + +namespace protozero { + +/** + * The type used for field tags (field numbers). + */ +using pbf_tag_type = uint32_t; + +/** + * The type used to encode type information. + * See the table on + * https://developers.google.com/protocol-buffers/docs/encoding + */ +enum class pbf_wire_type : uint32_t { + varint = 0, // int32/64, uint32/64, sint32/64, bool, enum + fixed64 = 1, // fixed64, sfixed64, double + length_delimited = 2, // string, bytes, nested messages, packed repeated fields + fixed32 = 5, // fixed32, sfixed32, float + unknown = 99 // used for default setting in this library +}; + +/** + * Get the tag and wire type of the current field in one integer suitable + * for comparison with a switch statement. + * + * See pbf_reader.tag_and_type() for an example how to use this. + */ +template <typename T> +constexpr inline uint32_t tag_and_type(T tag, pbf_wire_type wire_type) noexcept { + return (static_cast<uint32_t>(static_cast<pbf_tag_type>(tag)) << 3U) | static_cast<uint32_t>(wire_type); +} + +/** + * The type used for length values, such as the length of a field. + */ +using pbf_length_type = uint32_t; + +} // end namespace protozero + +#endif // PROTOZERO_TYPES_HPP diff --git a/ext/protozero/include/protozero/varint.hpp b/ext/protozero/include/protozero/varint.hpp new file mode 100644 index 0000000..b4648a4 --- /dev/null +++ b/ext/protozero/include/protozero/varint.hpp @@ -0,0 +1,245 @@ +#ifndef PROTOZERO_VARINT_HPP +#define PROTOZERO_VARINT_HPP + +/***************************************************************************** + +protozero - Minimalistic protocol buffer decoder and encoder in C++. + +This file is from https://github.com/mapbox/protozero where you can find more +documentation. + +*****************************************************************************/ + +/** + * @file varint.hpp + * + * @brief Contains low-level varint and zigzag encoding and decoding functions. + */ + +#include "buffer_tmpl.hpp" +#include "exception.hpp" + +#include <cstdint> + +namespace protozero { + +/** + * The maximum length of a 64 bit varint. + */ +constexpr const int8_t max_varint_length = sizeof(uint64_t) * 8 / 7 + 1; + +namespace detail { + + // from https://github.com/facebook/folly/blob/master/folly/Varint.h + inline uint64_t decode_varint_impl(const char** data, const char* end) { + const auto* begin = reinterpret_cast<const int8_t*>(*data); + const auto* iend = reinterpret_cast<const int8_t*>(end); + const int8_t* p = begin; + uint64_t val = 0; + + if (iend - begin >= max_varint_length) { // fast path + do { + int64_t b = *p++; + val = ((uint64_t(b) & 0x7fU) ); if (b >= 0) { break; } + b = *p++; val |= ((uint64_t(b) & 0x7fU) << 7U); if (b >= 0) { break; } + b = *p++; val |= ((uint64_t(b) & 0x7fU) << 14U); if (b >= 0) { break; } + b = *p++; val |= ((uint64_t(b) & 0x7fU) << 21U); if (b >= 0) { break; } + b = *p++; val |= ((uint64_t(b) & 0x7fU) << 28U); if (b >= 0) { break; } + b = *p++; val |= ((uint64_t(b) & 0x7fU) << 35U); if (b >= 0) { break; } + b = *p++; val |= ((uint64_t(b) & 0x7fU) << 42U); if (b >= 0) { break; } + b = *p++; val |= ((uint64_t(b) & 0x7fU) << 49U); if (b >= 0) { break; } + b = *p++; val |= ((uint64_t(b) & 0x7fU) << 56U); if (b >= 0) { break; } + b = *p++; val |= ((uint64_t(b) & 0x01U) << 63U); if (b >= 0) { break; } + throw varint_too_long_exception{}; + } while (false); + } else { + unsigned int shift = 0; + while (p != iend && *p < 0) { + val |= (uint64_t(*p++) & 0x7fU) << shift; + shift += 7; + } + if (p == iend) { + throw end_of_buffer_exception{}; + } + val |= uint64_t(*p++) << shift; + } + + *data = reinterpret_cast<const char*>(p); + return val; + } + +} // end namespace detail + +/** + * Decode a 64 bit varint. + * + * Strong exception guarantee: if there is an exception the data pointer will + * not be changed. + * + * @param[in,out] data Pointer to pointer to the input data. After the function + * returns this will point to the next data to be read. + * @param[in] end Pointer one past the end of the input data. + * @returns The decoded integer + * @throws varint_too_long_exception if the varint is longer then the maximum + * length that would fit in a 64 bit int. Usually this means your data + * is corrupted or you are trying to read something as a varint that + * isn't. + * @throws end_of_buffer_exception if the *end* of the buffer was reached + * before the end of the varint. + */ +inline uint64_t decode_varint(const char** data, const char* end) { + // If this is a one-byte varint, decode it here. + if (end != *data && ((static_cast<uint64_t>(**data) & 0x80U) == 0)) { + const auto val = static_cast<uint64_t>(**data); + ++(*data); + return val; + } + // If this varint is more than one byte, defer to complete implementation. + return detail::decode_varint_impl(data, end); +} + +/** + * Skip over a varint. + * + * Strong exception guarantee: if there is an exception the data pointer will + * not be changed. + * + * @param[in,out] data Pointer to pointer to the input data. After the function + * returns this will point to the next data to be read. + * @param[in] end Pointer one past the end of the input data. + * @throws end_of_buffer_exception if the *end* of the buffer was reached + * before the end of the varint. + */ +inline void skip_varint(const char** data, const char* end) { + const auto* begin = reinterpret_cast<const int8_t*>(*data); + const auto* iend = reinterpret_cast<const int8_t*>(end); + const int8_t* p = begin; + + while (p != iend && *p < 0) { + ++p; + } + + if (p - begin >= max_varint_length) { + throw varint_too_long_exception{}; + } + + if (p == iend) { + throw end_of_buffer_exception{}; + } + + ++p; + + *data = reinterpret_cast<const char*>(p); +} + +/** + * Varint encode a 64 bit integer. + * + * @tparam T An output iterator type. + * @param data Output iterator the varint encoded value will be written to + * byte by byte. + * @param value The integer that will be encoded. + * @returns the number of bytes written + * @throws Any exception thrown by increment or dereference operator on data. + * @deprecated Use add_varint_to_buffer() instead. + */ +template <typename T> +inline int write_varint(T data, uint64_t value) { + int n = 1; + + while (value >= 0x80U) { + *data++ = char((value & 0x7fU) | 0x80U); + value >>= 7U; + ++n; + } + *data = char(value); + + return n; +} + +/** + * Varint encode a 64 bit integer. + * + * @tparam TBuffer A buffer type. + * @param buffer Output buffer the varint will be written to. + * @param value The integer that will be encoded. + * @returns the number of bytes written + * @throws Any exception thrown by calling the buffer_push_back() function. + */ +template <typename TBuffer> +inline void add_varint_to_buffer(TBuffer* buffer, uint64_t value) { + while (value >= 0x80U) { + buffer_customization<TBuffer>::push_back(buffer, char((value & 0x7fU) | 0x80U)); + value >>= 7U; + } + buffer_customization<TBuffer>::push_back(buffer, char(value)); +} + +/** + * Varint encode a 64 bit integer. + * + * @param data Where to add the varint. There must be enough space available! + * @param value The integer that will be encoded. + * @returns the number of bytes written + */ +inline int add_varint_to_buffer(char* data, uint64_t value) noexcept { + int n = 1; + + while (value >= 0x80U) { + *data++ = char((value & 0x7fU) | 0x80U); + value >>= 7U; + ++n; + } + *data = char(value); + + return n; +} + +/** + * Get the length of the varint the specified value would produce. + * + * @param value The integer to be encoded. + * @returns the number of bytes the varint would have if we created it. + */ +inline int length_of_varint(uint64_t value) noexcept { + int n = 1; + + while (value >= 0x80U) { + value >>= 7U; + ++n; + } + + return n; +} + +/** + * ZigZag encodes a 32 bit integer. + */ +inline constexpr uint32_t encode_zigzag32(int32_t value) noexcept { + return (static_cast<uint32_t>(value) << 1U) ^ static_cast<uint32_t>(-static_cast<int32_t>(static_cast<uint32_t>(value) >> 31U)); +} + +/** + * ZigZag encodes a 64 bit integer. + */ +inline constexpr uint64_t encode_zigzag64(int64_t value) noexcept { + return (static_cast<uint64_t>(value) << 1U) ^ static_cast<uint64_t>(-static_cast<int64_t>(static_cast<uint64_t>(value) >> 63U)); +} + +/** + * Decodes a 32 bit ZigZag-encoded integer. + */ +inline constexpr int32_t decode_zigzag32(uint32_t value) noexcept { + return static_cast<int32_t>((value >> 1U) ^ static_cast<uint32_t>(-static_cast<int32_t>(value & 1U))); +} + +/** + * Decodes a 64 bit ZigZag-encoded integer. + */ +inline constexpr int64_t decode_zigzag64(uint64_t value) noexcept { + return static_cast<int64_t>((value >> 1U) ^ static_cast<uint64_t>(-static_cast<int64_t>(value & 1U))); +} + +} // end namespace protozero + +#endif // PROTOZERO_VARINT_HPP diff --git a/ext/protozero/include/protozero/version.hpp b/ext/protozero/include/protozero/version.hpp new file mode 100644 index 0000000..fc9b928 --- /dev/null +++ b/ext/protozero/include/protozero/version.hpp @@ -0,0 +1,34 @@ +#ifndef PROTOZERO_VERSION_HPP +#define PROTOZERO_VERSION_HPP + +/***************************************************************************** + +protozero - Minimalistic protocol buffer decoder and encoder in C++. + +This file is from https://github.com/mapbox/protozero where you can find more +documentation. + +*****************************************************************************/ + +/** + * @file version.hpp + * + * @brief Contains macros defining the protozero version. + */ + +/// The major version number +#define PROTOZERO_VERSION_MAJOR 1 + +/// The minor version number +#define PROTOZERO_VERSION_MINOR 7 + +/// The patch number +#define PROTOZERO_VERSION_PATCH 1 + +/// The complete version number +#define PROTOZERO_VERSION_CODE (PROTOZERO_VERSION_MAJOR * 10000 + PROTOZERO_VERSION_MINOR * 100 + PROTOZERO_VERSION_PATCH) + +/// Version number as string +#define PROTOZERO_VERSION_STRING "1.7.1" + +#endif // PROTOZERO_VERSION_HPP diff --git a/ext/yahttp/LICENSE b/ext/yahttp/LICENSE new file mode 100644 index 0000000..08f6f64 --- /dev/null +++ b/ext/yahttp/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Aki Tuomi + +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. diff --git a/ext/yahttp/Makefile.am b/ext/yahttp/Makefile.am new file mode 100644 index 0000000..aaaeee0 --- /dev/null +++ b/ext/yahttp/Makefile.am @@ -0,0 +1,3 @@ +SUBDIRS = yahttp + +EXTRA_DIST = LICENSE README.md diff --git a/ext/yahttp/Makefile.in b/ext/yahttp/Makefile.in new file mode 100644 index 0000000..e6e5ece --- /dev/null +++ b/ext/yahttp/Makefile.in @@ -0,0 +1,740 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = ext/yahttp +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_pthread_set_name.m4 \ + $(top_srcdir)/m4/ax_arg_default_enable_disable.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx_17.m4 \ + $(top_srcdir)/m4/ax_python_module.m4 $(top_srcdir)/m4/boost.m4 \ + $(top_srcdir)/m4/dnsdist_enable_dnscrypt.m4 \ + $(top_srcdir)/m4/dnsdist_enable_doh.m4 \ + $(top_srcdir)/m4/dnsdist_enable_tls_providers.m4 \ + $(top_srcdir)/m4/dnsdist_with_cdb.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 \ + $(top_srcdir)/m4/pdns_check_clock_gettime.m4 \ + $(top_srcdir)/m4/pdns_check_dnstap.m4 \ + $(top_srcdir)/m4/pdns_check_libcrypto.m4 \ + $(top_srcdir)/m4/pdns_check_libedit.m4 \ + $(top_srcdir)/m4/pdns_check_libh2o_evloop.m4 \ + $(top_srcdir)/m4/pdns_check_lmdb.m4 \ + $(top_srcdir)/m4/pdns_check_lua_hpp.m4 \ + $(top_srcdir)/m4/pdns_check_network_libs.m4 \ + $(top_srcdir)/m4/pdns_check_os.m4 \ + $(top_srcdir)/m4/pdns_check_pthread_np.m4 \ + $(top_srcdir)/m4/pdns_check_python_venv.m4 \ + $(top_srcdir)/m4/pdns_check_ragel.m4 \ + $(top_srcdir)/m4/pdns_check_secure_memset.m4 \ + $(top_srcdir)/m4/pdns_d_fortify_source.m4 \ + $(top_srcdir)/m4/pdns_enable_ipcipher.m4 \ + $(top_srcdir)/m4/pdns_enable_lto.m4 \ + $(top_srcdir)/m4/pdns_enable_sanitizers.m4 \ + $(top_srcdir)/m4/pdns_enable_tls.m4 \ + $(top_srcdir)/m4/pdns_enable_unit_tests.m4 \ + $(top_srcdir)/m4/pdns_init_auto_vars.m4 \ + $(top_srcdir)/m4/pdns_param_ssp_buffer_size.m4 \ + $(top_srcdir)/m4/pdns_pie.m4 $(top_srcdir)/m4/pdns_relro.m4 \ + $(top_srcdir)/m4/pdns_stack_protector.m4 \ + $(top_srcdir)/m4/pdns_with_ebpf.m4 \ + $(top_srcdir)/m4/pdns_with_gnutls.m4 \ + $(top_srcdir)/m4/pdns_with_libcap.m4 \ + $(top_srcdir)/m4/pdns_with_libsodium.m4 \ + $(top_srcdir)/m4/pdns_with_libssl.m4 \ + $(top_srcdir)/m4/pdns_with_lua.m4 \ + $(top_srcdir)/m4/pdns_with_net_snmp.m4 \ + $(top_srcdir)/m4/pdns_with_nghttp2.m4 \ + $(top_srcdir)/m4/pdns_with_re2.m4 \ + $(top_srcdir)/m4/pdns_with_service_user.m4 \ + $(top_srcdir)/m4/systemd.m4 $(top_srcdir)/m4/warnings.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +SOURCES = +DIST_SOURCES = +RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \ + ctags-recursive dvi-recursive html-recursive info-recursive \ + install-data-recursive install-dvi-recursive \ + install-exec-recursive install-html-recursive \ + install-info-recursive install-pdf-recursive \ + install-ps-recursive install-recursive installcheck-recursive \ + installdirs-recursive pdf-recursive ps-recursive \ + tags-recursive uninstall-recursive +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ + distclean-recursive maintainer-clean-recursive +am__recursive_targets = \ + $(RECURSIVE_TARGETS) \ + $(RECURSIVE_CLEAN_TARGETS) \ + $(am__extra_recursive_targets) +AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \ + distdir distdir-am +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +DIST_SUBDIRS = $(SUBDIRS) +am__DIST_COMMON = $(srcdir)/Makefile.in +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +am__relativize = \ + dir0=`pwd`; \ + sed_first='s,^\([^/]*\)/.*$$,\1,'; \ + sed_rest='s,^[^/]*/*,,'; \ + sed_last='s,^.*/\([^/]*\)$$,\1,'; \ + sed_butlast='s,/*[^/]*$$,,'; \ + while test -n "$$dir1"; do \ + first=`echo "$$dir1" | sed -e "$$sed_first"`; \ + if test "$$first" != "."; then \ + if test "$$first" = ".."; then \ + dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ + dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ + else \ + first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ + if test "$$first2" = "$$first"; then \ + dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ + else \ + dir2="../$$dir2"; \ + fi; \ + dir0="$$dir0"/"$$first"; \ + fi; \ + fi; \ + dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ + done; \ + reldir="$$dir2" +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_CPPFLAGS = @AM_CPPFLAGS@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_CPPFLAGS = @BOOST_CPPFLAGS@ +BOOST_LDPATH = @BOOST_LDPATH@ +BOOST_ROOT = @BOOST_ROOT@ +BOOST_UNIT_TEST_FRAMEWORK_LDFLAGS = @BOOST_UNIT_TEST_FRAMEWORK_LDFLAGS@ +BOOST_UNIT_TEST_FRAMEWORK_LDPATH = @BOOST_UNIT_TEST_FRAMEWORK_LDPATH@ +BOOST_UNIT_TEST_FRAMEWORK_LIBS = @BOOST_UNIT_TEST_FRAMEWORK_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_CFLAGS = @CDB_CFLAGS@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +DYNLINKFLAGS = @DYNLINKFLAGS@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FSTRM_CFLAGS = @FSTRM_CFLAGS@ +FSTRM_LIBS = @FSTRM_LIBS@ +GNUTLS_CFLAGS = @GNUTLS_CFLAGS@ +GNUTLS_LIBS = @GNUTLS_LIBS@ +GREP = @GREP@ +HAVE_CXX17 = @HAVE_CXX17@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +IPCRYPT_CFLAGS = @IPCRYPT_CFLAGS@ +IPCRYPT_LIBS = @IPCRYPT_LIBS@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBCAP_CFLAGS = @LIBCAP_CFLAGS@ +LIBCAP_LIBS = @LIBCAP_LIBS@ +LIBCRYPTO_INCLUDES = @LIBCRYPTO_INCLUDES@ +LIBCRYPTO_LDFLAGS = @LIBCRYPTO_LDFLAGS@ +LIBCRYPTO_LIBS = @LIBCRYPTO_LIBS@ +LIBEDIT_CFLAGS = @LIBEDIT_CFLAGS@ +LIBEDIT_LIBS = @LIBEDIT_LIBS@ +LIBH2OEVLOOP_CFLAGS = @LIBH2OEVLOOP_CFLAGS@ +LIBH2OEVLOOP_LIBS = @LIBH2OEVLOOP_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBSSL_CFLAGS = @LIBSSL_CFLAGS@ +LIBSSL_LIBS = @LIBSSL_LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LMDB_CFLAGS = @LMDB_CFLAGS@ +LMDB_LIBS = @LMDB_LIBS@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NET_SNMP_LIBS = @NET_SNMP_LIBS@ +NGHTTP2_CFLAGS = @NGHTTP2_CFLAGS@ +NGHTTP2_LIBS = @NGHTTP2_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGEVERSION = @PACKAGEVERSION@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +PROGRAM_LDFLAGS = @PROGRAM_LDFLAGS@ +PYTHON = @PYTHON@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_VERSION = @PYTHON_VERSION@ +RAGEL = @RAGEL@ +RANLIB = @RANLIB@ +RE2_CFLAGS = @RE2_CFLAGS@ +RE2_LIBS = @RE2_LIBS@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RT_LIBS = @RT_LIBS@ +SANITIZER_FLAGS = @SANITIZER_FLAGS@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYSTEMCTL = @SYSTEMCTL@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_DIR = @SYSTEMD_DIR@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +SYSTEMD_MODULES_LOAD = @SYSTEMD_MODULES_LOAD@ +THREADFLAGS = @THREADFLAGS@ +VERSION = @VERSION@ +WARN_CFLAGS = @WARN_CFLAGS@ +YAHTTP_CFLAGS = @YAHTTP_CFLAGS@ +YAHTTP_LIBS = @YAHTTP_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +service_group = @service_group@ +service_user = @service_user@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +systemd = @systemd@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +SUBDIRS = yahttp +EXTRA_DIST = LICENSE README.md +all: all-recursive + +.SUFFIXES: +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign ext/yahttp/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign ext/yahttp/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +# This directory's subdirectories are mostly independent; you can cd +# into them and run 'make' without going through this Makefile. +# To change the values of 'make' variables: instead of editing Makefiles, +# (1) if the variable is set in 'config.status', edit 'config.status' +# (which will cause the Makefiles to be regenerated when you run 'make'); +# (2) otherwise, pass the desired values on the 'make' command line. +$(am__recursive_targets): + @fail=; \ + if $(am__make_keepgoing); then \ + failcom='fail=yes'; \ + else \ + failcom='exit 1'; \ + fi; \ + dot_seen=no; \ + target=`echo $@ | sed s/-recursive//`; \ + case "$@" in \ + distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ + *) list='$(SUBDIRS)' ;; \ + esac; \ + for subdir in $$list; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + dot_seen=yes; \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done; \ + if test "$$dot_seen" = "no"; then \ + $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ + fi; test -z "$$fail" + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-recursive +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ + include_option=--etags-include; \ + empty_fix=.; \ + else \ + include_option=--include; \ + empty_fix=; \ + fi; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test ! -f $$subdir/TAGS || \ + set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ + fi; \ + done; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-recursive + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-recursive + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + $(am__make_dryrun) \ + || test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ + dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ + $(am__relativize); \ + new_distdir=$$reldir; \ + dir1=$$subdir; dir2="$(top_distdir)"; \ + $(am__relativize); \ + new_top_distdir=$$reldir; \ + echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ + echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ + ($(am__cd) $$subdir && \ + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$$new_top_distdir" \ + distdir="$$new_distdir" \ + am__remove_distdir=: \ + am__skip_length_check=: \ + am__skip_mode_fix=: \ + distdir) \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-recursive +all-am: Makefile +installdirs: installdirs-recursive +installdirs-am: +install: install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-recursive + +clean-am: clean-generic clean-libtool mostlyclean-am + +distclean: distclean-recursive + -rm -f Makefile +distclean-am: clean-am distclean-generic distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +html-am: + +info: info-recursive + +info-am: + +install-data-am: + +install-dvi: install-dvi-recursive + +install-dvi-am: + +install-exec-am: + +install-html: install-html-recursive + +install-html-am: + +install-info: install-info-recursive + +install-info-am: + +install-man: + +install-pdf: install-pdf-recursive + +install-pdf-am: + +install-ps: install-ps-recursive + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-generic mostlyclean-libtool + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +uninstall-am: + +.MAKE: $(am__recursive_targets) install-am install-strip + +.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \ + check-am clean clean-generic clean-libtool cscopelist-am ctags \ + ctags-am distclean distclean-generic distclean-libtool \ + distclean-tags distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + installdirs-am maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-generic mostlyclean-libtool pdf pdf-am \ + ps ps-am tags tags-am uninstall uninstall-am + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/ext/yahttp/README.md b/ext/yahttp/README.md new file mode 100644 index 0000000..86794b5 --- /dev/null +++ b/ext/yahttp/README.md @@ -0,0 +1,69 @@ +Yet Another HTTP Library +======================== + +YaHTTP aims to be a pure http request/response parser that has no IO ties. It is intended to be used on small-footprint applications and other utilities that want to use HTTP over something else than network IO. + +[![Build Status](https://travis-ci.org/cmouse/yahttp.svg?branch=master)](https://travis-ci.org/cmouse/yahttp) +[![Coverity Scan Build Status](https://scan.coverity.com/projects/2161/badge.svg)](https://scan.coverity.com/projects/2161) +[![Coverage Status](https://coveralls.io/repos/github/cmouse/yahttp/badge.svg)](https://coveralls.io/github/cmouse/yahttp) + +WARNINGS +-------- +If you are upgrading from version before May 02, 2014 - *PLEASE BE SURE TO CHECK THAT EVERYTHING WORKS AS EXPECTED*. There has been complete overhaul of the library and many things have changed. + +NOTES +----- +Do not use resp = req, or resp(req) to build the response object, despite it being supported. This will cause request headers to get duplicated. Also, you *must* set response#version to request#version if you intend to support older than HTTP/1.1 clients. Set response#status to at least 200, it won't be done for you. No Server or Product token is sent either, you can add those if you want. + +If you do not want to send chunked responses, set content-length header. Setting this header will always disable chunked responses. This will also happen if you downgrade your responses to version 10 or 9. + +Integration guide +----------------- + +Here are some instructions on how to integrate YaHTTP into your project. + +With automake and libtool +------------------------- + +If you don't need router or any of the C++11 features, you can just create empty yahttp-config.h, or symlink it to your project's config.h for the yahttp.hpp to include. Then just put this stuff into it's own folder and create Makefile.am with following contents (you can change the compilation flags): + +``` +noinst_LTLIBRARIES=libyahttp.la +libyahttp_la_CXXFLAGS=$(RELRO_CFLAGS) $(PIE_CFLAGS) -D__STRICT_ANSI__ +libyahttp_la_SOURCES=cookie.hpp exception.hpp reqresp.cpp reqresp.hpp router.cpp router.hpp url.hpp utility.hpp yahttp.hpp +``` + +You can define RELRO and PIE to match your project. + +To compile your project use -Lpath/to/yahttp -lyahttp + +If you need router, additionally check for boost or C++11 and replace yahttp-config.h to config.h in yahttp.hpp or add relevant options to your compiler CXXFLAGS. See below for the flags. + +Without automake +---------------- + +Create simple Makefile with contents for C++11: + +``` +OBJECTS=reqresp.o router.o +CXX=gcc +CXXFLAGS=-W -Wall -DHAVE_CXX11 -std=c++11 +``` + +Or create simple Makefile with contents for boost: + +``` +OBJECTS=reqresp.o router.o +CXX=gcc +CXXFLAGS=-W -Wall -DHAVE_BOOST +``` + +Or if you don't need either one, just: + +``` +OBJECTS=reqresp.o +CXX=gcc +CXXFLAGS=-W -Wall +``` + +YaHTTP include files can be placed where the rest of your includes are. Then just add your own code there and it should work just fine. diff --git a/ext/yahttp/yahttp/Makefile.am b/ext/yahttp/yahttp/Makefile.am new file mode 100644 index 0000000..3ed41e5 --- /dev/null +++ b/ext/yahttp/yahttp/Makefile.am @@ -0,0 +1,13 @@ +noinst_LTLIBRARIES = libyahttp.la + +libyahttp_la_SOURCES = \ + cookie.hpp \ + exception.hpp \ + reqresp.cpp \ + reqresp.hpp \ + router.cpp \ + router.hpp \ + url.hpp \ + utility.hpp \ + yahttp-config.h \ + yahttp.hpp diff --git a/ext/yahttp/yahttp/Makefile.in b/ext/yahttp/yahttp/Makefile.in new file mode 100644 index 0000000..ac4f348 --- /dev/null +++ b/ext/yahttp/yahttp/Makefile.in @@ -0,0 +1,748 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = ext/yahttp/yahttp +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_pthread_set_name.m4 \ + $(top_srcdir)/m4/ax_arg_default_enable_disable.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx_17.m4 \ + $(top_srcdir)/m4/ax_python_module.m4 $(top_srcdir)/m4/boost.m4 \ + $(top_srcdir)/m4/dnsdist_enable_dnscrypt.m4 \ + $(top_srcdir)/m4/dnsdist_enable_doh.m4 \ + $(top_srcdir)/m4/dnsdist_enable_tls_providers.m4 \ + $(top_srcdir)/m4/dnsdist_with_cdb.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 \ + $(top_srcdir)/m4/pdns_check_clock_gettime.m4 \ + $(top_srcdir)/m4/pdns_check_dnstap.m4 \ + $(top_srcdir)/m4/pdns_check_libcrypto.m4 \ + $(top_srcdir)/m4/pdns_check_libedit.m4 \ + $(top_srcdir)/m4/pdns_check_libh2o_evloop.m4 \ + $(top_srcdir)/m4/pdns_check_lmdb.m4 \ + $(top_srcdir)/m4/pdns_check_lua_hpp.m4 \ + $(top_srcdir)/m4/pdns_check_network_libs.m4 \ + $(top_srcdir)/m4/pdns_check_os.m4 \ + $(top_srcdir)/m4/pdns_check_pthread_np.m4 \ + $(top_srcdir)/m4/pdns_check_python_venv.m4 \ + $(top_srcdir)/m4/pdns_check_ragel.m4 \ + $(top_srcdir)/m4/pdns_check_secure_memset.m4 \ + $(top_srcdir)/m4/pdns_d_fortify_source.m4 \ + $(top_srcdir)/m4/pdns_enable_ipcipher.m4 \ + $(top_srcdir)/m4/pdns_enable_lto.m4 \ + $(top_srcdir)/m4/pdns_enable_sanitizers.m4 \ + $(top_srcdir)/m4/pdns_enable_tls.m4 \ + $(top_srcdir)/m4/pdns_enable_unit_tests.m4 \ + $(top_srcdir)/m4/pdns_init_auto_vars.m4 \ + $(top_srcdir)/m4/pdns_param_ssp_buffer_size.m4 \ + $(top_srcdir)/m4/pdns_pie.m4 $(top_srcdir)/m4/pdns_relro.m4 \ + $(top_srcdir)/m4/pdns_stack_protector.m4 \ + $(top_srcdir)/m4/pdns_with_ebpf.m4 \ + $(top_srcdir)/m4/pdns_with_gnutls.m4 \ + $(top_srcdir)/m4/pdns_with_libcap.m4 \ + $(top_srcdir)/m4/pdns_with_libsodium.m4 \ + $(top_srcdir)/m4/pdns_with_libssl.m4 \ + $(top_srcdir)/m4/pdns_with_lua.m4 \ + $(top_srcdir)/m4/pdns_with_net_snmp.m4 \ + $(top_srcdir)/m4/pdns_with_nghttp2.m4 \ + $(top_srcdir)/m4/pdns_with_re2.m4 \ + $(top_srcdir)/m4/pdns_with_service_user.m4 \ + $(top_srcdir)/m4/systemd.m4 $(top_srcdir)/m4/warnings.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +LTLIBRARIES = $(noinst_LTLIBRARIES) +libyahttp_la_LIBADD = +am_libyahttp_la_OBJECTS = reqresp.lo router.lo +libyahttp_la_OBJECTS = $(am_libyahttp_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/reqresp.Plo ./$(DEPDIR)/router.Plo +am__mv = mv -f +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CXXFLAGS) $(CXXFLAGS) +AM_V_CXX = $(am__v_CXX_@AM_V@) +am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@) +am__v_CXX_0 = @echo " CXX " $@; +am__v_CXX_1 = +CXXLD = $(CXX) +CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ + $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CXXLD = $(am__v_CXXLD_@AM_V@) +am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@) +am__v_CXXLD_0 = @echo " CXXLD " $@; +am__v_CXXLD_1 = +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(libyahttp_la_SOURCES) +DIST_SOURCES = $(libyahttp_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_CPPFLAGS = @AM_CPPFLAGS@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_CPPFLAGS = @BOOST_CPPFLAGS@ +BOOST_LDPATH = @BOOST_LDPATH@ +BOOST_ROOT = @BOOST_ROOT@ +BOOST_UNIT_TEST_FRAMEWORK_LDFLAGS = @BOOST_UNIT_TEST_FRAMEWORK_LDFLAGS@ +BOOST_UNIT_TEST_FRAMEWORK_LDPATH = @BOOST_UNIT_TEST_FRAMEWORK_LDPATH@ +BOOST_UNIT_TEST_FRAMEWORK_LIBS = @BOOST_UNIT_TEST_FRAMEWORK_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_CFLAGS = @CDB_CFLAGS@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +DYNLINKFLAGS = @DYNLINKFLAGS@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FSTRM_CFLAGS = @FSTRM_CFLAGS@ +FSTRM_LIBS = @FSTRM_LIBS@ +GNUTLS_CFLAGS = @GNUTLS_CFLAGS@ +GNUTLS_LIBS = @GNUTLS_LIBS@ +GREP = @GREP@ +HAVE_CXX17 = @HAVE_CXX17@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +IPCRYPT_CFLAGS = @IPCRYPT_CFLAGS@ +IPCRYPT_LIBS = @IPCRYPT_LIBS@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBCAP_CFLAGS = @LIBCAP_CFLAGS@ +LIBCAP_LIBS = @LIBCAP_LIBS@ +LIBCRYPTO_INCLUDES = @LIBCRYPTO_INCLUDES@ +LIBCRYPTO_LDFLAGS = @LIBCRYPTO_LDFLAGS@ +LIBCRYPTO_LIBS = @LIBCRYPTO_LIBS@ +LIBEDIT_CFLAGS = @LIBEDIT_CFLAGS@ +LIBEDIT_LIBS = @LIBEDIT_LIBS@ +LIBH2OEVLOOP_CFLAGS = @LIBH2OEVLOOP_CFLAGS@ +LIBH2OEVLOOP_LIBS = @LIBH2OEVLOOP_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBSSL_CFLAGS = @LIBSSL_CFLAGS@ +LIBSSL_LIBS = @LIBSSL_LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LMDB_CFLAGS = @LMDB_CFLAGS@ +LMDB_LIBS = @LMDB_LIBS@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NET_SNMP_LIBS = @NET_SNMP_LIBS@ +NGHTTP2_CFLAGS = @NGHTTP2_CFLAGS@ +NGHTTP2_LIBS = @NGHTTP2_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGEVERSION = @PACKAGEVERSION@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +PROGRAM_LDFLAGS = @PROGRAM_LDFLAGS@ +PYTHON = @PYTHON@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_VERSION = @PYTHON_VERSION@ +RAGEL = @RAGEL@ +RANLIB = @RANLIB@ +RE2_CFLAGS = @RE2_CFLAGS@ +RE2_LIBS = @RE2_LIBS@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RT_LIBS = @RT_LIBS@ +SANITIZER_FLAGS = @SANITIZER_FLAGS@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYSTEMCTL = @SYSTEMCTL@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_DIR = @SYSTEMD_DIR@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +SYSTEMD_MODULES_LOAD = @SYSTEMD_MODULES_LOAD@ +THREADFLAGS = @THREADFLAGS@ +VERSION = @VERSION@ +WARN_CFLAGS = @WARN_CFLAGS@ +YAHTTP_CFLAGS = @YAHTTP_CFLAGS@ +YAHTTP_LIBS = @YAHTTP_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +service_group = @service_group@ +service_user = @service_user@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +systemd = @systemd@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +noinst_LTLIBRARIES = libyahttp.la +libyahttp_la_SOURCES = \ + cookie.hpp \ + exception.hpp \ + reqresp.cpp \ + reqresp.hpp \ + router.cpp \ + router.hpp \ + url.hpp \ + utility.hpp \ + yahttp-config.h \ + yahttp.hpp + +all: all-am + +.SUFFIXES: +.SUFFIXES: .cpp .lo .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign ext/yahttp/yahttp/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign ext/yahttp/yahttp/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstLTLIBRARIES: + -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) + @list='$(noinst_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libyahttp.la: $(libyahttp_la_OBJECTS) $(libyahttp_la_DEPENDENCIES) $(EXTRA_libyahttp_la_DEPENDENCIES) + $(AM_V_CXXLD)$(CXXLINK) $(libyahttp_la_OBJECTS) $(libyahttp_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/reqresp.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/router.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.cpp.o: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ +@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $< + +.cpp.obj: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ +@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ +@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.cpp.lo: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ +@am__fastdepCXX_TRUE@ $(LTCXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/reqresp.Plo + -rm -f ./$(DEPDIR)/router.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/reqresp.Plo + -rm -f ./$(DEPDIR)/router.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-noinstLTLIBRARIES \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/ext/yahttp/yahttp/cookie.hpp b/ext/yahttp/yahttp/cookie.hpp new file mode 100644 index 0000000..aa5359b --- /dev/null +++ b/ext/yahttp/yahttp/cookie.hpp @@ -0,0 +1,144 @@ +namespace YaHTTP { + /*! Implements a single cookie */ + class Cookie { + public: + Cookie() { + secure = false; + httponly = false; + name = value = ""; + expires = DateTime(); + }; //!< Set the cookie to empty value + + Cookie(const Cookie &rhs) { + name = rhs.name; + value = rhs.value; + domain = rhs.domain; + path = rhs.path; + secure = rhs.secure; + httponly = rhs.httponly; + expires = rhs.expires; + }; //<! Copy cookie values + + Cookie& operator=(const Cookie &rhs) { + name = rhs.name; + value = rhs.value; + domain = rhs.domain; + path = rhs.path; + secure = rhs.secure; + httponly = rhs.httponly; + expires = rhs.expires; + return *this; + } + + DateTime expires; /*!< Expiration date */ + std::string domain; /*!< Domain where cookie is valid */ + std::string path; /*!< Path where the cookie is valid */ + bool httponly; /*!< Whether the cookie is for server use only */ + bool secure; /*!< Whether the cookie is for HTTPS only */ + + std::string name; /*!< Cookie name */ + std::string value; /*!< Cookie value */ + + std::string str() const { + std::ostringstream oss; + oss << YaHTTP::Utility::encodeURL(name) << "=" << YaHTTP::Utility::encodeURL(value); + + if (expires.isSet) + oss << "; expires=" << expires.cookie_str(); + if (domain.size()>0) + oss << "; domain=" << domain; + if (path.size()>0) + oss << "; path=" << path; + if (secure) + oss << "; secure"; + if (httponly) + oss << "; httpOnly"; + return oss.str(); + }; //!< Stringify the cookie + }; + + /*! Implements a Cookie jar for storing multiple cookies */ + class CookieJar { + public: + std::map<std::string, Cookie, ASCIICINullSafeComparator> cookies; //<! cookie container + + CookieJar() {}; //<! constructs empty cookie jar + CookieJar(const CookieJar & rhs) { + this->cookies = rhs.cookies; + } //<! copy cookies from another cookie jar + CookieJar& operator=(const CookieJar & rhs) = default; + + void clear() { + this->cookies.clear(); + } + + void keyValuePair(const std::string &keyvalue, std::string &key, std::string &value) { + size_t pos; + pos = keyvalue.find("="); + if (pos == std::string::npos) throw ParseError("Not a Key-Value pair (cookie)"); + key = std::string(keyvalue.begin(), keyvalue.begin()+pos); + value = std::string(keyvalue.begin()+pos+1, keyvalue.end()); + } //<! key value pair parser + + void parseCookieHeader(const std::string &cookiestr) { + size_t pos, npos; + std::list<Cookie> lcookies; + Cookie c; + pos = 0; + while(pos < cookiestr.size()) { + if ((npos = cookiestr.find("; ", pos)) == std::string::npos) + npos = cookiestr.size(); + keyValuePair(cookiestr.substr(pos, npos-pos), c.name, c.value); + c.name = YaHTTP::Utility::decodeURL(c.name); + c.value = YaHTTP::Utility::decodeURL(c.value); + lcookies.push_back(c); + pos = npos+2; + } + for(std::list<Cookie>::iterator i = lcookies.begin(); i != lcookies.end(); i++) { + this->cookies[i->name] = *i; + } + } + + void parseSetCookieHeader(const std::string &cookiestr) { + Cookie c; + size_t pos,npos; + std::string k, v; + + if ((pos = cookiestr.find("; ", 0)) == std::string::npos) + pos = cookiestr.size(); + keyValuePair(cookiestr.substr(0, pos), c.name, c.value); + c.name = YaHTTP::Utility::decodeURL(c.name); + c.value = YaHTTP::Utility::decodeURL(c.value); + if (pos < cookiestr.size()) pos+=2; + + while(pos < cookiestr.size()) { + if ((npos = cookiestr.find("; ", pos)) == std::string::npos) + npos = cookiestr.size(); + std::string s = cookiestr.substr(pos, npos-pos); + if (s.find("=") != std::string::npos) + keyValuePair(s, k, v); + else + k = s; + if (k == "expires") { + DateTime dt; + dt.parseCookie(v); + c.expires = dt; + } else if (k == "domain") { + c.domain = v; + } else if (k == "path") { + c.path = v; + } else if (k == "httpOnly") { + c.httponly = true; + } else if (k == "secure") { + c.secure = true; + } else { + // ignore crap + break; + } + pos = npos+2; + } + + this->cookies[c.name] = c; + }; //<! Parse multiple cookies from header + }; +}; diff --git a/ext/yahttp/yahttp/exception.hpp b/ext/yahttp/yahttp/exception.hpp new file mode 100644 index 0000000..d0ea0fe --- /dev/null +++ b/ext/yahttp/yahttp/exception.hpp @@ -0,0 +1,24 @@ +#pragma once +#include <exception> + +namespace YaHTTP { + /*! Generic error class */ + class Error: public std::exception { + public: + Error() {}; + Error(const std::string& reason_): reason(reason_) {}; + virtual ~Error() throw() {}; + + virtual const char* what() const throw() + { + return reason.c_str(); + } + const std::string reason; //<! Cause of the error + }; + /*! Parse error class */ + class ParseError: public YaHTTP::Error { + public: + ParseError() {}; + ParseError(const std::string& reason_): Error(reason_) {}; + }; +}; diff --git a/ext/yahttp/yahttp/reqresp.cpp b/ext/yahttp/yahttp/reqresp.cpp new file mode 100644 index 0000000..e5f9c95 --- /dev/null +++ b/ext/yahttp/yahttp/reqresp.cpp @@ -0,0 +1,336 @@ +#include "yahttp.hpp" + +#include <limits> + +namespace YaHTTP { + + template class AsyncLoader<Request>; + template class AsyncLoader<Response>; + + bool isspace(char c) { + return std::isspace(c) != 0; + } + + bool isspace(char c, const std::locale& loc) { + return std::isspace(c, loc); + } + + bool isxdigit(char c) { + return std::isxdigit(c) != 0; + } + + bool isxdigit(char c, const std::locale& loc) { + return std::isxdigit(c, loc); + } + + bool isdigit(char c) { + return std::isdigit(c) != 0; + } + + bool isdigit(char c, const std::locale& loc) { + return std::isdigit(c, loc); + } + + bool isalnum(char c) { + return std::isalnum(c) != 0; + } + + bool isalnum(char c, const std::locale& loc) { + return std::isalnum(c, loc); + } + + template <class T> + bool AsyncLoader<T>::feed(const std::string& somedata) { + buffer.append(somedata); + while(state < 2) { + int cr=0; + pos = buffer.find_first_of("\n"); + // need to find CRLF in buffer + if (pos == std::string::npos) return false; + if (pos>0 && buffer[pos-1]=='\r') + cr=1; + std::string line(buffer.begin(), buffer.begin()+pos-cr); // exclude CRLF + buffer.erase(buffer.begin(), buffer.begin()+pos+1); // remove line from buffer including CRLF + + if (state == 0) { // startup line + if (target->kind == YAHTTP_TYPE_REQUEST) { + std::string ver; + std::string tmpurl; + std::istringstream iss(line); + iss >> target->method >> tmpurl >> ver; + if (ver.size() == 0) + target->version = 9; + else if (ver.find("HTTP/0.9") == 0) + target->version = 9; + else if (ver.find("HTTP/1.0") == 0) + target->version = 10; + else if (ver.find("HTTP/1.1") == 0) + target->version = 11; + else + throw ParseError("HTTP version not supported"); + // uppercase the target method + std::transform(target->method.begin(), target->method.end(), target->method.begin(), ::toupper); + target->url.parse(tmpurl); + target->getvars = Utility::parseUrlParameters(target->url.parameters); + state = 1; + } else if(target->kind == YAHTTP_TYPE_RESPONSE) { + std::string ver; + std::istringstream iss(line); + std::string::size_type pos1; + iss >> ver >> target->status; + std::getline(iss, target->statusText); + pos1=0; + while(pos1 < target->statusText.size() && YaHTTP::isspace(target->statusText.at(pos1))) pos1++; + target->statusText = target->statusText.substr(pos1); + if ((pos1 = target->statusText.find("\r")) != std::string::npos) { + target->statusText = target->statusText.substr(0, pos1-1); + } + if (ver.size() == 0) { + target->version = 9; + } else if (ver.find("HTTP/0.9") == 0) + target->version = 9; + else if (ver.find("HTTP/1.0") == 0) + target->version = 10; + else if (ver.find("HTTP/1.1") == 0) + target->version = 11; + else + throw ParseError("HTTP version not supported"); + state = 1; + } + } else if (state == 1) { + std::string key,value; + size_t pos1; + if (line.empty()) { + chunked = (target->headers.find("transfer-encoding") != target->headers.end() && target->headers["transfer-encoding"] == "chunked"); + state = 2; + break; + } + // split headers + if ((pos1 = line.find(":")) == std::string::npos) { + throw ParseError("Malformed header line"); + } + key = line.substr(0, pos1); + value = line.substr(pos1 + 1); + for(std::string::iterator it=key.begin(); it != key.end(); it++) + if (YaHTTP::isspace(*it)) + throw ParseError("Header key contains whitespace which is not allowed by RFC"); + + Utility::trim(value); + std::transform(key.begin(), key.end(), key.begin(), ::tolower); + // is it already defined + + if (key == "set-cookie" && target->kind == YAHTTP_TYPE_RESPONSE) { + target->jar.parseSetCookieHeader(value); + } else if (key == "cookie" && target->kind == YAHTTP_TYPE_REQUEST) { + target->jar.parseCookieHeader(value); + } else { + if (key == "host" && target->kind == YAHTTP_TYPE_REQUEST) { + // maybe it contains port? + if ((pos1 = value.find(":")) == std::string::npos) { + target->url.host = value; + } else { + target->url.host = value.substr(0, pos1); + target->url.port = ::atoi(value.substr(pos1).c_str()); + } + } + if (target->headers.find(key) != target->headers.end()) { + target->headers[key] = target->headers[key] + ";" + value; + } else { + target->headers[key] = value; + } + } + } + } + + minbody = 0; + // check for expected body size + if (target->kind == YAHTTP_TYPE_REQUEST) maxbody = target->max_request_size; + else if (target->kind == YAHTTP_TYPE_RESPONSE) maxbody = target->max_response_size; + else maxbody = 0; + + if (!chunked) { + if (target->headers.find("content-length") != target->headers.end()) { + std::istringstream maxbodyS(target->headers["content-length"]); + maxbodyS >> minbody; + maxbody = minbody; + } + if (minbody < 1) return true; // guess there isn't anything left. + if (target->kind == YAHTTP_TYPE_REQUEST && static_cast<ssize_t>(minbody) > target->max_request_size) throw ParseError("Max request body size exceeded"); + else if (target->kind == YAHTTP_TYPE_RESPONSE && static_cast<ssize_t>(minbody) > target->max_response_size) throw ParseError("Max response body size exceeded"); + } + + if (maxbody == 0) hasBody = false; + else hasBody = true; + + if (buffer.size() == 0) return ready(); + + while(buffer.size() > 0) { + if (chunked) { + if (chunk_size == 0) { + char buf[100]; + // read chunk length + if ((pos = buffer.find('\n')) == std::string::npos) return false; + if (pos > 99) + throw ParseError("Impossible chunk_size"); + buffer.copy(buf, pos); + buf[pos]=0; // just in case... + buffer.erase(buffer.begin(), buffer.begin()+pos+1); // remove line from buffer + if (sscanf(buf, "%x", &chunk_size) != 1) { + throw ParseError("Unable to parse chunk size"); + } + if (chunk_size == 0) { state = 3; break; } // last chunk + if (chunk_size > (std::numeric_limits<decltype(chunk_size)>::max() - 2)) { + throw ParseError("Chunk is too large"); + } + } else { + int crlf=1; + if (buffer.size() < static_cast<size_t>(chunk_size+1)) return false; // expect newline + if (buffer.at(chunk_size) == '\r') { + if (buffer.size() < static_cast<size_t>(chunk_size+2) || buffer.at(chunk_size+1) != '\n') return false; // expect newline after carriage return + crlf=2; + } else if (buffer.at(chunk_size) != '\n') return false; + std::string tmp = buffer.substr(0, chunk_size); + buffer.erase(buffer.begin(), buffer.begin()+chunk_size+crlf); + bodybuf << tmp; + chunk_size = 0; + if (buffer.size() == 0) break; // just in case + } + } else { + if (bodybuf.str().length() + buffer.length() > maxbody) + bodybuf << buffer.substr(0, maxbody - bodybuf.str().length()); + else + bodybuf << buffer; + buffer = ""; + } + } + + if (chunk_size!=0) return false; // need more data + + return ready(); + }; + + void HTTPBase::write(std::ostream& os) const { + if (kind == YAHTTP_TYPE_REQUEST) { + std::ostringstream getparmbuf; + std::string getparms; + // prepare URL + for(strstr_map_t::const_iterator i = getvars.begin(); i != getvars.end(); i++) { + getparmbuf << Utility::encodeURL(i->first, false) << "=" << Utility::encodeURL(i->second, false) << "&"; + } + if (getparmbuf.str().length() > 0) { + std::string buf = getparmbuf.str(); + getparms = "?" + std::string(buf.begin(), buf.end() - 1); + } + else + getparms = ""; + os << method << " " << url.path << getparms << " HTTP/" << versionStr(this->version); + } else if (kind == YAHTTP_TYPE_RESPONSE) { + os << "HTTP/" << versionStr(this->version) << " " << status << " "; + if (statusText.empty()) + os << Utility::status2text(status); + else + os << statusText; + } + os << "\r\n"; + + bool cookieSent = false; + bool sendChunked = false; + + if (this->version > 10) { // 1.1 or better + if (headers.find("content-length") == headers.end() && !this->is_multipart) { + // must use chunked on response + sendChunked = (kind == YAHTTP_TYPE_RESPONSE); + if ((headers.find("transfer-encoding") != headers.end() && headers.find("transfer-encoding")->second != "chunked")) { + throw YaHTTP::Error("Transfer-encoding must be chunked, or Content-Length defined"); + } + if ((headers.find("transfer-encoding") == headers.end() && kind == YAHTTP_TYPE_RESPONSE)) { + sendChunked = true; + os << "Transfer-Encoding: chunked\r\n"; + } + } else { + sendChunked = false; + } + } + + // write headers + strstr_map_t::const_iterator iter = headers.begin(); + while(iter != headers.end()) { + if (iter->first == "host" && (kind != YAHTTP_TYPE_REQUEST || version < 10)) { iter++; continue; } + if (iter->first == "transfer-encoding" && sendChunked) { iter++; continue; } + std::string header = Utility::camelizeHeader(iter->first); + if (header == "Cookie" || header == "Set-Cookie") cookieSent = true; + os << Utility::camelizeHeader(iter->first) << ": " << iter->second << "\r\n"; + iter++; + } + if (version > 9 && !cookieSent && jar.cookies.size() > 0) { // write cookies + if (kind == YAHTTP_TYPE_REQUEST) { + bool first = true; + os << "Cookie: "; + for(strcookie_map_t::const_iterator i = jar.cookies.begin(); i != jar.cookies.end(); i++) { + if (first) + first = false; + else + os << "; "; + os << Utility::encodeURL(i->second.name) << "=" << Utility::encodeURL(i->second.value); + } + } else if (kind == YAHTTP_TYPE_RESPONSE) { + for(strcookie_map_t::const_iterator i = jar.cookies.begin(); i != jar.cookies.end(); i++) { + os << "Set-Cookie: "; + os << i->second.str() << "\r\n"; + } + } + } + os << "\r\n"; +#ifdef HAVE_CPP_FUNC_PTR + this->renderer(this, os, sendChunked); +#else + SendbodyRenderer r; + r(this, os, chunked) +#endif + }; + + std::ostream& operator<<(std::ostream& os, const Response &resp) { + resp.write(os); + return os; + }; + + std::istream& operator>>(std::istream& is, Response &resp) { + YaHTTP::AsyncResponseLoader arl; + arl.initialize(&resp); + while(is.good()) { + char buf[1024]; + is.read(buf, 1024); + if (is.gcount()>0) { // did we actually read anything + is.clear(); + if (arl.feed(std::string(buf, is.gcount())) == true) break; // completed + } + } + // throw unless ready + if (arl.ready() == false) + throw ParseError("Was not able to extract a valid Response from stream"); + arl.finalize(); + return is; + }; + + std::ostream& operator<<(std::ostream& os, const Request &req) { + req.write(os); + return os; + }; + + std::istream& operator>>(std::istream& is, Request &req) { + YaHTTP::AsyncRequestLoader arl; + arl.initialize(&req); + while(is.good()) { + char buf[1024]; + is.read(buf, 1024); + if (is.gcount() > 0) { // did we actually read anything + is.clear(); + if (arl.feed(std::string(buf, is.gcount())) == true) break; // completed + } + } + if (arl.ready() == false) + throw ParseError("Was not able to extract a valid Request from stream"); + arl.finalize(); + return is; + }; +}; diff --git a/ext/yahttp/yahttp/reqresp.hpp b/ext/yahttp/yahttp/reqresp.hpp new file mode 100644 index 0000000..4db53be --- /dev/null +++ b/ext/yahttp/yahttp/reqresp.hpp @@ -0,0 +1,351 @@ +#if __cplusplus >= 201103L +#include <functional> +#define HAVE_CPP_FUNC_PTR +namespace funcptr = std; +#else +#ifdef HAVE_BOOST +#include <boost/function.hpp> +namespace funcptr = boost; +#define HAVE_CPP_FUNC_PTR +#endif +#endif + +#include <fstream> +#include <cctype> + +#ifndef WIN32 +#include <cstdio> +#include <unistd.h> +#endif + +#include <algorithm> + +#ifndef YAHTTP_MAX_REQUEST_SIZE +#define YAHTTP_MAX_REQUEST_SIZE 2097152 +#endif + +#ifndef YAHTTP_MAX_RESPONSE_SIZE +#define YAHTTP_MAX_RESPONSE_SIZE 2097152 +#endif + +#define YAHTTP_TYPE_REQUEST 1 +#define YAHTTP_TYPE_RESPONSE 2 + +namespace YaHTTP { + typedef std::map<std::string,Cookie,ASCIICINullSafeComparator> strcookie_map_t; //<! String to Cookie map + + typedef enum { + urlencoded, + multipart + } postformat_t; //<! Enumeration of possible post encodings, url encoding or multipart + + /*! Base class for request and response */ + class HTTPBase { + public: + /*! Default renderer for request/response, simply copies body to response */ + class SendBodyRender { + public: + SendBodyRender() {}; + + size_t operator()(const HTTPBase *doc, std::ostream& os, bool chunked) const { + if (chunked) { + std::string::size_type i,cl; + for(i=0;i<doc->body.length();i+=1024) { + cl = std::min(static_cast<std::string::size_type>(1024), doc->body.length()-i); // for less than 1k blocks + os << std::hex << cl << std::dec << "\r\n"; + os << doc->body.substr(i, cl) << "\r\n"; + } + os << 0 << "\r\n\r\n"; // last chunk + } else { + os << doc->body; + } + return doc->body.length(); + }; //<! writes body to ostream and returns length + }; + /* Simple sendfile renderer which streams file to ostream */ + class SendFileRender { + public: + SendFileRender(const std::string& path_) { + this->path = path_; + }; + + size_t operator()(const HTTPBase *doc __attribute__((unused)), std::ostream& os, bool chunked) const { + char buf[4096]; + size_t n,k; +#if __cplusplus >= 201103L + std::ifstream ifs(path, std::ifstream::binary); +#else + std::ifstream ifs(path.c_str(), std::ifstream::binary); +#endif + n = 0; + + while(ifs.good()) { + ifs.read(buf, sizeof buf); + n += (k = ifs.gcount()); + if (k > 0) { + if (chunked) os << std::hex << k << std::dec << "\r\n"; + os.write(buf, k); + if (chunked) os << "\r\n"; + } + } + if (chunked) os << 0 << "\r\n\r\n"; + return n; + }; //<! writes file to ostream and returns length + + std::string path; //<! File to send + }; + + HTTPBase() { + HTTPBase::initialize(); + }; + + virtual void initialize() { + kind = 0; + status = 0; +#ifdef HAVE_CPP_FUNC_PTR + renderer = SendBodyRender(); +#endif + max_request_size = YAHTTP_MAX_REQUEST_SIZE; + max_response_size = YAHTTP_MAX_RESPONSE_SIZE; + url = ""; + method = ""; + statusText = ""; + jar.clear(); + headers.clear(); + parameters.clear(); + getvars.clear(); + postvars.clear(); + body = ""; + routeName = ""; + version = 11; // default to version 1.1 + is_multipart = false; + } +protected: + HTTPBase(const HTTPBase& rhs) { + this->url = rhs.url; this->kind = rhs.kind; + this->status = rhs.status; this->statusText = rhs.statusText; + this->method = rhs.method; this->headers = rhs.headers; + this->jar = rhs.jar; this->postvars = rhs.postvars; + this->parameters = rhs.parameters; this->getvars = rhs.getvars; + this->body = rhs.body; this->max_request_size = rhs.max_request_size; + this->max_response_size = rhs.max_response_size; this->version = rhs.version; +#ifdef HAVE_CPP_FUNC_PTR + this->renderer = rhs.renderer; +#endif + this->is_multipart = rhs.is_multipart; + }; + virtual HTTPBase& operator=(const HTTPBase& rhs) { + this->url = rhs.url; this->kind = rhs.kind; + this->status = rhs.status; this->statusText = rhs.statusText; + this->method = rhs.method; this->headers = rhs.headers; + this->jar = rhs.jar; this->postvars = rhs.postvars; + this->parameters = rhs.parameters; this->getvars = rhs.getvars; + this->body = rhs.body; this->max_request_size = rhs.max_request_size; + this->max_response_size = rhs.max_response_size; this->version = rhs.version; +#ifdef HAVE_CPP_FUNC_PTR + this->renderer = rhs.renderer; +#endif + this->is_multipart = rhs.is_multipart; + return *this; + }; +public: + URL url; //<! URL of this request/response + int kind; //<! Type of object (1 = request, 2 = response) + int status; //<! status code + int version; //<! http version 9 = 0.9, 10 = 1.0, 11 = 1.1 + std::string statusText; //<! textual representation of status code + std::string method; //<! http verb + strstr_map_t headers; //<! map of header(s) + CookieJar jar; //<! cookies + strstr_map_t postvars; //<! map of POST variables (from POST body) + strstr_map_t getvars; //<! map of GET variables (from URL) +// these two are for Router + strstr_map_t parameters; //<! map of route parameters (only if you use YaHTTP::Router) + std::string routeName; //<! name of the current route (only if you use YaHTTP::Router) + + std::string body; //<! the actual content + + ssize_t max_request_size; //<! maximum size of request + ssize_t max_response_size; //<! maximum size of response + bool is_multipart; //<! if the request is multipart, prevents Content-Length header +#ifdef HAVE_CPP_FUNC_PTR + funcptr::function<size_t(const HTTPBase*,std::ostream&,bool)> renderer; //<! rendering function +#endif + void write(std::ostream& os) const; //<! writes request to the given output stream + + strstr_map_t& GET() { return getvars; }; //<! acccessor for getvars + strstr_map_t& POST() { return postvars; }; //<! accessor for postvars + strcookie_map_t& COOKIES() { return jar.cookies; }; //<! accessor for cookies + + std::string versionStr(int version_) const { + switch(version_) { + case 9: return "0.9"; + case 10: return "1.0"; + case 11: return "1.1"; + default: throw YaHTTP::Error("Unsupported version"); + } + }; + + std::string str() const { + std::ostringstream oss; + write(oss); + return oss.str(); + }; //<! return string representation of this object + }; + + /*! Response class, represents a HTTP Response document */ + class Response: public HTTPBase { + public: + Response() { Response::initialize(); }; + Response(const HTTPBase& rhs): HTTPBase(rhs) { + this->kind = YAHTTP_TYPE_RESPONSE; + }; + Response& operator=(const HTTPBase& rhs) override { + HTTPBase::operator=(rhs); + this->kind = YAHTTP_TYPE_RESPONSE; + return *this; + }; + void initialize() override { + HTTPBase::initialize(); + this->kind = YAHTTP_TYPE_RESPONSE; + } + void initialize(const HTTPBase& rhs) { + HTTPBase::initialize(); + this->kind = YAHTTP_TYPE_RESPONSE; + // copy SOME attributes + this->url = rhs.url; + this->method = rhs.method; + this->jar = rhs.jar; + this->version = rhs.version; + } + friend std::ostream& operator<<(std::ostream& os, const Response &resp); + friend std::istream& operator>>(std::istream& is, Response &resp); + }; + + /* Request class, represents a HTTP Request document */ + class Request: public HTTPBase { + public: + Request() { Request::initialize(); }; + Request(const HTTPBase& rhs): HTTPBase(rhs) { + this->kind = YAHTTP_TYPE_REQUEST; + }; + Request& operator=(const HTTPBase& rhs) override { + HTTPBase::operator=(rhs); + this->kind = YAHTTP_TYPE_REQUEST; + return *this; + }; + void initialize() override { + HTTPBase::initialize(); + this->kind = YAHTTP_TYPE_REQUEST; + } + void initialize(const HTTPBase& rhs) { + HTTPBase::initialize(); + this->kind = YAHTTP_TYPE_REQUEST; + // copy SOME attributes + this->url = rhs.url; + this->method = rhs.method; + this->jar = rhs.jar; + this->version = rhs.version; + } + void setup(const std::string& method_, const std::string& url_) { + this->url.parse(url_); + this->headers["host"] = this->url.host.find(":") == std::string::npos ? this->url.host : "[" + this->url.host + "]"; + this->method = method_; + std::transform(this->method.begin(), this->method.end(), this->method.begin(), ::toupper); + this->headers["user-agent"] = "YaHTTP v1.0"; + }; //<! Set some initial things for a request + + void preparePost(postformat_t format = urlencoded) { + std::ostringstream postbuf; + if (format == urlencoded) { + for(strstr_map_t::const_iterator i = POST().begin(); i != POST().end(); i++) { + postbuf << Utility::encodeURL(i->first, false) << "=" << Utility::encodeURL(i->second, false) << "&"; + } + // remove last bit + if (postbuf.str().length()>0) + body = postbuf.str().substr(0, postbuf.str().length()-1); + else + body = ""; + headers["content-type"] = "application/x-www-form-urlencoded; charset=utf-8"; + } else if (format == multipart) { + headers["content-type"] = "multipart/form-data; boundary=YaHTTP-12ca543"; + this->is_multipart = true; + for(strstr_map_t::const_iterator i = POST().begin(); i != POST().end(); i++) { + postbuf << "--YaHTTP-12ca543\r\nContent-Disposition: form-data; name=\"" << Utility::encodeURL(i->first, false) << "\"; charset=UTF-8\r\nContent-Length: " << i->second.size() << "\r\n\r\n" + << Utility::encodeURL(i->second, false) << "\r\n"; + } + postbuf << "--"; + body = postbuf.str(); + } + + postbuf.str(""); + postbuf << body.length(); + // set method and change headers + method = "POST"; + if (!this->is_multipart) + headers["content-length"] = postbuf.str(); + }; //<! convert all postvars into string and stuff it into body + + friend std::ostream& operator<<(std::ostream& os, const Request &resp); + friend std::istream& operator>>(std::istream& is, Request &resp); + }; + + /*! Asynchronous HTTP document loader */ + template <class T> + class AsyncLoader { + public: + T* target; //<! target to populate + int state; //<! reader state + size_t pos; //<! reader position + + std::string buffer; //<! read buffer + bool chunked; //<! whether we are parsing chunked data + int chunk_size; //<! expected size of next chunk + std::ostringstream bodybuf; //<! buffer for body + size_t maxbody; //<! maximum size of body + size_t minbody; //<! minimum size of body + bool hasBody; //<! are we expecting body + + void keyValuePair(const std::string &keyvalue, std::string &key, std::string &value); //<! key value pair parser helper + + void initialize(T* target_) { + chunked = false; chunk_size = 0; + bodybuf.str(""); minbody = 0; maxbody = 0; + pos = 0; state = 0; this->target = target_; + hasBody = false; + buffer = ""; + this->target->initialize(); + }; //<! Initialize the parser for target and clear state + bool feed(const std::string& somedata); //<! Feed data to the parser + bool ready() { + return (chunked == true && state == 3) || // if it's chunked we get end of data indication + (chunked == false && state > 1 && + (!hasBody || + (bodybuf.str().size() <= maxbody && + bodybuf.str().size() >= minbody) + ) + ); + }; //<! whether we have received enough data + void finalize() { + bodybuf.flush(); + if (ready()) { + strstr_map_t::iterator cpos = target->headers.find("content-type"); + if (cpos != target->headers.end() && Utility::iequals(cpos->second, "application/x-www-form-urlencoded", 32)) { + target->postvars = Utility::parseUrlParameters(bodybuf.str()); + } + target->body = bodybuf.str(); + } + bodybuf.str(""); + this->target = NULL; + }; //<! finalize and release target + }; + + /*! Asynchronous HTTP response loader */ + class AsyncResponseLoader: public AsyncLoader<Response> { + }; + + /*! Asynchronous HTTP request loader */ + class AsyncRequestLoader: public AsyncLoader<Request> { + }; + +}; diff --git a/ext/yahttp/yahttp/router.cpp b/ext/yahttp/yahttp/router.cpp new file mode 100644 index 0000000..18ea9b6 --- /dev/null +++ b/ext/yahttp/yahttp/router.cpp @@ -0,0 +1,160 @@ +/* @file + * @brief Concrete implementation of Router + */ +#include "yahttp.hpp" +#include "router.hpp" + +namespace YaHTTP { + typedef funcptr::tuple<int,int> TDelim; + + // router is defined here. + YaHTTP::Router Router::router; + + void Router::map(const std::string& method, const std::string& url, THandlerFunction handler, const std::string& name) { + std::string method2 = method; + bool isopen=false; + // add into vector + for(std::string::const_iterator i = url.begin(); i != url.end(); i++) { + if (*i == '<' && isopen) throw Error("Invalid URL mask, cannot have < after <"); + if (*i == '<') isopen = true; + if (*i == '>' && !isopen) throw Error("Invalid URL mask, cannot have > without < first"); + if (*i == '>') isopen = false; + } + std::transform(method2.begin(), method2.end(), method2.begin(), ::toupper); + routes.push_back(funcptr::make_tuple(method2, url, handler, name)); + }; + + bool Router::route(Request *req, THandlerFunction& handler) { + std::map<std::string, TDelim> params; + int pos1,pos2; + bool matched = false; + std::string rname; + + // iterate routes + for(TRouteList::iterator i = routes.begin(); !matched && i != routes.end(); i++) { + int k1,k2,k3; + std::string pname; + std::string method, url; + funcptr::tie(method, url, handler, rname) = *i; + + if (method.empty() == false && req->method != method) continue; // no match on method + // see if we can't match the url + params.clear(); + // simple matcher func + for(k1=0, k2=0; k1 < static_cast<int>(url.size()) && k2 < static_cast<int>(req->url.path.size()); ) { + if (url[k1] == '<') { + pos1 = k2; + k3 = k1+1; + // start of parameter + while(k1 < static_cast<int>(url.size()) && url[k1] != '>') k1++; + pname = std::string(url.begin()+k3, url.begin()+k1); + // then we also look it on the url + if (pname[0]=='*') { + pname = pname.substr(1); + // this matches whatever comes after it, basically end of string + pos2 = req->url.path.size(); + if (pname != "") + params[pname] = funcptr::tie(pos1,pos2); + k1 = url.size(); + k2 = req->url.path.size(); + break; + } else { + // match until url[k1] + while(k2 < static_cast<int>(req->url.path.size()) && req->url.path[k2] != url[k1+1]) k2++; + pos2 = k2; + params[pname] = funcptr::tie(pos1,pos2); + } + k2--; + } + else if (url[k1] != req->url.path[k2]) { + break; + } + + k1++; k2++; + } + + // ensure. + if (url[k1] != req->url.path[k2]) + matched = false; + else + matched = true; + } + + if (!matched) { return false; } // no route + req->parameters.clear(); + + for(std::map<std::string, TDelim>::iterator i = params.begin(); i != params.end(); i++) { + int p1,p2; + funcptr::tie(p1,p2) = i->second; + std::string value(req->url.path.begin() + p1, req->url.path.begin() + p2); + value = Utility::decodeURL(value); + req->parameters[i->first] = value; + } + + req->routeName = rname; + + return true; + }; + + void Router::printRoutes(std::ostream &os) { + for(TRouteList::iterator i = routes.begin(); i != routes.end(); i++) { +#if __cplusplus >= 201103L + std::streamsize ss = os.width(); + std::ios::fmtflags ff = os.setf(std::ios::left); + os.width(10); + os << std::get<0>(*i); + os.width(50); + os << std::get<1>(*i); + os.width(ss); + os.setf(ff); + os << " " << std::get<3>(*i); + os << std::endl; +#else + os << i->get<0>() << " " << i->get<1>() << " " << i->get<3>() << std::endl; +#endif + } + }; + + std::pair<std::string,std::string> Router::urlFor(const std::string &name, const strstr_map_t& arguments) { + std::ostringstream path; + std::string mask,method,result; + int k1,k2,k3; + + bool found = false; + for(TRouteList::iterator i = routes.begin(); !found && i != routes.end(); i++) { +#if __cplusplus >= 201103L + if (std::get<3>(*i) == name) { mask = std::get<1>(*i); method = std::get<0>(*i); found = true; } +#else + if (i->get<3>() == name) { mask = i->get<1>(); method = i->get<0>(); found = true; } +#endif + } + + if (!found) + throw Error("Route not found"); + + for(k1=0,k3=0;k1<static_cast<int>(mask.size());k1++) { + if (mask[k1] == '<') { + std::string pname; + strstr_map_t::const_iterator pptr; + k2=k1; + while(k1<static_cast<int>(mask.size()) && mask[k1]!='>') k1++; + path << mask.substr(k3,k2-k3); + if (mask[k2+1] == '*') + pname = std::string(mask.begin() + k2 + 2, mask.begin() + k1); + else + pname = std::string(mask.begin() + k2 + 1, mask.begin() + k1); + if ((pptr = arguments.find(pname)) != arguments.end()) + path << Utility::encodeURL(pptr->second); + k3 = k1+1; + } + else if (mask[k1] == '*') { + // ready + k3++; + continue; + } + } + path << mask.substr(k3); + result = path.str(); + return std::make_pair(method, result); + } +}; diff --git a/ext/yahttp/yahttp/router.hpp b/ext/yahttp/yahttp/router.hpp new file mode 100644 index 0000000..205119c --- /dev/null +++ b/ext/yahttp/yahttp/router.hpp @@ -0,0 +1,72 @@ +#pragma once +/* @file + * @brief Defines router class and support structures + */ +#if __cplusplus >= 201103L +#include <functional> +#include <tuple> +#define HAVE_CPP_FUNC_PTR +#define IGNORE std::ignore +namespace funcptr = std; +#else +#ifdef HAVE_BOOST +#include <boost/function.hpp> +#include <boost/tuple/tuple.hpp> +#define IGNORE boost::tuples::ignore +namespace funcptr = boost; +#define HAVE_CPP_FUNC_PTR +#else +#warning "You need to configure with boost or have C++11 capable compiler for router" +#endif +#endif + +#ifdef HAVE_CPP_FUNC_PTR +#include <vector> +#include <utility> + +namespace YaHTTP { + typedef funcptr::function <void(Request* req, Response* resp)> THandlerFunction; //!< Handler function pointer + typedef funcptr::tuple<std::string, std::string, THandlerFunction, std::string> TRoute; //!< Route tuple (method, urlmask, handler, name) + typedef std::vector<TRoute> TRouteList; //!< List of routes in order of evaluation + + /*! Implements simple router. + +This class implements a router for masked urls. The URL mask syntax is as of follows + +/<masked>/url<number>/<hi>.<format> + +You can use <*param> to denote that everything will be matched and consumed into the parameter, including slash (/). Use <*> to denote that URL +is consumed but not stored. Note that only path is matched, scheme, host and url parameters are ignored. + */ + class Router { + private: + Router() {}; + static Router router; //<! Singleton instance of Router + public: + void map(const std::string& method, const std::string& url, THandlerFunction handler, const std::string& name); //<! Instance method for mapping urls + bool route(Request *req, THandlerFunction& handler); //<! Instance method for performing routing + void printRoutes(std::ostream &os); //<! Instance method for printing routes + std::pair<std::string, std::string> urlFor(const std::string &name, const strstr_map_t& arguments); //<! Instance method for generating paths + +/*! Map an URL. +If method is left empty, it will match any method. Name is also optional, but needed if you want to find it for making URLs +*/ + static void Map(const std::string& method, const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map(method, url, handler, name); }; + static void Get(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("GET", url, handler, name); }; //<! Helper for mapping GET + static void Post(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("POST", url, handler, name); }; //<! Helper for mapping POST + static void Put(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("PUT", url, handler, name); }; //<! Helper for mapping PUT + static void Patch(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("PATCH", url, handler, name); }; //<! Helper for mapping PATCH + static void Delete(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("DELETE", url, handler, name); }; //<! Helper for mapping DELETE + static void Any(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("", url, handler, name); }; //<! Helper for mapping any method + + static bool Route(Request *req, THandlerFunction& handler) { return router.route(req, handler); }; //<! Performs routing based on req->url.path + static void PrintRoutes(std::ostream &os) { router.printRoutes(os); }; //<! Prints all known routes to given output stream + + static std::pair<std::string, std::string> URLFor(const std::string &name, const strstr_map_t& arguments) { return router.urlFor(name,arguments); }; //<! Generates url from named route and arguments. Missing arguments are assumed empty + static const TRouteList& GetRoutes() { return router.routes; } //<! Reference to route list + static void Clear() { router.routes.clear(); } //<! Clear all routes + + TRouteList routes; //<! Instance variable for routes + }; +}; +#endif diff --git a/ext/yahttp/yahttp/url.hpp b/ext/yahttp/yahttp/url.hpp new file mode 100644 index 0000000..fbdd48d --- /dev/null +++ b/ext/yahttp/yahttp/url.hpp @@ -0,0 +1,200 @@ +#pragma once +#include <sstream> +#include <string> + +#include "utility.hpp" + +#ifndef YAHTTP_MAX_URL_LENGTH +#define YAHTTP_MAX_URL_LENGTH 2048 +#endif + +namespace YaHTTP { + /*! URL parser and container */ + class URL { + private: + bool parseSchema(const std::string& url, size_t &pos) { + size_t pos1; + if (pos >= url.size()) return false; // no data + if ( (pos1 = url.find_first_of(":",pos)) == std::string::npos ) return false; // schema is mandatory + protocol = url.substr(pos, pos1-pos); + if (protocol == "http") port = 80; + if (protocol == "https") port = 443; + pos = pos1+1; // after : + if (url.compare(pos, 2, "//") == 0) { + pathless = false; // if this is true we put rest into parameters + pos += 2; + } + return true; + }; //<! parse schema/protocol part + + bool parseHost(const std::string& url, size_t &pos) { + size_t pos1; + if (pos >= url.size()) return true; // no data + if ( (pos1 = url.find_first_of("/", pos)) == std::string::npos ) { + host = url.substr(pos); + path = "/"; + pos = url.size(); + } else { + host = url.substr(pos, pos1-pos); + pos = pos1; + } + if (host.at(0) == '[') { // IPv6 + if ((pos1 = host.find_first_of("]")) == std::string::npos) { + // incomplete address + return false; + } + size_t pos2; + if ((pos2 = host.find_first_of(":", pos1)) != std::string::npos) { + std::istringstream tmp(host.substr(pos2 + 1)); + tmp >> port; + } + host = host.substr(1, pos1 - 1); + } else if ( (pos1 = host.find_first_of(":")) != std::string::npos ) { + std::istringstream tmp(host.substr(pos1+1)); + tmp >> port; + host = host.substr(0, pos1); + } + return true; + }; //<! parse host and port + + bool parseUserPass(const std::string& url, size_t &pos) { + size_t pos1,pos2; + if (pos >= url.size()) return true; // no data + + if ( (pos1 = url.find_first_of("@",pos)) == std::string::npos ) return true; // no userinfo + pos2 = url.find_first_of(":",pos); + + if (pos2 != std::string::npos) { // comes with password + username = url.substr(pos, pos2 - pos); + password = url.substr(pos2+1, pos1 - pos2 - 1); + password = Utility::decodeURL(password); + } else { + username = url.substr(pos, pos1 - pos); + } + pos = pos1+1; + username = Utility::decodeURL(username); + return true; + }; //<! parse possible username and password + + bool parsePath(const std::string& url, size_t &pos) { + size_t pos1; + if (pos >= url.size()) return true; // no data + if (url[pos] != '/') return false; // not an url + if ( (pos1 = url.find_first_of("?", pos)) == std::string::npos ) { + path = url.substr(pos); + pos = url.size(); + } else { + path = url.substr(pos, pos1-pos); + pos = pos1; + } + return true; + }; //<! parse path component + + bool parseParameters(const std::string& url, size_t &pos) { + size_t pos1; + if (pos >= url.size()) return true; // no data + if (url[pos] == '#') return true; // anchor starts here + if (url[pos] != '?') return false; // not a parameter + if ( (pos1 = url.find_first_of("#", pos)) == std::string::npos ) { + parameters = url.substr(pos+1);; + pos = url.size(); + } else { + parameters = url.substr(pos+1, pos1-pos-1); + pos = pos1; + } + if (parameters.size()>0 && *(parameters.end()-1) == '&') parameters.resize(parameters.size()-1); + return true; + }; //<! parse url parameters + + bool parseAnchor(const std::string& url, size_t &pos) { + if (pos >= url.size()) return true; // no data + if (url[pos] != '#') return false; // not anchor + anchor = url.substr(pos+1); + return true; + }; //<! parse anchor + + void initialize() { + protocol = ""; host = ""; port = 0; username = ""; password = ""; path = ""; parameters = ""; anchor =""; pathless = true; + }; //<! initialize to empty URL + + public: + std::string to_string() const { + std::string tmp; + std::ostringstream oss; + + if (protocol.empty() == false) { + oss << protocol << ":"; + if (host.empty() == false) { + oss << "//"; + } + } + + if (username.empty() == false) { + if (password.empty() == false) + oss << Utility::encodeURL(username) << ":" << Utility::encodeURL(password) << "@"; + else + oss << Utility::encodeURL(username) << "@"; + } + if (host.empty() == false) + oss << host; + if (!(protocol == "http" && port == 80) && + !(protocol == "https" && port == 443) && + port > 0) + oss << ":" << port; + + oss << path; + if (parameters.empty() == false) { + if (!pathless) + oss << "?"; + oss << parameters; + } + if (anchor.empty() == false) + oss << "#" << anchor; + return oss.str(); + }; //<! convert this URL to string + + std::string protocol; //<! schema/protocol + std::string host; //<! host + int port; //<! port + std::string username; //<! username + std::string password; //<! password + std::string path; //<! path + std::string parameters; //<! url parameters + std::string anchor; //<! anchor + bool pathless; //<! whether this url has no path + + URL() { initialize(); }; //<! construct empty url + URL(const std::string& url) { + parse(url); + }; //<! calls parse with url + + URL(const char *url) { + parse(std::string(url)); + }; //<! calls parse with url + + bool parse(const std::string& url) { + // setup + initialize(); + + if (url.size() > YAHTTP_MAX_URL_LENGTH) return false; + size_t pos = 0; + if (*(url.begin()) != '/') { // full url? + if (parseSchema(url, pos) == false) return false; + if (pathless) { + parameters = url.substr(pos); + return true; + } + if (parseUserPass(url, pos) == false) return false; + if (parseHost(url, pos) == false) return false; + } + if (parsePath(url, pos) == false) return false; + if (parseParameters(url, pos) == false) return false; + return parseAnchor(url, pos); + }; //<! parse various formats of urls ranging from http://example.com/foo?bar=baz into data:base64:d089swt64wt... + + friend std::ostream & operator<<(std::ostream& os, const URL& url) { + os<<url.to_string(); + return os; + }; + }; +}; diff --git a/ext/yahttp/yahttp/utility.hpp b/ext/yahttp/yahttp/utility.hpp new file mode 100644 index 0000000..23e6b8a --- /dev/null +++ b/ext/yahttp/yahttp/utility.hpp @@ -0,0 +1,462 @@ +#pragma once +namespace YaHTTP { + static const char *MONTHS[] = {0,"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec",0}; //<! List of months + static const char *DAYS[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat",0}; //<! List of days + + bool isspace(char c); + bool isspace(char c, const std::locale& loc); + bool isxdigit(char c); + bool isxdigit(char c, const std::locale& loc); + bool isdigit(char c); + bool isdigit(char c, const std::locale& loc); + bool isalnum(char c); + bool isalnum(char c, const std::locale& loc); + + /*! Case-Insensitive NULL safe comparator for string maps */ + struct ASCIICINullSafeComparator { + bool operator() (const std::string& lhs, const std::string& rhs) const { + int v; + std::string::const_iterator lhi = lhs.begin(); + std::string::const_iterator rhi = rhs.begin(); + for(;lhi != lhs.end() && rhi != rhs.end(); lhi++, rhi++) + if ((v = ::tolower(*lhi) - ::tolower(*rhi)) != 0) return v<0; + if (lhi == lhs.end() && rhi != rhs.end()) return true; + if (lhi != lhs.end() && rhi == rhs.end()) return false; + return false; // they are equal + } + }; + + typedef std::map<std::string,std::string,ASCIICINullSafeComparator> strstr_map_t; //<! String to String map + + /*! Represents a date/time with utc offset */ + class DateTime { + public: + bool isSet; //<! if this is initialized yet + + int year; //<! year, 0 is year 0, not 1900 + + int month; //<! month, range 1-12 + int day; //<! day, range 1-31 + int wday; //<! week day, range 1-7 + + int hours; //<! hours, range 0-23 + int minutes; //<! minutes, range 0-59 + int seconds; //<! seconds, range 0-60 + + int utc_offset; //<! UTC offset with minutes (hhmm) + + DateTime() { + initialize(); + }; //<! Construct and initialize + + void initialize() { + isSet = false; + year = month = day = wday = hours = minutes = seconds = utc_offset = 0; + month = 1; // it's invalid otherwise + }; //<! Creates year 0 date + + void setLocal() { + fromLocaltime(time((time_t*)NULL)); + }; //<! sets current local time + + void setGm() { + fromGmtime(time((time_t*)NULL)); + }; //<! sets current gmtime (almost UTC) + + void fromLocaltime(time_t t) { +#ifdef HAVE_LOCALTIME_R + struct tm tm; + localtime_r(&t, &tm); + fromTm(&tm); +#else + struct tm *tm; + #error define HAVE_LOCALTIME_R + tm = localtime(&t); // lgtm [cpp/potentially-dangerous-function] + fromTm(tm); +#endif +#ifndef HAVE_TM_GMTOFF + time_t t2; +# ifdef HAVE_LOCALTIME_R + gmtime_r(&t, &tm); + t2 = mktime(&tm); +# else + #error define HAVE_LOCALTIME_R + tm = gmtime(&t); // lgtm [cpp/potentially-dangerous-function] + t2 = mktime(tm); +# endif + this->utc_offset = ((t2-t)/10)*10; // removes any possible differences. +#endif + }; //<! uses localtime for time + + void fromGmtime(time_t t) { +#ifdef HAVE_GMTIME_R + struct tm tm; + gmtime_r(&t, &tm); + fromTm(&tm); +#else + struct tm *tm; + #error define HAVE_GMTIME_R + tm = gmtime(&t);// lgtm [cpp/potentially-dangerous-function] + fromTm(tm); +#endif +#ifndef HAVE_TM_GMTOFF + this->utc_offset = 0; +#endif + }; //<! uses gmtime for time + + void fromTm(const struct tm *tm) { + year = tm->tm_year + 1900; + month = tm->tm_mon + 1; + day = tm->tm_mday; + hours = tm->tm_hour; + minutes = tm->tm_min; + seconds = tm->tm_sec; + wday = tm->tm_wday; +#ifdef HAVE_TM_GMTOFF + utc_offset = tm->tm_gmtoff; +#endif + isSet = true; + }; //<! parses date from struct tm + + void validate() const { + if (wday < 0 || wday > 6) throw std::range_error("Invalid date"); + if (month < 1 || month > 12) throw std::range_error("Invalid date"); + if (year < 0) throw std::range_error("Invalid date"); + if (hours < 0 || hours > 23 || + minutes < 0 || minutes > 59 || + seconds < 0 || seconds > 60) throw std::range_error("Invalid date"); + }; //<! make sure we are within ranges (not a *REAL* validation, just range check) + + std::string rfc_str() const { + std::ostringstream oss; + validate(); + oss << DAYS[wday] << ", " << std::setfill('0') << std::setw(2) << day << " " << MONTHS[month] << " " << + std::setfill('0') << std::setw(2) << year << " " << + std::setfill('0') << std::setw(2) << hours << ":" << + std::setfill('0') << std::setw(2) << minutes << ":" << + std::setfill('0') << std::setw(2) << seconds << " "; + if (utc_offset>=0) oss << "+"; + else oss << "-"; + int tmp_off = ( utc_offset < 0 ? utc_offset*-1 : utc_offset ); + oss << std::setfill('0') << std::setw(2) << (tmp_off/3600); + oss << std::setfill('0') << std::setw(2) << (tmp_off%3600)/60; + + return oss.str(); + }; //<! converts this date into a RFC-822 format + + std::string cookie_str() const { + std::ostringstream oss; + validate(); + oss << std::setfill('0') << std::setw(2) << day << "-" << MONTHS[month] << "-" << year << " " << + std::setfill('0') << std::setw(2) << hours << ":" << + std::setfill('0') << std::setw(2) << minutes << ":" << + std::setfill('0') << std::setw(2) << seconds << " GMT"; + return oss.str(); + }; //<! converts this date into a HTTP Cookie date + + void parse822(const std::string &rfc822_date) { + struct tm tm; + const char *ptr; +#ifdef HAVE_TM_GMTOFF + if ( (ptr = strptime(rfc822_date.c_str(), "%a, %d %b %Y %T %z", &tm)) != NULL) { +#else + if ( (ptr = strptime(rfc822_date.c_str(), "%a, %d %b %Y %T", &tm)) != NULL) { + int sign; + // parse the timezone parameter + while(*ptr && YaHTTP::isspace(*ptr)) ptr++; + if (*ptr == '+') sign = 0; + else if (*ptr == '-') sign = -1; + else throw YaHTTP::ParseError("Unparseable date"); + ptr++; + utc_offset = ::atoi(ptr) * sign; + while(*ptr != '\0' && YaHTTP::isdigit(*ptr)) ptr++; +#endif + while(*ptr != '\0' && YaHTTP::isspace(*ptr)) ptr++; + if (*ptr != '\0') throw YaHTTP::ParseError("Unparseable date"); // must be final. + fromTm(&tm); + } else { + throw YaHTTP::ParseError("Unparseable date"); + } + }; //<! parses RFC-822 date + + void parseCookie(const std::string &cookie_date) { + struct tm tm; + const char *ptr; + if ( (ptr = strptime(cookie_date.c_str(), "%d-%b-%Y %T", &tm)) != NULL +#ifdef HAVE_TM_GMTOFF + || (ptr = strptime(cookie_date.c_str(), "%d-%b-%Y %T %z", &tm)) != NULL + || (ptr = strptime(cookie_date.c_str(), "%a, %d-%b-%Y %T %Z", &tm)) != NULL +#endif + ) { + while(*ptr != '\0' && ( YaHTTP::isspace(*ptr) || YaHTTP::isalnum(*ptr) )) ptr++; + if (*ptr != '\0') throw YaHTTP::ParseError("Unparseable date (non-final)"); // must be final. + fromTm(&tm); + this->utc_offset = 0; + } else { + std::cout << cookie_date << std::endl; + throw YaHTTP::ParseError("Unparseable date (did not match pattern cookie)"); + } + }; //<! parses HTTP Cookie date + + time_t unixtime() const { + struct tm tm; + tm.tm_year = year-1900; + tm.tm_mon = month-1; + tm.tm_mday = day; + tm.tm_hour = hours; + tm.tm_min = minutes; + tm.tm_sec = seconds; + tm.tm_isdst = 0; +#ifdef HAVE_TM_GMTOFF + tm.tm_gmtoff = utc_offset; +#endif + return mktime(&tm); + }; //<! returns this datetime as unixtime. will not work for dates before 1970/1/1 00:00:00 GMT + }; + + /*! Various helpers needed in the code */ + class Utility { + public: + static std::string decodeURL(const std::string& component) { + std::string result = component; + size_t pos1,pos2; + pos2 = 0; + while((pos1 = result.find_first_of("%", pos2))!=std::string::npos) { + std::string code; + char a,b,c; + if (pos1 + 2 > result.length()) return result; // end of result + code = result.substr(pos1+1, 2); + a = std::tolower(code[0]); b = std::tolower(code[1]); + + if ((( '0' > a || a > '9') && ('a' > a || a > 'f')) || + (( '0' > b || b > '9') && ('a' > b || b > 'f'))) { + pos2 = pos1+3; + continue; + } + + if ('0' <= a && a <= '9') a = a - '0'; + else if ('a' <= a && a <= 'f') a = a - 'a' + 0x0a; + if ('0' <= b && b <= '9') b = b - '0'; + else if ('a' <= b && b <= 'f') b = b - 'a' + 0x0a; + + c = (a<<4)+b; + result = result.replace(pos1,3,1,c); + pos2=pos1; + } + return result; + }; //<! Decodes %xx from string into bytes + + static std::string encodeURL(const std::string& component, bool asUrl = true) { + std::string result = component; + std::string skip = "+-.:,&;_#%[]?/@(){}="; + char repl[3]; + size_t pos; + for(std::string::iterator iter = result.begin(); iter != result.end(); iter++) { + if (!YaHTTP::isalnum(*iter) && (!asUrl || skip.find(*iter) == std::string::npos)) { + // replace with different thing + pos = std::distance(result.begin(), iter); + ::snprintf(repl,3,"%02x", static_cast<unsigned char>(*iter)); + result = result.replace(pos, 1, "%", 1).insert(pos+1, repl, 2); + iter = result.begin() + pos + 2; + } + } + return result; + }; //<! Escapes any characters into %xx representation when necessary, set asUrl to false to fully encode the url + + static std::string encodeURL(const std::wstring& component, bool asUrl = true) { + unsigned char const *p = reinterpret_cast<unsigned char const*>(&component[0]); + std::size_t s = component.size() * sizeof((*component.begin())); + std::vector<unsigned char> vec(p, p+s); + + std::ostringstream result; + std::string skip = "+-.,&;_#%[]?/@(){}="; + for(std::vector<unsigned char>::iterator iter = vec.begin(); iter != vec.end(); iter++) { + if (!YaHTTP::isalnum((char)*iter) && (!asUrl || skip.find((char)*iter) == std::string::npos)) { + // bit more complex replace + result << "%" << std::hex << std::setw(2) << std::setfill('0') << static_cast<unsigned int>(*iter); + } else result << (char)*iter; + } + return result.str(); + }; //<! Escapes any characters into %xx representation when necessary, set asUrl to false to fully encode the url, for wide strings, returns ordinary string + + static std::string status2text(int status) { + switch(status) { + 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 Time-out"; + 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 Large"; + case 415: + return "Unsupported Media Type"; + case 416: + return "Requested range not satisfiable"; + case 417: + return "Expectation Failed"; + case 422: + return "Unprocessable Entity"; + 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 Time-out"; + case 505: + return "HTTP Version not supported"; + default: + return "Unknown Status"; + } + }; //<! static HTTP codes to text mappings + + static strstr_map_t parseUrlParameters(std::string parameters) { + std::string::size_type pos = 0; + strstr_map_t parameter_map; + while (pos != std::string::npos) { + // find next parameter start + std::string::size_type nextpos = parameters.find("&", pos); + std::string::size_type delim = parameters.find("=", pos); + if (delim > nextpos) { + delim = nextpos; + } + std::string key; + std::string value; + if (delim == std::string::npos) { + key = parameters.substr(pos); + } else { + key = parameters.substr(pos, delim-pos); + if (nextpos == std::string::npos) { + value = parameters.substr(delim+1); + } else { + value = parameters.substr(delim+1, nextpos-delim-1); + } + } + if (key.empty()) { + // no parameters at all + break; + } + key = decodeURL(key); + value = decodeURL(value); + parameter_map[key] = value; + if (nextpos == std::string::npos) { + // no more parameters left + break; + } + + pos = nextpos+1; + } + return parameter_map; + }; //<! parses URL parameters into string map + + static bool iequals(const std::string& a, const std::string& b, size_t length) { + std::string::const_iterator ai, bi; + size_t i; + for(ai = a.begin(), bi = b.begin(), i = 0; ai != a.end() && bi != b.end() && i < length; ai++,bi++,i++) { + if (::toupper(*ai) != ::toupper(*bi)) return false; + } + + if (ai == a.end() && bi == b.end()) return true; + if ((ai == a.end() && bi != b.end()) || + (ai != a.end() && bi == b.end())) return false; + + return ::toupper(*ai) == ::toupper(*bi); + }; //<! case-insensitive comparison with length + + static bool iequals(const std::string& a, const std::string& b) { + if (a.size() != b.size()) return false; + return iequals(a,b,a.size()); + }; //<! case-insensitive comparison + + static void trimLeft(std::string &str) { + const std::locale &loc = std::locale::classic(); + std::string::iterator iter = str.begin(); + while(iter != str.end() && YaHTTP::isspace(*iter, loc)) iter++; + str.erase(str.begin(), iter); + }; //<! removes whitespace from left + + static void trimRight(std::string &str) { + const std::locale &loc = std::locale::classic(); + std::string::reverse_iterator iter = str.rbegin(); + while(iter != str.rend() && YaHTTP::isspace(*iter, loc)) iter++; + str.erase(iter.base(), str.end()); + }; //<! removes whitespace from right + + static void trim(std::string &str) { + trimLeft(str); + trimRight(str); + }; //<! removes whitespace from left and right + + static std::string camelizeHeader(const std::string &str) { + std::string::const_iterator iter = str.begin(); + std::string result; + const std::locale &loc = std::locale::classic(); + + bool doNext = true; + + while(iter != str.end()) { + if (doNext) + result.insert(result.end(), std::toupper(*iter, loc)); + else + result.insert(result.end(), std::tolower(*iter, loc)); + doNext = (*(iter++) == '-'); + } + + return result; + }; //<! camelizes headers, such as, content-type => Content-Type + }; +}; diff --git a/ext/yahttp/yahttp/yahttp-config.h b/ext/yahttp/yahttp/yahttp-config.h new file mode 100644 index 0000000..1ac2545 --- /dev/null +++ b/ext/yahttp/yahttp/yahttp-config.h @@ -0,0 +1 @@ +#include "config.h" diff --git a/ext/yahttp/yahttp/yahttp.hpp b/ext/yahttp/yahttp/yahttp.hpp new file mode 100644 index 0000000..d60be7a --- /dev/null +++ b/ext/yahttp/yahttp/yahttp.hpp @@ -0,0 +1,37 @@ +#include <map> +#include <iostream> +#include <locale> +#include <algorithm> +#include <string> +#include <cstdio> +#include <stdexcept> +#include <sys/time.h> +#include <iomanip> +#include <list> +#include <vector> + +#include "yahttp-config.h" +#include "exception.hpp" +#include "url.hpp" +#include "utility.hpp" +#include "url.hpp" +#include "cookie.hpp" +#include "reqresp.hpp" + +/*! \mainpage Yet Another HTTP Library Documentation +\section sec_quick_start Quick start example + +@code +#include <yahttp/yahttp.hpp> + +int main(void) { + std::ifstream ifs("request.txt"); + YaHTTP::Request req; + ifs >> req; + + std::cout << req.method " " << req.url.path << std::endl; + return 0; +} +@endcode +\author Aki Tuomi +*/ |